【问题标题】:Creating Guice singleton that's not tied to an Injector创建与 Injector 无关的 Guice 单例
【发布时间】:2025-12-18 15:55:01
【问题描述】:

我们的项目设置如下:

1) 主模块:包含启动 Spark 流服务器的驱动程序。它有自己的 Guice 注射器。

2) 当消息进入时,它会转到另一个模块,该模块创建它自己的 Guice 注入器。

3) 该模块使用其他模块中的类,这些模块本身使用依赖模块。这些模块中的每一个都创建了自己的 Guice 注入器,以便它可以独立工作、单独测试等。

这里有一个问题:现在我们需要一个单例,但是作为@Singleton 创建的对象绑定到一个注入器(而不是一个类加载器),所以当注入器消失时,对象消失了。

问题:

1) 我们的架构不好吗?我们不应该在每个模块中都创建一个注入器吗?

2) 我们如何创建一个即使在注入器消失时仍保留在 ClassLoader 中的 Singleton?

顺便说一句,我们在 Guice 上使用 Netflix Governator。

注意:对据称与此问题重复的问题的答案并未回答如何将在*模块上创建的“单一”注入器传递给子模块。另外,如果子模块本身没有注入器,我们可以为它们编写独立的单元测试吗?

【问题讨论】:

  • Singletons 是 Java 中的反模式,因为它们不像您想象的那样与 JVM 绑定,它们与 ClassLoader 绑定,JVM 实例可以有许多实例并且是一个根本不同的范围。如果您阅读了 Guice 文档,则有一个模块类型的概念 inheritance 允许 sub-Modules 具有父模块的范围。这是你需要使用的。
  • 这个问题让我有些困惑,因为我不知道你的意思是“架构模块”还是“Guice Module
  • Tavin - “模块”是指 Maven 下的模块。每个都有自己的 pom.xml,每个都有自己的 Guice 模块。

标签: java scala dependency-injection guice


【解决方案1】:

问题 2 的一个想法是使用可以在所有不同模块中绑定的提供程序。该提供者将访问存在于 Guice“外部”的单例。

我制作了一个简单的示例,我提供了对静态字段的访问。您可以更改它以使用适合您需要的单例模式的实现。

public class SingletonProvider implements Provider<TheSingleton> {

    private static final TheSingleton instance  = new TheSingleton();
    @Override
    public TheSingleton get() {
        return instance;
    }
}

然后我们将它绑定到我们的不同模块

public class Module1 extends AbstractModule {

    @Override
    protected void configure() {
       bind(TheSingleton.class).toProvider( SingletonProvider.class);
    }
}
public class Module2 extends AbstractModule {

    @Override
    protected void configure() {
       bind(TheSingleton.class).toProvider( SingletonProvider.class);
    }
}

然后我创建了两个返回相同实例的不同注入器

public class Test {
    public static void main(String[] args) {
        Injector injector1 = Guice.createInjector(new Module1());

        TheSingleton instance = injector1.getInstance(TheSingleton.class);
        System.out.println("instance = " + instance);

        Injector injector2 = Guice.createInjector(new Module2());
        TheSingleton instance2 = injector2.getInstance(TheSingleton.class);
        System.out.println("instance2 = " + instance2);


    }
}

【讨论】:

  • 你将 SingletonProvider 保存在哪个模块中?
  • @DilTeam 提供程序未在任何模块中“保留”。每个注入器都会创建自己的提供者实例,并使用它来获取 TheSingleton.class 的实例。您可以将其视为可以提供模块外部访问权限的类。
  • 您是否在调试器中确认它们确实是相同的对象而不是副本?
  • 从两个注入器返回的Singleton 实例是同一个对象。副本是什么意思?
  • System.out 将在 'TheSingleton' 上调用 'toString' 方法对吗?所以输出可能是相同的,但在调试器中你可以看到实际的“内存地址”。您是否确认两者的地址相同?我相信在您的代码中会生成两个不同的对象(具有不同的内存地址),但我不确定。请确认。