singleton - 如何在 Guice 上找到所有实现特定类型的单例?

标签 singleton guice

想象一下我有一个类型 Disposable一些类实现:

class FactoryImpl implements Disposable {}

我可以将此类绑定(bind)为单例:
bind(Factory.class)
.to(FactoryImpl.class)
.in(Singleton.class);

或者作为一个热切的单例:
bind(Factory.class)
.to(FactoryImpl.class)
.asEagerSingleton();

请注意,实现具有类型,而不是接口(interface)。

如何找到 Guice 实际创建并实现类型 Disposable 的所有单例?

请注意,我不想盲目调用get()在提供者中避免创建我不需要的东西(特别是因为我正在销毁单例,所以创建新的可能会导致问题)。

这与 How can I get all singleton instances from a Guice Injector? 等问题相反。只有在界面包含您需要的键时才有效。

[编辑] 这就是我走多远。这段代码正确吗?

首先,我需要我的界面。
public interface Disposable {
    public void dispose();
}

魔法发生在这里:
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);
        }
    }
}

该代码包装了其他模块并确保 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
}

最佳答案

不要重新发明范围

首先,手动“处理”单例 Guice 绑定(bind)有点本末倒置。与其将对象绑定(bind)为单例然后需要定期清理它们,不如使用更合适的 scope (或定义 your own )以便这些对象在预期存在的时间段内具有自然的生命周期,例如对于单个请求或测试。
DisposableListener.dispose() 上的文档证明了这一点。 :

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



这些都不是你应该需要这样的地方:
  • .main() JVM 也将很快终止(并且可能您的 injector 将超出范围),因此在让二进制文件终止之前通常不需要进行任何此类清理。
  • 同样,当一个 ServletContext已经被销毁了,你通常是要终止JVM,所以让它正常退出。
  • 在测试中,您通常应该为每个测试构建隔离注入(inject)器,从而避免任何交叉测试污染。当测试结束注入(inject)器并且它的所有绑定(bind)都超出范围时,应该没有什么需要清理的。

  • 与 Guice 分开管理资源

    当然,您可能正在创建需要清理的对象,例如 AutoCloseable 例如,但这不应该是 Guice 的责任。一般为.getInstance()获取可关闭资源的调用站点应负责清理它。或者,模块可以负责创建和管理这些资源。然后,您在管理资源模块生命周期的 try-with-resources block 中构建注入(inject)器。

    如果这些选项还不够,并且您确实需要更强大的生命周期语义,请使用适当的生命周期框架,例如 Guava's ServiceManager ,而不是将 Guice 纳入其中。

    也就是说,在 Guice 中拥有需要清理的对象本身通常不是一个好主意。考虑改为绑定(bind)允许调用者根据需要打开(和关闭)资源的包装器类型,而不是直接绑定(bind)长期存在的有状态资源对象。

    比 Guice 监听器更喜欢显式绑定(bind)

    如果你真的,真的需要收集绑定(bind)在 Guice 注入(inject)器中的几个不相关的对象,请在 .configure() 上明确地这样做。时间,而不是通过内省(introspection)隐含地。使用 Multibinder 允许您的模块通过将它们绑定(bind)到 Multibinder<Disposable> 来显式声明需要处理的对象。聚合它们的实例。那么你的清理步骤很简单:
    for (Disposable resource : injector.getInstance(new Key<Set<Disposable>>() {}) {
      resource.dispose();
    }
    

    这避免了监听器的“魔法”,它默默地进入并在您之后进行清理,而是允许模块作者确定如何最好地处理它们绑定(bind)的资源,并在必要时选择利用此清理功能。

    关于singleton - 如何在 Guice 上找到所有实现特定类型的单例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35315930/

    相关文章:

    python - super 在单例中的使用

    objective-c - 单例获取核心数据,在 [fetchedResultsController performFetch :&error] 上崩溃

    java - 哪种Java Web Framework最适合Google Guice?

    javascript - 在 JavaScript 中实现单例的最简单/最干净的方法

    objective-c - Facebook 连接类与单例 : Access token issue

    python - 是否有更多方法来定义只有一项的元组?

    guice - 适用于工厂的 Google Guice Autowiring

    java - play 2.5 生产模式无法在应用程序启动时运行的 Singleton 类中使用注入(inject)的 ConfigurationProvider 类对象

    java - 使用 Guice,如何注入(inject)父类(super class)的构造函数参数?

    java - Guice 的 bind() 中的 in()...它有什么作用?