Как использовать ServletScopes.scopeRequest () и ServletScopes.continueRequest ()? - PullRequest
8 голосов
/ 06 декабря 2011
  1. Как предполагается использовать ServletScopes.scopeRequest () ?
  2. Как мне получить ссылку на @RequestScoped объект внутри Callable?
  3. Какой смысл seedMap? Это означает переопределение привязки по умолчанию?
  4. В чем разница между этим методом и ServletScopes.continueRequest () ?

1 Ответ

12 голосов
/ 13 января 2012

Отвечая на мой вопрос:

  1. ServletScopes.scopeRequest () запускает функцию Callable в новой области запроса. Будьте осторожны, чтобы не ссылаться на объекты в разных областях, в противном случае вы столкнетесь с проблемами потоков, такими как попытка использовать соединение с базой данных, которое уже было закрыто другим запросом. static или классы верхнего уровня здесь ваши друзья.
  2. Вы вводите Callable перед передачей его в ServletScopes.scopeRequest () . По этой причине вы должны внимательно следить за тем, какие поля содержит ваш Callable. Подробнее об этом ниже.
  3. seedMap позволяет вводить объекты без области действия в область действия. Это опасно, поэтому будьте осторожны с тем, что вы вводите.
  4. ServletScopes.continueRequest () аналогичен, за исключением того, что он выполняется внутри существующей области запроса. Он берет снимок текущей области HTTP и оборачивает его в Callable. Исходный HTTP-запрос завершается (вы возвращаете некоторый ответ от сервера), но затем завершаете фактическую операцию асинхронно в отдельном потоке. Когда Callable вызывается позднее (в этом отдельном потоке), у него будет доступ к исходному HttpServletRequest, но не к HTTP-ответу или сеансу.

Итак, как лучше всего это сделать?

Если вам не нужно передавать пользовательские объекты в Callable: введите Callable вне области запроса и передайте его в ServletScopes.scopeRequest () . Callable может ссылаться только на Provider<Foo> вместо Foo, в противном случае вы получите экземпляры, внедренные за пределы области запроса.

Если вам нужно передать пользовательские объекты в Callable, читайте дальше.

Скажем, у вас есть метод, который вставляет имена в базу данных. У нас есть два способа передать имя в Callable.

Подход 1 : передача пользовательских объектов с использованием дочернего модуля:

  1. Определите InsertName, Callable, который вставляется в базу данных:

    @RequestScoped
    private static class InsertName implements Callable<Boolean>
    {
      private final String name;
      private final Connection connection;
    
      @Inject
      public InsertName(@Named("name") String name, Connection connection)
      {
        this.name = name;
        this.connection = connection;
      }
    
      @Override
      public Boolean call()
      {
        try
        {
          boolean nameAlreadyExists = ...;
          if (!nameAlreadyExists)
          {
            // insert the name
            return true;
          }
          return false;
        }
        finally
        {
          connection.close();
        }
      }
    }
    
  2. Свяжите все пользовательские объекты в дочернем модуле и определите область действия вызываемого объекта, используя RequestInjector.scopeRequest ():

    requestInjector.scopeRequest(InsertName.class, new AbstractModule()
    {
      @Override
      protected void configure()
      {
        bind(String.class).annotatedWith(Names.named("name")).toInstance("John");
      }
    })
    
  3. Мы создаем RequestInjector вне запроса, а он, в свою очередь, вводит второй Callable внутрь запроса. Второй Callable может ссылаться на Foo напрямую (не требуется для провайдеров), поскольку он внедряется в область запроса.

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.servlet.ServletScopes;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * Injects a Callable into a non-HTTP request scope.
 * <p/>
 * @author Gili Tzabari
 */
public final class RequestInjector
{
    private final Map<Key<?>, Object> seedMap = Collections.emptyMap();
    private final Injector injector;

    /**
     * Creates a new RequestInjector.
     */
    @Inject
    private RequestInjector(Injector injector)
    {
        this.injector = injector;
    }

    /**
     * Scopes a Callable in a non-HTTP request scope.
     * <p/>
     * @param <V> the type of object returned by the Callable
     * @param callable the class to inject and execute in the request scope
     * @param modules additional modules to install into the request scope
     * @return a wrapper that invokes delegate in the request scope
     */
    public <V> Callable<V> scopeRequest(final Class<? extends Callable<V>> callable,
        final Module... modules)
    {
        Preconditions.checkNotNull(callable, "callable may not be null");

        return ServletScopes.scopeRequest(new Callable<V>()
        {
            @Override
            public V call() throws Exception
            {
                return injector.createChildInjector(modules).getInstance(callable).call();
            }
        }, seedMap);
    }
}

Подход 2 : введите Callable вне запроса, который ссылается на Provider<Foo>. Метод call() может затем get() фактические значения внутри области запроса. Объектные объекты передаются с помощью seedMap (лично я считаю этот подход нелогичным):

  1. Определите InsertName, Callable, который вставляется в базу данных. Обратите внимание, что в отличие от подхода 1, мы должны использовать Providers:

    @RequestScoped
    private static class InsertName implements Callable<Boolean>
    {
      private final Provider<String> name;
      private final Provider<Connection> connection;
    
      @Inject
      public InsertName(@Named("name") Provider<String> name, Provider<Connection> connection)
      {
        this.name = name;
        this.connection = connection;
      }
    
      @Override
      public Boolean call()
      {
        try
        {
          boolean nameAlreadyExists = ...;
          if (!nameAlreadyExists)
          {
            // insert the name
            return true;
          }
          return false;
        }
        finally
        {
          connection.close();
        }
      }
    }
    
  2. Создайте фиктивные привязки для типов, которые вы хотите передать. Если нет, вы получите: No implementation for String annotated with @com.google.inject.name.Named(value=name) was bound. https://stackoverflow.com/a/9014552/14731 объясняет, почему это необходимо.

  3. Заполните seedMap желаемыми значениями:

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.<Key<?>, Object>of(Key.get(String.class, Names.named("name")), "john");
    
  4. Invoke ServletScopes.scopeRequest():

    ServletScopes.scopeRequest(injector.getInstance(InsertName.class), seedMap);
    
...