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
}