翻译自官方文档,能力有限,如有缺漏,还望指正。
1.绑定(Bindings)
注射器(injector)的主要作用是搜集对象依赖图。当我们需要创建一个类型的对象时,他就会找出绑定类,然后会解决依赖的关系,并将所有的关系联系到一起。为了帮助解决依赖之间的关系,我们需要给injector指定绑定关系。
1.1 创建绑定关系
创建bind,我们只需要实现AbstractModule
接口,然后重写configure
方法即可。在这个方法体中,我们通过调用bind
方法就可以实现绑定了。这些方法会进行类型检查,所以当我们参数有问题的时候,编译就会报错。我们创建了自定义的module之后就可以将其作为参数传递给Guice.creatInjector()
来创建一个injector了。
绑定方式也有很多种:
- linked bindings
- instance bindings
- @Provides methods
- provider bindings
- constructor bindings
- untargetted bindings
1.2 其他的绑定
除了普通的绑定之外,我们还可以指定内嵌(built-in)绑定的injector。当有一个创建请求到达,但是为找到依赖项时,它就会尝试创建一个即时绑定(just-in-time)。
2. Linked bindings
Linked Bindings 会将一种类型与它的实现绑定。下面的例子,就是将TransactionLog
接口绑定到了它的实现类 DatabaseTransactionLog
上了:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
}
}
如此,当我们调用injector.getInstance(TransactionLog.class)
时,或者有类依赖于TransactionLog
时,它就会创建DatabaseTransactionLog
。它就是把一种类型绑定到了它的子类或者实现类上。我们甚至可以把这个实现类DatabaseTransactionLog
绑定到它的子类上:
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
Link bindings 还可以传递绑定关系,就是连在一起,如下:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
在上面的代码中,当我们需要一个TransactionLog
的时候,injector
就会返回一个MySqlDatabaseTransactionLog
对象。
3. Bindings Annotations
通常情况下,我们的一种类型可能绑定了多个实现或者子类。例如,当你需要使用信用卡消费时,你用的可能是PayPal的卡,也可能是Google的卡。在这种情况下你就需要使用不同的processor 实现类了。为了做到这一点,guice支持注解绑定。注解加上类型组合在一起就是唯一的标识符了。这部分称之为key
。
自定义注解
定义一个绑定注解,需要在代码中导入相应的库,当然现在的IDE都有自动导入功能了,这个应该没什么问题。
package example.pizza;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface PayPal {}
我们没必要去关心这些元注解,但是如果你真的想要知道的话,可以看下面:
-
@BindingAnnotation:
这个注解是告诉Guice,这个是个绑定注解。这样,在后面如果这个类型没有对应多个绑定注解,那么Guice就会报错; -
@Target({FIELD, PARAMETER, METHOD})
: 这个注解可以防止@PayPal
在没用的地方被意外引用。 -
@Retention(RUNTIME)
:让这个注解只在运行时有效。
通过绑定注解,接下来我们就可以通过注解来注入参数了:
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@PayPal CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
最后我们通过注解把类型和对应的实现类绑定起来。在bind()
方法后调用annotateWith(PayPal.class)
即可,如下:
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
通过自定义注解实现绑定的过程主要有三步:
- 定义注解
- 绑定注解
- 注解注入
@Name注解
Guice也提供了一个内嵌的注解@Name
,这个注解通过一个字段指定绑定的实现类:
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
在需要注入的地方我们直接通过Names.name就可以指定实现类了:
bind(CreditCardProsssscessor.class)
.annotatedWith(Names.named("Checkout"))
.to(CheckoutCreditCardProcessor.class);
因为编译器无法对字符串做类型检查,所以使用这个Name注解可能不太安全。最好还是通过定义自定义注解来绑定对象,这样可以保证类型安全。
4. Instance Bindings
在Guice中我们可以直接将一种类型绑定到它的对象实例上。这个通常只用于那些自身没有依赖项的对象,例如某些比较特殊的值对象:
bind(String.class)
.annotatedWith(Names.named("JDBC URL"))
.toInstance("jdbc:mysql://localhost/pizza");
bind(Integer.class)
.annotatedWith(Names.named("login timeout seconds"))
.toInstance(10);
避免使用.toInstance
创建复杂对象,这会导致我们的程序在启动时比较耗性能。可以使用@Provides
方法代替这种方式。
5. @Provides Methods
当需要创建通过代码创建对象时,我们可以使用一个@Provides
方法。这个方法必须在Module
中绑定,而且必须使用@Provides
注解。这个方法返回一个限定类型的对象。当注射器(Injector)需要创建一个这种类型的对象时,它就会调用这个方法。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
...
}
@Provides
TransactionLog provideTransactionLog() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
transactionLog.setThreadPoolSize(30);
return transactionLog;
}
}
我们也可以给这个方法添加自定义注解,或者@Named
注解,这样我们就可以像使用注解绑定那样来使用这个方法创建对象了。我们也可以在这个方法中使用值对象绑定:
@Provides @PayPal
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
processor.setApiKey(apiKey);
return processor;
}
Provides方法不允许抛出异常,如果需要抛出异常可以考虑使用ThrowingProviders extension ——@CheckedProvides
方法 。
6. Provider Bindings
当我们需要使用很多的@Provides
方法,而且它们的实现都比较复杂的时候,我们可能就需要考虑把它们各自迁移到一个特定的类中了。provider类需要实现Guice的Provider
接口,然后实现一个简单的get方法:
public interface Provider<T> {
T get();
}
我们自己实现的provider类可以通过构造器上使用@Inject
注解注入自己的依赖。下面的例子就是通过实现Provider
接口,定义了一个返回复杂类型的方法:
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
private final Connection connection;
@Inject
public DatabaseTransactionLogProvider(Connection connection) {
this.connection = connection;
}
public TransactionLog get() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setConnection(connection);
return transactionLog;
}
}
最后我们通过.toProvider
来绑定provider:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
}
为了保证代码质量和功能,当我们的provide方法比较复杂的时候,最后保证测试通过。
跟provide方法一样,Provider中的方法也不允许抛出异常,如果需要抛出异常可以考虑使用ThrowingProviders extension ——@CheckedProvides
方法 。
7. Untargeted Bindings
有些时候我可能没法把依赖绑定到一些具体的目标上,这个时候@ImplementedBy
或者@ProvidedBy
类型注解就比较有用了。非目标绑定会将绑定类型信的相关信息通知给injector,这样的绑定不会使用to
方法,具体的使用方法如下:
bind(MyConcreteClass.class);
//通知injector这是一个单例对象
bind(AnotherConcreteClass.class).in(Singleton.class);
然后在使用注解绑定的时候,我们再将其绑定到具体的实现类上:
bind(MyConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(MyConcreteClass.class);
bind(AnotherConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(AnotherConcreteClass.class)
.in(Singleton.class);
8. Constructor Bindings
Guice 3.0版本新特性
有些时候我们需要把一种类型绑定到任意的构造器上。这个通常出现在@Inject
注解无法应用于目标构造器的情况:要么因为它是一个第三方类,要么是因为它有多个构造器参与了依赖注入。@Provides
方法提供了最佳的解决方案。通过显式调用我们自己的对象构造方法,我们就可以得到相应的类型了。但是这种方法有个缺陷:手动的构造的对象无法参与到AOP中。
为了解决这个问题,Guice提供了toConstructor()
绑定的方式。这种方式需要我们通过反射选择一个类的构造器。而且,当构造器不存在的时候我们需要自己解决异常:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
try {
bind(TransactionLog.class).toConstructor(
DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
} catch (NoSuchMethodException e) {
addError(e);
}
}
}
在这个例子中,DatabaseTransactionLog
类应该得有一个只接受一个DatabaseConnection
类型参数的构造器。这个构造器不需要@Inject
注解。Guice会调用它的构造器来适配对应的绑定。
每个toConstructor()
绑定都是独立的。如果我们将一种单例类型绑定在多个构造器上,那么每个构造器都是创建出它们对应的对象。
9. Bult-in Bindings
除了显示绑定和即时绑定之外,Guice也内嵌了一些其他的绑定。
Loggers
Guice专门为java.util.logging.Logger
内嵌了一个绑定,这样可以节省一些我们的开发时间。这个绑定会自动把logger的名字设置成logger被注入的类名。
@Singleton
public class ConsoleTransactionLog implements TransactionLog {
private final Logger logger;
@Inject
public ConsoleTransactionLog(Logger logger) {
this.logger = logger;
}
public void logConnectException(UnreachableException e) {
/* the message is logged to the "ConsoleTransacitonLog" logger */
logger.warning("Connect exception failed, " + e.getMessage());
}
Injector
在我们的框架代码中,有的时候直到代码运行时我们才知道哪一种类型是我们需要的。在这种特殊情况下,我们就需要注入injector了。需要注意的是,注入injector的代码不会自行记录它的依赖关系,所以我们需要谨慎地使用这种方式。
Providers
Guice可以为所有感知到类型注入一个Provider。在Injectiong Providers中可以了解实现的细节。
TypeLiterals
Guice对其注入的所有类型都具有完整的类型信息。如果我们需要参数化这些类型,我们可以注入一个TypeLiteral<T>
。这样我们就可以通过一些反射机制获取这些元素的类型了。
The Stage
Guice支持不同的阶段枚举类型,这样就可以区分开发环境和生产环境了。
MembersInjectors
当我们绑定providers或者扩展一些extensions的时候,我们可能需要Guice为我们写的对象注入依赖。想要做到这一点,只要添加一个MemberInjector<T>
(其中T是我们自己的对象类型),然后通过调用membersInjector.injectMembers(myNewObject)
就可以了。
10. Just-in-time Bindings
当injector需要一种类型的对象实例时,它就需要指定绑定。在modules中的绑定,我们称之为显式绑定,而我们想用的时候就可以通过这些modules创建injector。当我们需要的一个类型没有通过显示绑定到实现类上时,injector就会尝试创建一个即使绑定(Just-In_Time buding),这也被称之为JIT绑定或隐式绑定。
Eligible Constructor
Guice可以通过类型可注入的构造器为具体的类型穿件绑定。这个要么是一个无私有字段的空参构造器,要么是一个有@Inject
注解的构造器:
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private final String apiKey;
@Inject
public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
一些内嵌的类,除非它们static修饰的,否则Guice不会构造这些类。内部类对其无法注入的封闭类有隐式引用。
@ImplementedBy
这个注解可以告诉injector,接口默认的实现类是哪个。@ImplementedBy
就跟Link binding
差不多,当我们没有通过其他方式指定和注入的时候,默认注入的就是这个注解指定的实现类:
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
上面这个实现通过Link binding
操作就是:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
如果一个类型同时使用了bind()
方法和@ImplementedBy
注解。那么注解指定的默认实现类会被bind()
方法指定的实现类覆盖。我们在使用@ImplementedBy
的时候还是需要小心一点的;它为接口添加了一个运行时的依赖绑定。
@ProvidedBy
@ProvidedBy
可以告诉injector生产实例对象的Provider
:
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
这个跟.toProvider()
效果是一样的:
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
跟@ImplementedBy
一样,当我们同时使用注解和方法绑定时,注解会被方法覆盖掉。
网友评论