- How is one supposed to use ServletScopes.scopeRequest()?
- How do I get a reference to a
@RequestScoped
object inside the Callable? - What's the point of
seedMap
? Is it meant to override the default binding? - What's the difference between this method and ServletScopes.continueRequest()?

- 86,244
- 97
- 390
- 689
1 Answers
Answering my own question:
- ServletScopes.scopeRequest() runs a Callable in a new request scope. Be careful not to reference objects across different scopes, otherwise you'll end up with threading issues such as trying to use a database connection that has already been closed by another request.
static
or top-level classes are your friend here. - You inject the
Callable
before passing it into ServletScopes.scopeRequest(). For this reason, you must be careful what fields yourCallable
contains. More on this below. seedMap
allows you to inject non-scoped objects into the scope. This is dangerous so be careful with what you inject.- ServletScopes.continueRequest() is similar except that it runs inside an existing request scope. It takes a snapshot of the current HTTP scope and wraps it in a Callable. The original HTTP request completes (you return some response from the server) but then complete the actual operation asynchronously in a separate thread. When the Callable is invoked at some later time (in that separate thread) it will have access to the original HttpServletRequest but not the HTTP response or session.
So, what's the best way to do this?
If you don't need to pass user-objects into the Callable
: Inject the Callable
outside the request scope, and pass it into ServletScopes.scopeRequest(). The Callable
may only reference Provider<Foo>
instead of Foo
, otherwise you'll end up with instances injected outside of the request scope.
If you need to pass user-objects into the Callable
, read on.
Say you have a method that inserts names into a database. There are two ways for us to pass the name into the Callable
.
Approach 1: Pass user-objects using a child module:
Define
InsertName
, aCallable
that inserts into the database:@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(); } } }
Bind all user-objects in a child module and scope the callable using RequestInjector.scopeRequest():
requestInjector.scopeRequest(InsertName.class, new AbstractModule() { @Override protected void configure() { bind(String.class).annotatedWith(Names.named("name")).toInstance("John"); } })
We instantiate a
RequestInjector
outside the request and it, in turn, injects a secondCallable
inside the request. The secondCallable
can referenceFoo
directly (no need for Providers) because it's injected inside the request scope.
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);
}
}
Approach 2: Inject a Callable
outside the request that references Provider<Foo>
. The call()
method can then get()
the actual values inside the request scope. The object objects are passed in by way of a seedMap
(I personally find this approach counter-intuitive):
Define
InsertName
, aCallable
that inserts into the database. Notice that unlike Approach 1, we must useProviders
:@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(); } } }
Create bogus bindings for the types you want to pass in. If you don't you will get:
No implementation for String annotated with @com.google.inject.name.Named(value=name) was bound.
https://stackoverflow.com/a/9014552/14731 explains why this is needed.Populate the seedMap with the desired values:
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);