原文链接:https://github.com/google/guice/wiki/Bindings
Bindings
Injector的工作就是建立object graph。你请求一个给定类型的实例,然后Guice自行去构建实例、解析依赖,然后将所有绑定在一起。为了了解依赖是怎么被解析的,请使用binding来配置你的injector。
Creating Bindings
继承AbstractModule,重写configure方法,在方法主体中,调用bind()来声明每个binding。这些方法都是类型检查的所以在你使用错误的类型时编译器会告知你哪里出错了。modules创建好之后,把它们作为参数传递给Guice.createInjector()来构造一个injector。
binding包括 linked bindings, instance bindings, @Provides methods, provider bindings, constructor bindings and untargetted bindings.
Linked Binding
Linked Binding映射了类型及其实现。下面的栗子将TransactionLog接口和DatabaseTransactionLog实现类映射到一起。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
}
}
现在,当你调用injector.getInstance(TransactionLog.class)
,或者injector需要一个TransactionLog的时候,它都会使用DatabaseTransactionLog。还可以将类绑定到它的子类上。
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
Linked Binding也可以串联起来:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
在这种情况下,当请求TransactionLog时,Guice会返回一个MySQLDatabaseTransactionLog。
Binding Annotations
有的时候你可能想要对一个类型有多个绑定(binding),比如,你可能同时想要一个PayPal credit processor和一个Google Checkout processor.为了实现这个需求,bindings支持一个可选的binding annotation。这个注解和类型一起定义了一个binding。这个组合叫做一个key。
定义一个binding annotation需要两行额外代码,以及新导入几个包。
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这是一个binding注解,当多个注解指向同一个成员时,Guice会提示这是一个error。 -
@Target({FIELD, PARAMETER, METHOD})
是用户友好的,它可以防止@PayPal
意外地被应用到无法使用的地方。 -
@Retention(RUNTIME)
使得这个注解可以在运行时被获取。
To depend on the annotated binding, apply the annotation to the injected parameter:
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@PayPal CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
最后,我们创建一个使用注释的绑定。它在bind()语句中使用可选的annotatedWith子句:
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
Named
Guice有一个内置的binding annotation@Named
,它将一个字符串作为参数。
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
为了绑定一个特定的名称,使用Names.named()
来生成一个实例传递给annotatedWith
:
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Checkout"))
.to(CheckoutCreditCardProcessor.class);
由于编译器无法检查字符串,我们建议使用@Named
。定义您自己的专用注释可以提供更好的类型安全。
Binding Annotations with Attributes
Guice支持具有属性值的绑定注释(如@Named
)。在极少数情况下,您需要这样的注释(并且不能使用@Provides
方法),我们建议您从Auto / Value项目中使用@AutoAnnotation
,因为正确实现注释是很难的。如果您决定手动创建自定义实现,请务必正确实现Annotation Javadoc中详细说明的equals()
和hashCode()
规范。将此类的实例传递给annotatedWith()
绑定子句。
Instance Bindings
你可以把一个类型绑定到它的一个具体的实例上,这通常仅适用于没有自己的依赖关系的对象,例如值对象:
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
方法。
Provides Methods
当需要创建对象的代码时,请使用@Provides
方法。该方法必须在模块中定义,并且必须具有@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;
}
}
如果@Provides
方法具有像@PayPal
或@Named("Checkout")
的绑定注释,则Guice绑定注释类型(Guice binds the annotated type)。依赖关系可以作为参数传递给方法。在调用该方法之前,injector将对每一个依赖执行绑定。
@Provides @PayPal
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
processor.setApiKey(apiKey);
return processor;
}
Throwing Exceptions
Guice不允许从Providers抛出异常。 @Provides
方法抛出的异常将被包装在一个ProvisionException
中。允许任何类型的异常从@Provides
方法抛出--运行时或检查时--是不好的做法。如果由于某种原因需要抛出异常,可能需要使用ThrowingProviders extension ```@CheckedProvides方法。
Untargeted Bindings
您可以创建绑定而不指定目标。这对于由@ImplementedBy
或@ProvidedBy
注解的实体类和类型是最有用的。Untargeted Binding通知injector有关类型,因此它可以热切地准备依赖关系。(An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly. )Untargeted Binding没有to子句,如下所示:
bind(MyConcreteClass.class);
bind(AnotherConcreteClass.class).in(Singleton.class);
当指定binding annotations时,您仍然必须添加目标绑定,即使它是同一个具体的类。例如:
bind(MyConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(MyConcreteClass.class);
bind(AnotherConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(AnotherConcreteClass.class)
.in(Singleton.class);
Constructor Bindings
在Guice3.0中的新特性。
有的时候需要将一个类型绑定到任意一个构造函数。这种情形在@Inject
注解不能应用于目标构造函数时出现,可能是因为它是个第三方类,或者多个构造函数参与了DI。@Provides
方法为这个问题提供最好的解决方案!通过明确调用目标构造函数,您不需要反思及其相关的陷阱(you don't need reflection and its associated pitfalls.)。但是这种方法有局限性:手动构造的实例不参与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()
绑定的范围是独立的。如果创建多个单例绑定来定位相同的构造函数,则每个绑定都会生成自己的实例。
Built-in Bindings
有一些bindings只有injector自己可以生成,尝试自己去绑定它们是一个错误的决定。
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());
}
The Injector
在框架代码中,有时直到运行时你才知道你需要的类型。在这种罕见的情况下,you should inject the injector. Code that injects the injector does not self-document its dependencies, so this approach should be done sparingly.
Providers
对于Guice知道的每个类型,它也可以给该类型注入一个Provider。详见Injecting Providers.
TypeLiterals
Guice对于注入的所有内容都有完整的类型信息。如果您正在注入参数化类型,则可以注入TypeLiteral
以反映性地告诉您元素类型。
The Stage
Guice supports a stage enum to differentiate between development and production runs.
MembersInjectors
当绑定到Providers或编写扩展名时,您可能希望Guice将依赖项注入到您自己构建的对象中。为此,在MembersInjector
(其中T是对象的类型)中添加依赖关系,然后调用membersInjector.injectMembers(myNewObject)
。(没看懂)
Just-in-time Bindings
Guice自动生成的bindings。当injector需要一个类型的实例时,它需要绑定。模块中的绑定称为显式绑定,并且injector在可用时使用它们。如果需要类型但没有明确的绑定,则injector将尝试创建just-in-time bindings。这些也称为JIT绑定和隐式绑定。
Eligible Constructors
Guice可以通过使用该类型的可注入构造函数为具体类型创建绑定。这可以是非私有的,无参数的构造函数,也可以是带有@Inject
注释的构造函数:
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private final String apiKey;
@Inject
public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
@ImplementedBy
这个注解告诉injector该接口的默认实现类是哪个。@ImplementedBy
注解表现得就像一个Linked Binding。
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
上面的注解和下面的bind()
等效:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);```
如果一个类型同时包含一个```bind()```语句并且具有```@ImplementedBy```注释,则使用```bind()```语句。
#####@ProvidedBy
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
与下面的```toProvider()```等效
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
和```@ImplementedBy```一样,```bind()```优先级最高.