@RequestScoped
object inside the Callable?seedMap
? Is it meant to override the default binding?Answering my own question:
static
or top-level classes are your friend here.Callable
before passing it into ServletScopes.scopeRequest(). For this reason, you must be careful what fields your Callable
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.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
, a Callable
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 second Callable
inside the request. The second Callable
can reference Foo
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
, a Callable
that inserts into the database. Notice that unlike Approach 1, we must use 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();
}
}
}
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With