18

Imagine I have a type Disposable which some classes implement:

class FactoryImpl implements Disposable {}

I can bind this class as a singleton:

bind(Factory.class)
.to(FactoryImpl.class)
.in(Singleton.class);

or as an eager singleton:

bind(Factory.class)
.to(FactoryImpl.class)
.asEagerSingleton();

Note that the implementation has the type, not the interface.

How can I find all singletons which Guice has actually created and which implement the type Disposable?

Note that I don't want to blindly call get() in the provider to avoid to create stuff which I don't need (especially since I'm destroying singletons, so creating new ones might cause problems).

This is the opposite of questions like How can I get all singleton instances from a Guice Injector? which only work then the interface contains the keys that you need.

[EDIT] This is how far I got. Is this code correct?

First, I need my interface.

public interface Disposable {
    public void dispose();
}

The magic happens here:

import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.beust.jcommander.internal.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import com.google.inject.util.Modules;

/** Support for disposable beans. */
@Singleton
public class DisposableListener implements InjectionListener<Object> {

    private static final Logger log = LoggerFactory.getLogger(DisposableListener.class);

    /** Use this method to create the injector */
    public static Module createModule(Module ...modules) {
        /* Create a new module with ourself at the start. That way, our listeners will see all bindings. */
        List<Module> list = Lists.newArrayList(new DisposingModule());
        Collections.addAll(list, modules);
        return Modules.combine(list);
    }

    /** To dispose all disposables, call this method.
     * 
     *  <p>Good places to call this is at the end of {@code main()},
     *  in an destroy listener of a {@link javax.servlet.ServletContext}, or after a test.
     */
    public static void dispose(Injector injector) {
        injector.getInstance(DisposableListener.class).disposeAll();
    }

    /** Everything that is disposable */
    private List<Disposable> beans = Lists.newArrayList();

    private void disposeAll() {
        log.debug("Disposing {} beans", beans.size());

        for(Disposable bean: beans) {
            try {
                bean.dispose();
            } catch(Exception e) {
                log.warn("Error disposing {}", bean, e);
            }
        }
    }

    @Override
    public void afterInjection(Object injectee) {
        if(injectee instanceof Disposable) {
            log.debug("Noticed disposable bean {}", injectee);
            beans.add((Disposable) injectee);
        }
    }

    /** Module which creates the {@link DisposableListener} for the injector and sets everything up. */
    private static class DisposingModule extends AbstractModule {

        @Override
        protected void configure() {
            DisposableListener disposableListener = new DisposableListener();

            /* Attach a type listener to Guice which will add disposableListener to all types which extend Disposable */
            bindListener(TypeMatchers.subclassesOf(Disposable.class), new TypeListener() {

                @Override
                public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
                    Class<?> clazz = type.getRawType();
                    log.debug("Found disposable: {}", clazz);
                    encounter.register(disposableListener);
                }
            });

            /* Add the listener instance to the module, so we can get it later */
            bind(DisposableListener.class)
            .toInstance(disposableListener);
        }
    }
}

The code wraps the other modules and makes sure the DisposableListener is installed in the injector early on. Then it listens for new instances which are created and collects them in a list.

The code probably should check that these are all singletons but I don't know how to do that.

Here are the unit tests:

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.beust.jcommander.internal.Lists;
import com.google.common.base.Joiner;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Singleton;

public class DisposableListenerTest {

    private static List<String> events = Lists.newArrayList();

    @Before
    public void clearEvents() {
        events.clear();
    }

    @Test
    public void testEagerNoGetInstance() {

        Injector injector = Guice.createInjector(DisposableListener.createModule(new TestEagerSingleton()));
        // No call to getInstance()
        DisposableListener.dispose(injector);

        assertEvents("Foo created", "Foo disposed");
    }

    @Test
    public void testEagerGetInstance() {

        Injector injector = Guice.createInjector(DisposableListener.createModule(new TestEagerSingleton()));
        Foo inst1 = injector.getInstance(Foo.class);
        Foo inst2 = injector.getInstance(Foo.class);
        DisposableListener.dispose(injector);

        assertSame(inst1, inst2); // validate singleton

        assertEvents("Foo created", "Foo disposed");
    }

    @Test
    public void testLazyNoGetInstance() {

        Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton()));
        // No call to getInstance()
        DisposableListener.dispose(injector);

        assertEvents();
    }

    @Test
    public void testLazyGetInstance() {

        Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton()));
        Foo inst1 = injector.getInstance(Foo.class);
        Foo inst2 = injector.getInstance(Foo.class);
        DisposableListener.dispose(injector);

        assertSame(inst1, inst2); // validate singleton

        assertEvents("Foo created", "Foo disposed");
    }

    @Test
    public void testAnnotation() {

        Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton()));
        FooWithAnnotation inst1 = injector.getInstance(FooWithAnnotation.class);
        FooWithAnnotation inst2 = injector.getInstance(FooWithAnnotation.class);
        DisposableListener.dispose(injector);

        assertSame(inst1, inst2); // validate singleton

        assertEvents("FooWithAnnotation created", "FooWithAnnotation disposed");
    }

    private void assertEvents(String...expectedEvents) {
        Joiner joiner = Joiner.on('\n');
        String expected = joiner.join(expectedEvents);
        String actual = joiner.join(events);
        assertEquals(expected, actual);
    }

    public static class Foo implements Disposable {

        public Foo() {
            events.add("Foo created");
        }

        @Override
        public void dispose() {
            events.add("Foo disposed");
        }

    }

    @Singleton
    public static class FooWithAnnotation implements Disposable {

        public FooWithAnnotation() {
            events.add("FooWithAnnotation created");
        }

        @Override
        public void dispose() {
            events.add("FooWithAnnotation disposed");
        }

    }

    public static class TestLazySingleton extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).in(Singleton.class);
        }
    }

    public static class TestEagerSingleton extends AbstractModule {

        @Override
        protected void configure() {
            bind(Foo.class).asEagerSingleton();
        }
    }

    // TODO test when bean isn't a singleton
}
Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820

1 Answers1

1

Don't re-invent scopes

First off, manually "disposing" of singleton Guice bindings is somewhat putting the cart before the horse. Instead of binding objects as singletons and then needing to regularly clean them up, you should use a more appropriate scope (or define your own) so that these objects have natural life cycles for the duration of time that they're expected to exist, such as for a single request or test.

This is evidenced by the documentation on DisposableListener.dispose():

Good places to call this is at the end of main(), in an destroy listener of a ServletContext, or after a test

None of those are place you should need something like this:

  1. When .main() terminates the JVM will soon terminate too (and presumably your injector will go out of scope) so there's generally no need to do any such cleanup before letting the binary terminate.

  2. Similarly when a ServletContext has been destroyed you're generally just about to terminate the JVM, so just let it exit normally.

  3. In tests you should normally be constructing isolated injectors for each test, thereby avoiding any cross-test pollution. When the test ends the injector and all its bindings goes out of scope, and there should be nothing to clean up.

Manage resources separately from Guice

Of course, you could be creating objects that need to be cleaned up, such as an AutoCloseable instance, but that shouldn't be the responsibility of Guice. Generally the .getInstance() call site that obtains the closeable resource should be responsible for cleaning it up. Alternatively the module(s) could be responsible for creating and managing these resources. Then you construct the injector inside a try-with-resources block that manages the lifecycle of the resources module(s).

If those options aren't sufficient and you really need more powerful life cycle semantics use a proper life cycle framework such as Guava's ServiceManager, rather than co-opting Guice into one.

That said, having objects that require cleanup in Guice is itself generally not a good idea. Consider instead binding a wrapper type that allows the caller to open (and close) resources as needed, rather than binding a long-lived stateful resource object directly.

Prefer explicit bindings over Guice listeners

If you really, really need to collect several unrelated objects bound in a Guice injector, do so explicitly at .configure() time, rather than implicitly via introspection. Using a Multibinder allows your modules to explicitly declare which objects need to be disposed of, by binding them to a Multibinder<Disposable> instance which aggregates them all. Then your cleanup step is simply:

for (Disposable resource : injector.getInstance(new Key<Set<Disposable>>() {}) {
  resource.dispose();
}

This avoids the "magic" of a listener that silently comes in and cleans up after you, instead allowing module authors to determine how best to handle the resources they bind, and optionally taking advantage of this cleanup functionality if necessary.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • I need this code for database or JMS connections and other resources where I need to do cleanup at specific points in time. I'd use a scope then I'd need to save that scope somewhere. The multi-binding thing is nice but I really want to select by type so I can close all those JMS connections and execution pools and other stuff that were created all over the place. – Aaron Digulla May 30 '17 at 13:08
  • Guice isn't a good tool for resource management, and trying to shoehorn it into being one is going to be unpleasant. Another option is to create a wrapper that manages opening and closing these resources, and have Guice inject that wrapper type instead. – dimo414 May 30 '17 at 21:10
  • In my case, the injector has a life cycle. I create them and I destroy them. They don't live as long as the VM. I have many of them. I really need to be able to close all resources which were created by the injector at a certain point in time. But Guice has no nice way to get a collection of all resources (as you said). I can't get rid of Guice (and I don't want to), so I have to make this work. – Aaron Digulla Jun 01 '17 at 12:58
  • I really wish Guice had a "iterate over all instances which it created" method. – Aaron Digulla Jun 01 '17 at 12:59
  • Regularly creating and destroying injectors is, similarly, an antipattern. It really sounds like you're trying to put a square peg in a round hole. I'd strongly encourage you to step back and consider other approaches. (1) you don't care about *all* instance, just certain ones (2) in order to provide such a feature Guice would need to hold references to all such instances, which would be a huge memory leak. It's really not clear to me why you need Guice to solve this problem for you. – dimo414 Jun 01 '17 at 16:13
  • Just create a holder object of some sort, and have your providers add the resources to a thread-safe data structure (such as a set multibinder) and then close all such resources before destroying the injector. Guice is a tool for dependency injection, not resource management. – dimo414 Jun 01 '17 at 16:15
  • I think the problem boils down to: How can I create a "Resource" scope in such a way that I can ask an injector for it? Maybe the most simple solution would be to create a wrapper for Injector (which just delegates to the real one) plus a field for the resource scope. That way, I could call `wrapper.destroy()` which would trigger the cleanup. – Aaron Digulla Jun 06 '17 at 12:25
  • Don't ask the injector for a resource, ask it for a thing that *provides* resources. Then, the code that actually needs the resource will clearly be responsible for opening and closing it. Otherwise, ask the injector for a thing that *manages* resources, therefore it becomes the single thing responsible for opening/closing the resources. There's simply no need to do something like create a decorated injector or otherwise shoehorn Guice into doing what you're envisioning. – dimo414 Jun 06 '17 at 15:48
  • That just changes the problem into "how can I get all managers from Guice". In this scenario, managers need to be singletons, so Guice has to track them somehow. But I can't find a simple API to get the Singleton scope from Guice. – Aaron Digulla Jun 08 '17 at 15:47
  • "How can I get all managers from Guice" -> With a [`Multibinder`](https://github.com/google/guice/wiki/Multibindings). – dimo414 Jun 09 '17 at 08:20
  • I'd like something that works without me having to write a lot of code. If I forget to register a manager, then bad things happen without anyone noticing. That's why I wanted to register a listener which collects beans by some interface or annotation. And we're back at scopes. Looking at the JavaDoc for SingletonScope, I begin to doubt that Guice is the right tool for our needs in general. Iterating over beans in Spring had some pitfalls but at least it wasn't hostile to the idea. – Aaron Digulla Jun 09 '17 at 09:57
  • Honestly, it really isn't that complicated. Create a ManagerModule that binds your manager and also adds it to the multibinder. Add something that loops over the multibinder's elements at clean-up time and ensures they're all closed. – dimo414 Jun 09 '17 at 18:17
  • How can I write a unit test that makes sure no one forgot to add anything to the multibinder? – Aaron Digulla Jun 15 '17 at 08:12
  • You're now asking "how do I verify that no one forgets to close a resource across my binary" which again has nothing to do with Guice (someone could create one and not bind it at all, and you'd still want to know about it). One option would be to maintain a private static `Set` that every resource adds itself to at construction time, and which you assert is empty at the end of your integration tests (not unit tests). Another option would be to provide the module yourself and have users just install that, rather than specify the bindings themselves. – dimo414 Jun 15 '17 at 17:40