Guice基本用法
来源:互联网 发布:3799游戏盒软件 编辑:程序博客网 时间:2024/05/17 02:19
本文适合对依赖注入有相对了解的读者,文章中对于部分名词未作详细解释。对于没有恰当的中文与之对应的英文内容,遂未翻译
Guice简介
Guice 简介,本文中的内容也是参考该文档完成,如有不一致,以该文为准。
快速上手
作为示例,我们使用 BillingService
,它依赖于 CreditCardProcessor
和 TransactionLog
两个接口。接下来我们看看如何使用Guice:
class BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; @Inject BillingService(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 we've 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); }}
在这种情况下,当一个对象依赖于 TransactionLog
,injector将会返回一个 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 { @Inject public RealBillingService(@PayPal CreditCardProcessor processor, TransactionLog transactionLog) { ... }
最后我们还需要创建相应的binding。
bind(CreditCardProcessor.class) .annotatedWith(PayPal.class) .to(PayPalCreditCardProcessor.class);
@Named
也并不是必须创建自己的注解,Guice提供了一个内建的注解@Named
。用法如下:
public class RealBillingService implements BillingService { @Inject public 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 { @Override protected void configure() { ... } @Provides TransactionLog provideTransactionLog() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza"); transactionLog.setThreadPoolSize(30); return transactionLog; }}
如果 @Provides
方法有binding annotation ,比如@Paypal
或者 @Named("Checkout")
,Guice 也是可以的。所有的被依赖对象以参数形式传入该方法即可。
@Provides @PayPal CreditCardProcessor 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 Provider<T> { T get();}
我们的Provider实现类有自己的依赖,所有的依赖是通过被@Inject
修饰的构造函数接收的。
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; }}
绑定
public class BillingModule extends AbstractModule { @Override protected 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; @Inject public 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()
和 @ImplementedBy
,bind()
会生效。
@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。例如:
@Singletonpublic class InMemoryTransactionLog implements TransactionLog { /* everything here should be threadsafe! */}@Provides @SingletonTransactionLog 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; @Inject public 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; @Inject public void setApiKey(@Named("PayPal API key") String apiKey) { this.apiKey = apiKey; }
字段注入
这种情况下,需要使用 @Inject
标注字段。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> { @Inject Connection connection; public TransactionLog get() { return new DatabaseTransactionLog(connection); }}
可选的注入
有时候,我们的依赖项不是必须的,如果系统中存在依赖项则注入,如果不存在,也不强制要求注入。这种情况在方法注入和字段注入中都是适用的。 启用可选注入,只需要使用 @Inejct(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; }
不过,如果混用可选注入和Just-in-time bindings,可能会产生奇怪的接口。例如:
@Inject(optional=true) 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 { @NotOnWeekends public 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源码,但是个人能力有限,如有感兴趣的读者,希望可以一起研究。
- Guice基本用法
- Guice特殊用法
- guice
- Guice
- Guice
- Google Guice之基本类型注入
- Guice 学习(四)基本属性注入(Field Inject)
- 基本用法
- 基本用法
- Guice 链式绑定,常量绑定, 基本类型绑定和Properties绑定
- 初试Guice
- 关于“Guice ”
- GUICE学习心得
- Guice 初识
- Guice笔记
- Guice(二)
- Guice(三)
- Guice框架
- 回调函数
- 【SQL Server】数据操作语言——数据更改功能
- poj-3259-Wormholes
- 构造函数、拷贝构造函数和析构函数
- C++读取、写入配置文件
- Guice基本用法
- php-fallibility
- 1089. Insert or Merge (25)
- js实现瀑布流的两种方法
- nodejs
- tomcat出现Could not load the Tomcat server configuration at 。。异常解决办法
- HTTP协议的简单认识
- Linux多进程
- CodeFoeces732C 枚举