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
Guice Singleton Static Injection Pattern
提问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 @Inject
to cxn
but 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:
我的第一个假设是我会简单地添加@Inject
,cxn
但这不起作用,因为我没有从 Guice 获取实例;它只是给了我一个 NPE。在我看来,我有两个获取 Connection 对象的选项:
- Expose a
getConnection()
method in MyApplication essentially following the Service Locator Pattern - Add
requestStaticInjection(MyConfiguration)
toMyModule
getConnection()
在 MyApplication 中公开一个方法,基本上遵循服务定位器模式- 添加
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.getInstance
each 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 MyApplication
needs a MyConfiguration
object, it should just accept a MyConfiguration
object 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 new
manually -- 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 MyConfiguration
into lots of tiny pieces, so that instead of doing myConfiguration.getConfig("x")
you would do @Inject @Configuration("x") String
or something like that. Alternatively, you could make MyConfiguration
itself 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 MyApplication
needs is passed in its constructor, it's impossible to ever have a MyApplication
object 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 Injector
exactly 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 Injector
anywhere. I tend to build applications using Guava's ServiceManager
abstraction (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();
}