This package is a toolkit for building custom scopes. On the one hand, Guice's own docs advise against doing that; this package aims to make it safe for mere mortals to do.
Reasons you might want to do that:
Injector,
or you get to take advantage of Guice but the users of your
framework can't - at least not in any non-trivial way.
Custom scopes provide a reasonable way to provide a pile of objects which should be injectable into objects down the call-chain, and a few utilities for doing multithreading in an environment with scopes.
This is a fairly small library, as it should be. The heart of it is
AbstractScope. Typically
you will create an instance of ReentrantScope
or SingleEntryScope.
You enter the scope with some objects which are made available
for injection. The enter() method returns an
AutoCloseable which ensure the scope is exited properly.
// bind the types you will use inside your scope in your Guice
// module!
AbstractScope scope = new ReentrantScope();
try (AutoCloseable ac = scope.enter("hello")) {
// here the injector can inject "hello" for String.class
}
Probably you noticed that inside the try block above,
you will still have to write code that directly handles the injector.
On the one hand, that's frowned upon. On the other hand, something
is touching the injector if you're using AssistedInject, so the idea
that there is something evil about doing that was always a fiction.
On the other hand, we do provide a way to encapsulate doing that and
do it in a consistent way. For that case, you simply subclass
Runnable or Callable and pass the Class
object for it to a ScopeRunner for your scope.
If, as is commonly the case, your application has only one "request"
or request-like scope, the simplest thing is to bind AbstractScope
to the scope instance you want to use (this also makes it easier to
write tests for scope contents by replacing the scope in tests).
Here is a simple example of how this works in practice
@Test
public void testScopeRunner() throws Exception {
ScopesModule module = new ScopesModule();
Injector inj = Guice.createInjector(module);
// We will inject this StringBuilder into an instance of C below
StringBuilder constant = new StringBuilder("hello");
// ScopeRunner wraps the Injector and lets us instantiate C and get it
// injected with objects only available in the scope
ScopeRunner r = inj.getInstance(ScopeRunner.class);
AbstractScope scope = r.scope();
try (AutoCloseable cl = scope.enter(constant)) {
assertTrue(scope.inScope());
StringBuilder sb = r.call(C.class);
assertNotNull(sb);
assertSame(constant, sb);
assertEquals("hello", sb.toString());
}
}
static class C implements Callable<StringBuilder> {
private final StringBuilder sb;
@Inject
C(StringBuilder sb) {
this.sb = sb;
}
@Override
public StringBuilder call() throws Exception {
return sb;
}
}
private static final class ScopesModule extends AbstractModule {
@Override
protected void configure() {
ReentrantScope scope = new ReentrantScope();
bind(AbstractScope.class).toInstance(scope);
scope.bind(binder(), String.class, StringBuilder.class, Integer.class);
}
}
Guice's scopes are typically just magical wrappers for ThreadLocals, and in fact that's what the Scope subclasses here are, for the most part.
One thing which is useful and not generally simple, once you're dealing with scopes, is the notion that you might need to re-dispatch work on another thread. For example, one use of this project is in a framework which uses Netty to do single-threaded, highly scalable web servers. If you need to actually do blocking work, you have to have a way to shuffle that work off into a thread-pool. If you're using injection, of course it would be nice to be able to bring the scope contents with you.
So the one other thing this library does is make it easy to do that -
just use AbstractScope.wrapThreadPool(ExecutorService)
to wrap a JDK thread-pool with one which lets you:
On the one hand, this means that any objects that were in-scope when something was submitted to the thread pool will exist until the runnable or callable you submitted has exited. On the other hand, most other ways to do this sort of callback programming do the same thing based on the visibility of variables.