Guice是一个轻量级的DI框架。本文对Guice的基本用法作以介绍。
本文的所有例子基于Guice 3.0
考虑到是入门介绍,本文中并未涉及到AOP相关内容,如有需要还请参考上面链接。
首先有一个需要被实现的接口:
public interface BillingService {
/**
* Attempts to charge the order to the credit card. Both successful and
* failed transactions will be recorded.
*
* @return a receipt of the transaction. If the charge was successful, the
* receipt will be successful. Otherwise, the receipt will contain a
* decline note describing why the charge failed.
*/
Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}
class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
@Inject
RealBillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
@Override
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
...
}
}
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
/*
* This tells Guice that whenever it sees a dependency on a TransactionLog,
* it should satisfy the dependency using a DatabaseTransactionLog.
*/
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
/*
* Similarly, this binding tells Guice that when CreditCardProcessor is used in
* a dependency, that should be satisfied with a PaypalCreditCardProcessor.
*/
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
}
}
好了,现在万事俱备,就让我们一起看看怎么使用Guice进行依赖注入吧:
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.
*/
RealBillingService billingService = injector.getInstance(RealBillingService.class);
...
}
以上就是使用Guice的一个完整的例子,很简单吧,不需要繁琐的配置,只需要定义一个Module来表述接口和实现类,以及父类和子类之间的关联关系的绑定。本文不对比guice和spring,只是单纯介绍Guice的用法。
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
}
就是直接把一种类型的class对象绑定到另外一种类型的class对象,这样,当外界获取TransactionLog时,其实返回的就是一个DatabaseTransactionLog对象。当然,链式绑定也可以串起来,如:
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
这样,当外界请求TransactionLog时,其实返回的就会是一个MySqlDatabaseTransactionLog对象。
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) {
...
}
}
或
public class RealBillingService implements BillingService {
@Inject
@Www
private CreditCardProcessor processor;
...
}
最后,在我们进行链式绑定时,就可以区分一个接口的不同实现了,如:
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
这样,被Annotations
PayPal?修饰的CreditCardProcessor就会被绑定到目标实现类PayPalCreditCardProcessor。如果有其他的实现类,则可把用不同Annotations修饰的CreditCardProcessor绑定到不同的实现类
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Checkout"))
.to(CheckoutCreditCardProcessor.class);
上面介绍的链式绑定是把接口的class对象绑定到实现类的class对象,而实例绑定则可以看作是链式绑定的一种特例,它直接把一个实例对象绑定到它的class对象上。
bind(String.class)
.annotatedWith(Names.named("JDBC URL"))
.toInstance("jdbc:mysql://localhost/pizza");
bind(Integer.class)
.annotatedWith(Names.named("login timeout seconds"))
.toInstance(10);
需要注意的是,实例绑定要求对象不能包含对自己的引用。并且,尽量不要对那种创建实例比较复杂的类使用实例绑定,否则会让应用启动变慢
在使用基于@Provides方法绑定的过程中,如果方法中创建对象的过程很复杂,我们就会考虑,是不是可以把它独立出来,形成一个专门作用的类。Guice提供了一个接口:
public interface Provider {
T get();
}
public class DatabaseTransactionLogProvider implements Provider {
private final Connection connection;
@Inject
public DatabaseTransactionLogProvider(Connection connection) {
this.connection = connection;
}
public TransactionLog get() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setConnection(connection);
return transactionLog;
}
}
其实,个人感觉在configure方法中使用Provider绑定和直接写@Provides方法所实现的功能是没有差别的,不过使用Provider绑定会使代码更清晰。而且当提供对象的方法中也需要有其他类型的依赖注入时,使用Provider绑定会是更好的选择。
bind(MyConcreteClass.class);
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);
无目标绑定,主要是用于与被@ImplementedBy 或者 @ProvidedBy修饰的类型一起用。如果无目标绑定的类型不是被@ImplementedBy 或者 @ProvidedBy修饰的话,该类型一定不能只提供有参数的构造函数,要么不提供构造函数,要么提供的构造函数中必须有无参构造函数。因为guice会默认去调用该类型的无参构造函数。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
try {
bind(TransactionLog.class).toConstructor(
DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
} catch (NoSuchMethodException e) {
addError(e);
}
}
}
这种绑定方式主要用于不方便用注解@Inject修饰目标类型的构造函数的时候。比如说目标类型是第三方提供的类型,或者说目标类型中有多个构造函数,并且可能会在不同情况采用不同的构造函数。
@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());
}
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
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
processor.setApiKey(apiKey);
return processor;
}
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
等价于:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
这里需要注意的是,Guice在创建对象的过程中,无法初始化该类型的内部类(除非内部类有static修饰符),因为内部类会有隐含的对外部类的引用,Guice无法处理。
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private final String apiKey;
@Inject
public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
package guice.test;
import com.google.inject.ImplementedBy;;
@ImplementedBy (SayHello.class)
public interface Talk {
public void sayHello();
}
package guice.test;
public class SayHello implements Talk{
@Override
public void sayHello() {
System.out.println("Say Hello!");
}
}
package guice.test;
import com.google.inject.Inject;
public class FieldDI {
@Inject
private Talk bs;
public Talk getBs() {
return bs;
}
}
package guice.test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class Test {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
FieldDI fdi = injector.getInstance(FieldDI.class);
fdi.getBs().sayHello();
}
}
Exception in thread "main" java.lang.NullPointerException
如果我们用@Inject修饰Talk bs了,但是Talk本身没有被@ImplementedBy修饰的话,会得到如下错误:
Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
1) No implementation for guice.test.Talk was bound.
while locating guice.test.Talk
for field at guice.test.FieldDI.bs(FieldDI.java:5)
while locating guice.test.FieldDI
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:961)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1013)
at guice.test.Test.main(Test.java:24)
另外,属性注入的一种特例是注入provider。如:
public class RealBillingService implements BillingService {
private final Provider processorProvider;
private final Provider transactionLogProvider;
@Inject
public RealBillingService(Provider processorProvider,
Provider transactionLogProvider) {
this.processorProvider = processorProvider;
this.transactionLogProvider = transactionLogProvider;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = processorProvider.get();
TransactionLog transactionLog = transactionLogProvider.get();
/* use the processor and transaction log here */
}
}
public interface Provider {
T get();
}
package guice.test;
import com.google.inject.Inject;
public class FieldDI {
@Inject
public void setBs(Talk bs) {
this.bs = bs;
}
private Talk bs;
public Talk getBs() {
return bs;
}
}
下面我们就以@Singleton举例说明怎么来告诉Guice我们要以@Singleton的方式产生对象:1. 在定义子类型时声明
@Singleton
public class InMemoryTransactionLog implements TransactionLog {
/* everything here should be threadsafe! */
}
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);3.在module的Provides方法里声明
@Provides @Singleton
TransactionLog provideTransactionLog() {
...
}
这里有一点需要注意的是,如果发生下面的情况:
bind(Bar.class).to(Applebees.class).in(Singleton.class);
bind(Grill.class).to(Applebees.class).in(Singleton.class);
bind(Applebees.class).in(Singleton.class);