电子商务与网站建设策划书,达州市住房和城乡建设局网站,北京做网站建设价格,手机企业网站开发本文适合对依赖注入有相对了解的读者#xff0c;文章中对于部分名词未作详细解释。对于没有恰当的中文与之对应的英文内容#xff0c;遂未翻译 Guice简介
Guice 简介#xff0c;本文中的内容也是参考该文档完成#xff0c;如有不一致#xff0c;以该文为准。
快速上手
… 本文适合对依赖注入有相对了解的读者文章中对于部分名词未作详细解释。对于没有恰当的中文与之对应的英文内容遂未翻译 Guice简介
Guice 简介本文中的内容也是参考该文档完成如有不一致以该文为准。
快速上手
作为示例我们使用 BillingService它依赖于 CreditCardProcessor 和 TransactionLog 两个接口。接下来我们看看如何使用Guice class BillingService {private final CreditCardProcessor processor;private final TransactionLog transactionLog;InjectBillingService(CreditCardProcessor processor, TransactionLog transactionLog) {this.processor processor;this.transactionLog transactionLog;}public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {...}
}我们将会把 PaypalCreditCardProcessor 和 DatabaseTransactionLog 注入BillingService。 Guice 使用 bindings 将类型和实现对应起来。module 是特定的 bindings 的集合。 public class BillingModule extends AbstractModule {Override protected void configure() {/***这是告诉Guice当遇到一个对象依赖于TransactionLog时*将DatabaseTransactionLog注入*/bind(TransactionLog.class).to(DatabaseTransactionLog.class);/**同上*/bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);}
}现在可以编写main方法了 public static void main(String[] args) {/** Guice.createInjector() takes your Modules, and returns a new Injector* instance. Most applications will call this method exactly once, in their* main() method.*/Injector injector Guice.createInjector(new BillingModule());/** Now that weve got the injector, we can build objects.*/BillingService billingService injector.getInstance(BillingService.class);...}大功告成 Bindings
injector 的职责是确定系统中需要构造哪些对象解析依赖装配对象(将被依赖的对象注入依赖的对象)。那么如何指定依赖的解析方式答案是使用 bindings 配置你的 injector
创建bindings
继承 AbstractModule 重写 configure 方法。在该方法中调用 bind() 便创建了一个binding。完成module之后调用 Guice.createInjector(),将module作为参数传入便可获得一个injector对象。
Linked Bindings
Linked bindings 将一个实现绑定到一个类型。 下面例子将 DatabaseTransactionLog 绑定到接口 TransactionLog public class BillingModule extends AbstractModule {Override protected void configure() {bind(TransactionLog.class).to(DatabaseTransactionLog.class);}
}绑定之后当你调用 injector.getInstance(TransactionLog.class) 或当injector遇到一个对象依赖与 TransactionLog它便会使用 DatabaseTransactionLog。链接可以建立于接口和其实现类或者子类和父类之间。Linked bindings 可以链式使用。 public class BillingModule extends AbstractModule {Override protected void configure() {bind(TransactionLog.class).to(DatabaseTransactionLog.class);bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);}
}在这种情况下当一个对象依赖于 TransactionLoginjector将会返回一个 MySqlDatabaseTransactionLog.
BindingAnnotations
Binding Annotations
有时候你想要给特定的类型绑定多个实现。例如你想给 CreditCardProcessor同时绑定 PaypalCreditCardProcessor 和 CheckoutCreditCardProcessor 两个实现. Guice 通过binding annotation满足上述需求。注解和类型共同确定了一个唯一的binding。 在这儿注解和类型构成了Key。
首先我们定义一个注解 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 {}然后使用我们定义的注解标示需要注入的类型。 public class RealBillingService implements BillingService {Injectpublic RealBillingService(PayPal CreditCardProcessor processor,TransactionLog transactionLog) {...}最后我们还需要创建相应的binding。
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);
Named
也并不是必须创建自己的注解Guice提供了一个内建的注解Named。用法如下:
public class RealBillingService implements BillingService {Injectpublic RealBillingService(Named(Checkout) CreditCardProcessor processor,TransactionLog transactionLog) {...}bind(CreditCardProcessor.class).annotatedWith(Names.named(Checkout)).to(CheckoutCreditCardProcessor.class);
因为编译器不能对String类型进行检查所以不建议使用Named
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 方法。该方法只能定义在module中。并且需要使用 Provides 修饰。他的返回值是一个对象。当Injector需要某个类型的实例时便会调用相应的Provides 方法。
public class BillingModule extends AbstractModule {Overrideprotected void configure() {...}ProvidesTransactionLog provideTransactionLog() {DatabaseTransactionLog transactionLog new DatabaseTransactionLog();transactionLog.setJdbcUrl(jdbc:mysql://localhost/pizza);transactionLog.setThreadPoolSize(30);return transactionLog;}
}
如果 Provides 方法有binding annotation 比如Paypal 或者 Named(Checkout),Guice 也是可以的。所有的被依赖对象以参数形式传入该方法即可。
Provides PayPalCreditCardProcessor providePayPalCreditCardProcessor(Named(PayPal API key) String apiKey) {PayPalCreditCardProcessor processor new PayPalCreditCardProcessor();processor.setApiKey(apiKey);return processor;}
需要注意的是 Guice不允许 Provides 方法抛出异常。
Provider Bindings
当 Provides 方法比较复杂时你也许会考虑将该方法转移到一个单独的类中。Provider类继承Guice的 Provider 接口。 Provider 接口定义如下
public interface ProviderT {T get();
}
我们的Provider实现类有自己的依赖所有的依赖是通过被Inject 修饰的构造函数接收的。
public class DatabaseTransactionLogProvider implements ProviderTransactionLog {private final Connection connection;Injectpublic DatabaseTransactionLogProvider(Connection connection) {this.connection connection;}public TransactionLog get() {DatabaseTransactionLog transactionLog new DatabaseTransactionLog();transactionLog.setConnection(connection);return transactionLog;}
}
绑定
public class BillingModule extends AbstractModule {Overrideprotected void configure() {bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);}
Untargeted Bindings
一些情况下你需要创建bindings但是却不能指定具体的实现。这个对于被ImplementedBy 或者 ProvidedBy 修饰的具体类或者类型特别有用。An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly. Untargetted bindings have no to clause, like so:
bind(MyConcreteClass.class);
bind(AnotherConcreteClass.class).in(Singleton.class);
当指定binding annotation时必须加上绑定的目标。
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
有时候 我们需要绑定一个类型到任意的的构造函数。以下情况会有这种需求Inject 注解无法被应用到目标构造函数。或者该类型是一个第三方类。或者该类型中有多个构造函数参与依赖注入。 针对这个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);}}
}
JustInTimeBindings
Just-in-time Bindings
当Injector需要一个类型的实例的时候它需要一个binding。 如果这个binding在一个module中被创建那么这个binding是显式binding此时injector在每次需要该类型实例时都使用该实例。但是如果Injector需要一个类型的实例但是这个类型并没有对应的显式binding。此时injector会尝试创建一个Just-in-time binding。也叫JIT binding或者隐式binding。
合格的构造函数
Guice会使用具体类型的可注入构造函数创建binding。可注入构造函数需要是非private无参数的或者该构造函数被 Inject 修饰。
public class PayPalCreditCardProcessor implements CreditCardProcessor {private final String apiKey;Injectpublic PayPalCreditCardProcessor(Named(PayPal API key) String apiKey) {this.apiKey apiKey;}ImplementedBy
告诉injector该类型的默认实现类。
ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {ChargeResult charge(String amount, CreditCard creditCard)throws UnreachableException;
}
上述代码和一下代码是等价的 bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);如果某个类型同时使用 bind() 和 ImplementedBybind() 会生效。
ProvidedBy
告诉Injector产生该类型的Provider类
ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {void logConnectException(UnreachableException e);void logChargeResult(ChargeResult result);
}
上述代码等价于
bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
如果同时使用 ProvidedBy 和 bind() , bind() 会生效。
Scopes
默认情况下Guice 每次都会返回一个新创建的对象。不过这也是可以配置的以便于我们重用实例。
配置Scopes
Guice 使用注解标示Scope。例如
Singleton
public class InMemoryTransactionLog implements TransactionLog {/* everything here should be threadsafe! */
}Provides Singleton
TransactionLog provideTransactionLog() {...
}
上例中Singleton 标示该类型只能有一个实例。并且是线程安全的。
Scope也可以通过代码来配置
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
如果某个类型已经被注解标注了scope同时还使用bind() 方法配置了scope那么后者生效。如果一个类型已经被注解配置了scope而你不想那样你可以使用 bind() 覆盖。
预加载的单例
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();
Injections
构造函数注入
这种情况下需要用 Inject 标注构造函数。构造函数同时需要将所依赖的类型作为其参数。通常情况下都是将传入的参数复制给类的final成员。
public class RealBillingService implements BillingService {private final CreditCardProcessor processorProvider;private final TransactionLog transactionLogProvider;Injectpublic RealBillingService(CreditCardProcessor processorProvider,TransactionLog transactionLogProvider) {this.processorProvider processorProvider;this.transactionLogProvider transactionLogProvider;}
如果没有 Inject 标注的构造函数Guice 会使用共有的午餐构造函数如果存在。
方法注入
这种情况下需要使用Inject 标注方法该方法的参数为需要注入的类型。
public class PayPalCreditCardProcessor implements CreditCardProcessor {private static final String DEFAULT_API_KEY development-use-only;private String apiKey DEFAULT_API_KEY;Injectpublic void setApiKey(Named(PayPal API key) String apiKey) {this.apiKey apiKey;}
字段注入
这种情况下需要使用 Inject 标注字段。
public class DatabaseTransactionLogProvider implements ProviderTransactionLog {Inject Connection connection;public TransactionLog get() {return new DatabaseTransactionLog(connection);}
}
可选的注入
有时候我们的依赖项不是必须的如果系统中存在依赖项则注入如果不存在也不强制要求注入。这种情况在方法注入和字段注入中都是适用的。 启用可选注入只需要使用 Inejctoptionaltrue 标注字段或方法即可。
public class PayPalCreditCardProcessor implements CreditCardProcessor {private static final String SANDBOX_API_KEY development-use-only;private String apiKey SANDBOX_API_KEY;Inject(optionaltrue)public void setApiKey(Named(PayPal API key) String apiKey) {this.apiKey apiKey;}
不过如果混用可选注入和Just-in-time bindings可能会产生奇怪的接口。例如
Inject(optionaltrue) Date launchDate;
上面代码中的date总是会被成功注入即使没有为他创建对应的显示binding因为它有无参构造函数Guice会为他创建Just-in-time bindings。
On-demand注入
方法和字段注入可以用来初始化一个现存的实例。我们可以使用Injector.injectMember()API:
public static void main(String[] args) {Injector injector Guice.createInjector(...);CreditCardProcessor creditCardProcessor new PayPalCreditCardProcessor();injector.injectMembers(creditCardProcessor);
AOP
Guice 支持方法拦截器。这里直接看一个实例 假如我们需要禁止在周末调用特定方法
为了标注我们在周末禁止调用的方法我们定义一个注解类型
Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD)
interface NotOnWeekends {}
然后使用该注解标注我们方法
public class RealBillingService implements BillingService {NotOnWeekendspublic Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {...}
}
现在我们定义我们的拦截器我们的拦截器需要实现org.aopalliance.intercept.MethodInterceptor 接口。
public class WeekendBlocker implements MethodInterceptor {public Object invoke(MethodInvocation invocation) throws Throwable {Calendar today new GregorianCalendar();if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith(S)) {throw new IllegalStateException(invocation.getMethod().getName() not allowed on weekends!);}return invocation.proceed();}
}
然后配置我们的拦截器
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {/**在这里我们匹配所有的类但是只匹配类中有NotOnWeekends的方法*/bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker());}
}
所有工作就完成了。
注入拦截器
如果需要注入拦截器使用 requestInjection API
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {WeekendBlocker weekendBlocker new WeekendBlocker();requestInjection(weekendBlocker);bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), weekendBlocker);}
}
另外一种方式是使用 Binder.getProvider将依赖的内容传入拦截器的构造函数。
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {bindInterceptor(any(),annotatedWith(NotOnWeekends.class),new WeekendBlocker(getProvider(Calendar.class)));}
}
END
本人有意进一步阅读Guice源码但是个人能力有限如有感兴趣的读者希望可以一起研究。