注入(Injections)
本节介绍Guice如何初始化对象
Injections
依赖注入模式实现了业务逻辑与依赖解析的分离。该模式实现了依赖的自行传入,而不是通过直接查找依赖或工厂的方式。将依赖实例设置到相应对象的这个过程称之为依赖注入。下面介绍Guice中提供的几种注入方式:
1. 构造器注入(Constructor Injection)
构造器注入将注入的过程与初始化结合。在构造器上使用@Inject
注解就可以开启构造器注入,构造器会将依赖作为参数引入。通常,构造器会将被注入的参数传递给final成员变量。
public class RealBillingService implements BillingService {
private final CreditCardProcessor processorProvider;
private final TransactionLog transactionLogProvider;
@Inject
public RealBillingService(CreditCardProcessor processorProvider,
TransactionLog transactionLogProvider) {
this.processorProvider = processorProvider;
this.transactionLogProvider = transactionLogProvider;
}
如果没有在构造器上添加@Inject注解,那么Guice会根据类中public的无参构造器进行依赖注入(如果存在的话)。通常而言,使用注解的方式可以更好地文档化参与依赖注入的类型。
构造器注入的方式对于非常有利于单元测试。如果你的类通过唯一的一个构造器来传入依赖,那么这种方式可以确保你不会忘记设置依赖。当需要引入一个新的依赖时,所有的测试用例都会失败。而你只需要解决编译错误就可以确保所有的一切都正确地组装好了。
2. 方法注入(Method Injection)
Guice可以对方法中的参数进行注入,只要方法添加了@Inject
注解。注入器会在执行方法之前检查参数形式的依赖,并执行注入。被注入的方法的参数数量没有限制,并且方法名对于注入没有影响。
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private static final String DEFAULT_API_KEY = "development-use-only";
private String apiKey = DEFAULT_API_KEY;
@Inject
public void setApiKey(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
3. 成员变量注入(Field Injection)
Guice可以对注解了@Inject
的成员变量进行注入。这种注入方式是最精确的,但是最不利于测试的(why?)。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
@Inject Connection connection;
public TransactionLog get() {
return new DatabaseTransactionLog(connection);
}
}
避免在final成员变量上使用注入,因为这会导致弱语义的问题1。(除非在序列化或重建时使用,否则会影响final的语义(修改了final域的值),导致不可预测)。
4. 可选注入
在某些情况下,当依赖存在时才会注入,而不存在时则仍然采用默认值(我们称之为可选的(Optional))。方法注入和成员变量注入可以是可选的,当依赖不可用时Guice默默忽略不去注入。使用可选注入时,只需要添加@Inject(optional=true)
注解。
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private static final String SANDBOX_API_KEY = "development-use-only";
private String apiKey = SANDBOX_API_KEY;
@Inject(optional=true)
public void setApiKey(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
需要注意的是,混用可选注入和JIT绑定可能会产生意想不到的结果。例如,下面的例子中,launchDate
变量会始终被注入即使Date
没有显式的绑定实例。这是因为Date
类有一个公共的无参构造器,符合JIT绑定的条件。
@Inject(optional=true) Date launchDate;
按需注入(On-demand Injection)
方法注入和成员变量注入可以被用于初始化已经存在的实例对象。因此,你可以使用injector.injectMembers
按需将依赖注入现有对象中,而不必在一定要通过injector.getInstance()
获取已经注入完成的对象。
public static void main(String[] args) {
Injector injector = Guice.createInjector(...);
CreditCardProcessor creditCardProcessor = new PayPalCreditCardProcessor();
injector.injectMembers(creditCardProcessor);
静态注入(Static Injections)
当你从静态工厂模式迁移到使用Guice的过程中,可以采用静态注入(static injection)这个工具帮你逐步完成重构。静态注入使得对象可以部分参与依赖注入,它能够在被实例注入前访问被注入的类型而完成静态域的注入。在使用时,只需要使用requestStaticInjection()来指定注入器创建阶段(injector-creation time)需要注入的类。
@Override public void configure() {
requestStaticInjection(ProcessorFactory.class);
...
}
Guice会注入含有@Inject注解的静态成员变量:
class ProcessorFactory {
@Inject static Provider<Processor> processorProvider;
/**
* @deprecated prefer to inject your processor instead.
*/
@Deprecated
public static Processor getInstance() {
return processorProvider.get();
}
}
在实例注入阶段(instance-injection time),静态成员变量不会被注入。通常,这种方法不建议频繁使用,因为它与静态工厂模式存在同样问题:难以测试、暴露依赖关系并且依赖于全局状态。
自动注入(Atomatic Injection)
Guice会对以下对象自动注入依赖:
- 传递给toInstance()语句的绑定实例
- 在bind语句中传递给toProvider()的provider实例。这些对象会在注入器创建时就被注入。If they're needed to satisfy other startup injections, Guice will inject them before they're used.(??)
1. If the underlying field is final, the method throws anIllegalAccessException
, unlesssetAccessible(true)
has succeeded for this field and this field is non-static. Setting a final field in this way is meaningful only during deserialization or reconstruction of instances of classes with blank final fields, before they are made available for access by other parts of a program. Use in any other context may have unpredictable effects, including cases in which other parts of a program continue to use the original value of this field. ↩