Java Guice 单例静态注入模式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28734581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 06:42:28  来源:igfitidea点击:

Guice Singleton Static Injection Pattern

javadependency-injectionguice

提问by lamarvannoy

I'm new to Google Guice and understand Dependency Injection conceptually, but am running into issues trying to incorporate it into my application. My specific question is around Singleton objects. Here's an example:

我是 Google Guice 的新手,从概念上理解依赖注入,但在尝试将其合并到我的应用程序中时遇到了问题。我的具体问题是围绕单例对象。下面是一个例子:

First, my Module class, which binds a heavy Singleton Connection interface to its implementation.

首先是我的 Module 类,它将一个沉重的 Singleton Connection 接口绑定到它的实现。

public class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Connection.class).to(MyConnection.class).asEagerSingleton();
    }
}

Now, in my main method, I instantiate my application server and inject the Connection:

现在,在我的主要方法中,我实例化我的应用程序服务器并注入连接:

public class MyApplication {
    @Inject
    public MyApplication(Connection cxn) {

    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new MyModule());
        MyApplication app = injector.getInstance(MyApplication.class);
        // Start application, add ShutdownHook, etc...
    }
}

Everything good so far... Now, I have some DAO classes that leverage my Connection object, but are retrieved with static methods like so:

到目前为止一切都很好......现在,我有一些 DAO 类利用了我的 Connection 对象,但使用静态方法检索,如下所示:

public class MyConfiguration {
    private Config conf;
    private Connection cxn; // Would like to have this injected

    private MyConfiguration(Config conf) {
        this.conf = conf;
    }

    public static MyConfiguration getConfig(String name) {
        return new MyConfiguration(cxn.getConfig(name));
    }
}

My first assumption was that I would simply add @Injectto cxnbut this doesn't work because I am not getting the instance from Guice; it just gives me a NPE. The way I see it, I have 2 options for getting the Connection object:

我的第一个假设是我会简单地添加@Injectcxn但这不起作用,因为我没有从 Guice 获取实例;它只是给了我一个 NPE。在我看来,我有两个获取 Connection 对象的选项:

  1. Expose a getConnection()method in MyApplication essentially following the Service Locator Pattern
  2. Add requestStaticInjection(MyConfiguration)to MyModule
  1. getConnection()在 MyApplication 中公开一个方法,基本上遵循服务定位器模式
  2. 添加requestStaticInjection(MyConfiguration)MyModule

I opted for #2, however the docs say:

我选择了#2,但是文档说

This API is not recommended for general use

不建议将此 API 用于一般用途

What is best practice for providing my Singleton to the classes that need it without having to go through Injector.getInstanceeach time? What am I missing?

将我的单例提供给需要它的类而不必Injector.getInstance每次都经过的最佳实践是什么?我错过了什么?

采纳答案by Daniel Pryden

You're thinking about dependency injection incorrectly. Dependency Injection and Service Locator are mirror-images of each other: with a service locator, you ask itfor an object. With dependency injection, you don't go looking for dependencies, they're just handed to you.

您正在错误地考虑依赖注入。依赖注入和服务定位器是彼此的镜像:使用服务定位器,你向它请求一个对象。使用依赖注入,您不必去寻找依赖项,它们只是交给您

Basically, "it's turtles all the way down"! Everydependency your class has should be injected. If MyApplicationneeds a MyConfigurationobject, it should just accept a MyConfigurationobject as a constructor parameter, and not worry about how it was constructed.

基本上,“一路下来都是乌龟”!你的类所拥有的每个依赖项都应该被注入。如果MyApplication需要一个MyConfiguration对象,它应该只接受一个MyConfiguration对象作为构造函数参数,而不必担心它是如何构造的。

Now, this isn't to say that you can never use newmanually -- but you should reserve that for value-type objects that don't have external dependencies. (And in those cases, I'd argue that you're often better off with a static factory method than a public constructor anyway, but that's beside the point.)

现在,这并不是说你永远不能new手动使用——而是你应该为没有外部依赖的值类型对象保留它。(在这些情况下,我认为静态工厂方法通常比公共构造函数更好,但这不是重点。)

Now there are a couple of ways of doing this. One way is to shard MyConfigurationinto lots of tiny pieces, so that instead of doing myConfiguration.getConfig("x")you would do @Inject @Configuration("x") Stringor something like that. Alternatively, you could make MyConfigurationitself injectable, and then provide accessor methods on it for the pieces. The right answer depends somewhat on the kind of data you're trying to model -- make the dependencies too fine-grained and your bindings may become hard to maintain (although there are ways to make that better); make the dependencies too coarse and you make it harder to test (for example: which is easier, providing just the "x" config that the class you're testing needs, or building the whole application's config?).

现在有几种方法可以做到这一点。一种方法是将其MyConfiguration分成许多小块,这样myConfiguration.getConfig("x")您就不会做@Inject @Configuration("x") String或类似的事情。或者,您可以使MyConfiguration自身可注入,然后在其上为这些部分提供访问器方法。正确的答案在某种程度上取决于您尝试建模的数据类型——使依赖关系过于细粒度,并且您的绑定可能变得难以维护(尽管有一些方法可以使之变得更好);使依赖关系过于粗糙,使测试变得更加困难(例如:哪个更容易,仅提供您正在测试的类需要的“x”配置,还是构建整个应用程序的配置?)。

You can even do both:

您甚至可以同时执行以下两种操作:

/** Annotates a configuration value. */
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
  String value();
}

/** Installs bindings for {@link MyConfiguration}. */
final class MyConfigurationModule extends AbstractModule {
  @Override protected void configure() {}

  @Provides
  @Singleton
  MyConfiguration provideMyConfiguration() {
    // read MyConfiguration from disk or somewhere
  }

  @Provides
  @Config("x")
  String provideX(MyConfiguration config) {
    return config.getConfig("x").getName();
  }
}

// elsewhere:

/** The main application. */
final class MyApplication {
  private final String xConfig;

  @Inject MyApplication(@Config("x") String xConfig) {
    this.xConfig = xConfig;
  }

  // ...
}

You can take a similar approach in unit tests:

您可以在单元测试中采用类似的方法:

/** Tests for {@link MyApplication}. */
@RunWith(JUnit4.class)
public final class MyApplicationTest {
  // Note that we don't need to construct a full MyConfiguration object here
  // since we're providing our own binding, not using MyConfigurationModule.
  // Instead, we just bind the pieces that we need for this test.
  @Bind @Config("x") String xConfig = "x-configuration-for-test";

  @Before public void setUp() {
    // See https://github.com/google/guice/wiki/BoundFields
    Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
  }

  @Inject MyApplication app;

  @Test public void testMyApp() {
    // test app here
  }
}

Dependency injection also encourages another best practice which I highly recommend, which is to design your type system such that invalid states are not representable (to the maximal degree possible). If all the configuration MyApplicationneeds is passed in its constructor, it's impossible to ever have a MyApplicationobject that doesn't have a valid configuration. This allows you to "front-load" your class invariants, which makes it much easier to reason about the behavior of your objects.

依赖注入还鼓励我强烈推荐的另一种最佳实践,即设计类型系统以使无效状态不可表示(尽可能地)。如果所有配置MyApplication需求都在其构造函数中传递,则不可能有一个MyApplication没有有效配置的对象。这允许您“预先加载”您的类不变量,从而更容易推理您的对象的行为。

Finally, a note about Injector.getInstance(). Ideally you use Injectorexactly once in your program: immediately after it is constructed. That is, you should be able to do Guice.createInjector(...).getInstance(MyApplication.class).start()and never store a reference to the Injectoranywhere. I tend to build applications using Guava's ServiceManagerabstraction (see also this question), so the only thing I ever need to do is:

最后,关于Injector.getInstance(). 理想情况下,您Injector在程序中只使用一次:在它被构造之后立即使用。也就是说,您应该能够Guice.createInjector(...).getInstance(MyApplication.class).start()并且永远不会存储对Injector任何地方的引用。我倾向于使用 Guava 的ServiceManager抽象来构建应用程序(另见这个问题),所以我唯一需要做的就是:

public static void main(String[] args) throws Exception {
  Injector injector = Guice.createInjector(...);
  ServiceManager manager = injector.getInstance(ServiceManager.class);
  manager.startAsync().awaitHealthy();
}