Отвечая на мой вопрос:
- ServletScopes.scopeRequest () запускает функцию Callable в новой области запроса. Будьте осторожны, чтобы не ссылаться на объекты в разных областях, в противном случае вы столкнетесь с проблемами потоков, такими как попытка использовать соединение с базой данных, которое уже было закрыто другим запросом.
static
или классы верхнего уровня здесь ваши друзья.
- Вы вводите
Callable
перед передачей его в ServletScopes.scopeRequest () . По этой причине вы должны внимательно следить за тем, какие поля содержит ваш Callable
. Подробнее об этом ниже.
seedMap
позволяет вводить объекты без области действия в область действия. Это опасно, поэтому будьте осторожны с тем, что вы вводите.
- ServletScopes.continueRequest () аналогичен, за исключением того, что он выполняется внутри существующей области запроса. Он берет снимок текущей области HTTP и оборачивает его в Callable. Исходный HTTP-запрос завершается (вы возвращаете некоторый ответ от сервера), но затем завершаете фактическую операцию асинхронно в отдельном потоке. Когда Callable вызывается позднее (в этом отдельном потоке), у него будет доступ к исходному HttpServletRequest, но не к HTTP-ответу или сеансу.
Итак, как лучше всего это сделать?
Если вам не нужно передавать пользовательские объекты в Callable
: введите Callable
вне области запроса и передайте его в ServletScopes.scopeRequest () . Callable
может ссылаться только на Provider<Foo>
вместо Foo
, в противном случае вы получите экземпляры, внедренные за пределы области запроса.
Если вам нужно передать пользовательские объекты в Callable
, читайте дальше.
Скажем, у вас есть метод, который вставляет имена в базу данных. У нас есть два способа передать имя в Callable
.
Подход 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();
}
}
}
Свяжите все пользовательские объекты в дочернем модуле и определите область действия вызываемого объекта, используя RequestInjector.scopeRequest ():
requestInjector.scopeRequest(InsertName.class, new AbstractModule()
{
@Override
protected void configure()
{
bind(String.class).annotatedWith(Names.named("name")).toInstance("John");
}
})
Мы создаем 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
(лично я считаю этот подход нелогичным):
Определите 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();
}
}
}
Создайте фиктивные привязки для типов, которые вы хотите передать. Если нет, вы получите: No implementation for String annotated with @com.google.inject.name.Named(value=name) was bound.
https://stackoverflow.com/a/9014552/14731 объясняет, почему это необходимо.
Заполните seedMap желаемыми значениями:
ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.<Key<?>, Object>of(Key.get(String.class, Names.named("name")), "john");
Invoke ServletScopes.scopeRequest()
:
ServletScopes.scopeRequest(injector.getInstance(InsertName.class), seedMap);