Scopes¶
Reminder
By default, all guice beans are created in prototype scope. Guicey only force singleton scope for jersey extensions (resources and extensions))
Available scopes:
@Singleton- single instance per context@RequestScoped- object per request (if guice filter support is not disabled)@Prototype- prototype scope annotation (useful to override forced singleton scope for jersey services)
Session scope (@SessionScoped) is usually useless, because sessions are not enabled by default in dropwizard (but it is possible to enable them).
Tip
Scopes of registered beans could be checked in guice report
Prototype¶
Normally, prototype scope (new instance on each new injection) is the default - no need to explicitly specify it.
The only possible usage is overriding default forced singleton scope for jersey extensions. For example, resource declared like this:
@Path("/my")
public class MyResource {}
Will be singleton. Prototype scope must be explicitly declared (if required):
@Path("/my")
@Prototype
public class MyResource {}
Note
@Prototype scope annotation support is registered by guicey
Singleton¶
Both com.google.inject.Singleton and jakarta.inject.Singleton annotations could be used.
Tip
Prefer declaring @Singleton scope on all beans, except cases when different scope is required.
Request¶
By default, GuiceFilter is registered for both application and admin contexts.
And so request (and session) scopes will be available in both contexts.
@RequestScoped
public class MyRequestScopedBean { ... }
In order to access request scoped beans in other beans you'll need to use provider:
Provider<MyRequestScopedBean> myBeanProvider;
Some jersey objects are already bound in request scope
Context request and response objects are also available through request scope:
Provider<HttpServletRequest> requestProvider
Provider<HttpServletResponse> responseProvider
Request scope transition¶
This is guice feature, it is just mentioned here.
Guice could access request scoped bindings only in current thread. If you need to access request scoped binding in different thread, you need to transfer request scope into that thread:
@Singleton
public class RequestBean {
// provider will not work in other thread because of request scope
@Inject
Provider<UriInfo> uri;
public void doSomethingInRequestScope() {
// jersey object must be resolved inside hk request scope (to store it in guice request scope)
// so guice could see its instance later in another thread
uri.get();
// wrap logic that require request scope
Callable<String> action = ServletScopes.transferRequest(() -> {
// access request scoped binding in different thread
return uri.get().getQueryParameters().getFirst("q");
});
CompletableFuture.runAsync(() -> {
try {
// execute action in different thread
action.call();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
Warning
Pay attention that for jersey scope objects provider must be called first in the current thread!
Guice would be able to propagate to another thread only objects already present in guice request scope.
Without it, provider.get(), called under different thread would be delegated to jersey,
which is not aware of this thread and so fail to provide its request scope object.
Such additional call is not required for pure guice-managed request scope objects.
Note that you can't use scope inside the scope (e.g. call one transferRequest action inside another). If you need to transfer scope to another sub-thread (3rd thread), make sure that the transferRequest action will be called after the current action closes.
Hint
The current scope object is stored in thread locale (for http it's GuiceFilter.localContext).
Each time you call ServletScopes.transferRequest it gets this context from thread local.
This means all transferRequest actions, created in the current thread (or inside such action)
will use THE SAME context instance.
There is a simple ReentrantLock in context which prevents simultaneous context usage
from multiple threads (locks scope opening). So if you're going to spawn and wait for another thread, calling
transferRequest actions inside current action, you'll get a dead lock.
Pay attention, that spawing a new thread, using request context from current scope is completely normal as long as you don't wait for the result (lock will release as soon as your current context will close).
If you need to spawn a new thread (requiring request scope) and wait for its result within trasferRequest scope,
you can prepare several transfer actions ahead of time (separate action for each thread):
// action for the first thread
final Callable<String> action1 = ServletScopes.transferRequest(...);
// action for the sub-furst thread
final Callable<String> action2 = ServletScopes.transferRequest(...);
// note: both actions share the same context instance
CompletableFuture.supplyAsync(() -> {
action1.call();
CompletableFuture.supplyAsync(() -> {
action2.call();
}).join();
}).join()
or returning another scoped action from the first one (if the first thread result must be used in the third thread):
final Callable<Callable<String>> action1 = ServletScopes.transferRequest(() -> {
// do something and then action for sub-thread
return ServletScopes.transferRequest(...);
});
// in this case the context instance will also be THE SAME
CompletableFuture.supplyAsync(() -> {
final Callable<String> action2 = action1.call();
CompletableFuture.supplyAsync(() -> {
action2.call();
}).join();
}).join()
Request scope simulation¶
Sometimes, request scoped beans may need to be used somewhere without request (for example, inside scheduled job). Of course, this is not correct situation, and the best way is to re-design services, but not always possible.
As a workaround, request scope could be simulated:
@Inject
Provider<RScopedService> service;
...
final RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
try (final RequestScoper.CloseableScope ignored = scope.open()) {
// work with request-scoped bean
service.get().doSomething();
}
Eager singleton¶
By default, guicey create injector in PRODUCTION stage, so all registered singletons
will be instantiated immediately.
But if you rely on guice JIT (instantiation by injection) it may defer bean creation (until it will be requested first time).
To always start beans (even in DEVELOPMENT stage) guice provide eager singleton option:
bind(MyService.class).asEagerSingleton().
For cases when you don't want to manually declare bean, but require it to start with guice context you can either implement Managed or mark bean as @EagerSingleton (the latter will simply bind annotated bean as guice eager singleton instead of you).