想象一下,我有一个Disposable类实现的类型:
class FactoryImpl implements Disposable {}
Run Code Online (Sandbox Code Playgroud)
我可以将这个类绑定为单例:
bind(Factory.class)
.to(FactoryImpl.class)
.in(Singleton.class);
Run Code Online (Sandbox Code Playgroud)
或者作为一个热切的单身人士:
bind(Factory.class)
.to(FactoryImpl.class)
.asEagerSingleton();
Run Code Online (Sandbox Code Playgroud)
请注意,实现具有类型,而不是接口.
我怎样才能找到Guice实际创建的所有单例以及实现该类型的单例Disposable?
请注意,我不想盲目地调用get()提供程序以避免创建我不需要的东西(特别是因为我正在销毁单例,因此创建新的单例可能会导致问题).
这与诸如如何从Guice Injector获取所有单例实例的问题相反?只有工作,然后界面包含您需要的键.
[编辑]这是我有多远.这段代码是否正确?
首先,我需要我的界面.
public interface Disposable {
public void dispose();
}
Run Code Online (Sandbox Code Playgroud)
魔术发生在这里:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
代码包装其他模块并确保DisposableListener早期安装在进样器中.然后它会侦听创建的新实例并在列表中收集它们.
代码可能应该检查这些都是单身,但我不知道该怎么做.
以下是单元测试:
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
}
Run Code Online (Sandbox Code Playgroud)
首先,手动“处理”单例 Guice 绑定有点本末倒置。您不应该将对象绑定为单例,然后需要定期清理它们,而应该使用更合适的范围(或定义您自己的范围),以便这些对象在预期存在的时间内具有自然的生命周期,例如至于单个请求或测试。
以下文档证明了这一点DisposableListener.dispose():
调用它的好地方是在 a 的末尾
main()、在 a 的销毁侦听器中ServletContext或在测试之后
这些都不是您应该需要的地方:
当.main()终止时,JVM 也会很快终止(并且可能injector会超出范围),因此在让二进制文件终止之前通常不需要进行任何此类清理。
类似地,当 aServletContext被销毁时,您通常即将终止 JVM,因此只需让它正常退出即可。
在测试中,您通常应该为每个测试构建隔离的注入器,从而避免任何交叉测试污染。当测试结束时,注入器及其所有绑定都会超出范围,并且应该没有任何内容需要清理。
当然,您可能会创建需要清理的对象,例如实例AutoCloseable,但这不应该是 Guice 的责任。通常,.getInstance()获取可关闭资源的调用站点应该负责清理它。或者,模块可以负责创建和管理这些资源。然后,您在 try-with-resources 块内构造注入器,该块管理资源模块的生命周期。
如果这些选项还不够,并且您确实需要更强大的生命周期语义,请使用适当的生命周期框架,例如Guava 的ServiceManager,而不是将 Guice 纳入其中。
也就是说,在 Guice 中拥有需要清理的对象本身通常不是一个好主意。考虑绑定一个包装器类型,允许调用者根据需要打开(和关闭)资源,而不是直接绑定长期有状态资源对象。
如果您确实非常需要收集绑定在 Guice 注入器中的几个不相关的对象,请及时明确地执行此操作.configure(),而不是通过内省隐式地执行此操作。使用 a允许您的模块通过将它们绑定到聚合所有对象的实例Multibinder来显式声明需要处置哪些对象。Multibinder<Disposable>那么你的清理步骤很简单:
for (Disposable resource : injector.getInstance(new Key<Set<Disposable>>() {}) {
resource.dispose();
}
Run Code Online (Sandbox Code Playgroud)
这避免了侦听器默默进入并在您之后进行清理的“魔力”,而是允许模块作者确定如何最好地处理他们绑定的资源,并在必要时选择利用此清理功能。