Scopes

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:

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.

Using Custom 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);
        }
    }
        

Thread Pools

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.