问题
I have the following problem with Guice: a singleton service, is injected with provider of context-sensitive information. Until now, context was related only to servlet requests, so I used a @RequestScoped provider, and I was injecting this provider in service like so:
@RequestScoped
public class ContextProvider<IContext> implements Provider<IContext> {
@Override
public IContext get() { ... } // returns context
}
@Singleton
public class ServiceImpl implements IService {
@Inject
private Provider<IContext> contextProvider;
}
That works fine. Now, I'm working on adding background task processing to the application. Background tasks are not initiated from web-requests, so I can not use ServletScopes.scopeRequest(..). I have written a custom scope (almost exact copy of BatchScoped from Giuce doc) to make each Task run in it's own scope. Now the question is - how to make BatchScoped ContextProvider and configure Guice to use it?
I've made this attempt with binding EDSL:
line 1 : bind(IContext.class).toProvider(ContextProvider.class).in(RequestScoped.class);
line 2 : bind(IContext.class).toProvider(BatchContextProvider.class).in(BatchScoped.class);
but Guice tells me at line 2 that 'A binding to IContext was already configured at line 1'.
The question is: what's the right way of doing such injection with Guice?
回答1:
A similar question: Getting multiple guice singletons of the same type
In general the problem here is that you want to bind the same class to two different providers (and scopes, but that's actually beside the point). That is only possible if you use unique binding annotations for each one, like so:
bind(IContext.class)
.annotatedWith(MyAnnotation1.class)
.toProvider(ContextProvider.class)
.in(RequestScoped.class);
bind(IContext.class)
.annotatedWith(MyAnnotation2.class)
.toProvider(BatchContextProvider.class)
.in(BatchScoped.class);
And change injection sites to include relevant annotation:
@Inject
@MyAnnotationX
private Provider<IContext> contextProvider;
回答2:
You need a fake request that starts with your background task and remains for all of it. That is what ServletScopes.scopeRequest
does.
public class MyBackgroundTask extends Thread {
@Override
public void run() {
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
try ( RequestScoper.CloseableScope ignored = scope.open() ) {
doTask();
}
}
private void doTask() {
}
}
Oh, don't forget to use providers so you delay the retrieval of your dependencies. For example, expading the previous example so the background task uses your IContext
.
public class MyBackgroundTask extends Thread {
private Provider<IContext> contextProvider;
@Inject
public MyBackgroundTask(Provider<IContext> contextProvider) {
this.contextProvider = contextProvider;
}
@Override
public void run() {
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
try ( RequestScoper.CloseableScope ignored = scope.open() ) {
doTask();
}
}
private void doTask() {
}
}
If you don't use providers the injection, in this example, will be done from the thread that creates the background task which could be inside another scope.
BONUS: You may have noticed the empty map sent as a parameter to the scopeRequest
method. Check the Guice javadocs. Those are the instances that you want already present in your fake request scope. Depending on your IContext
you may need it.
来源:https://stackoverflow.com/questions/27336407/using-the-provider-from-two-different-scopes