感谢本文的原作者让我对Guice可以初步的了解一点,出于项目的考虑,有时候Spring并不能作为一个通用的框架,尤其需要一个轻量级的纯DI框架时,个人认为Guice是个不错的选择。在文章最后还附了一段关于Spring和Guice的比较,有兴趣的童鞋可以看看
本文出处:http://blog.csdn.net/derekjiang/article/details/7231490
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) { } }
现在接口有了,实现类也有了,接下来就是如何将接口和实现类关联的问题了,在Guice中需要定义Module来进行关联
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绑定到不同的实现类
使用@Named的方式和上面自己写Annotation的方式很类似,只不过做了相应的简化,不再需要自己去写Annotation了。
public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { } }直接使用@Named修饰要注入的目标,并起个名字,下面就可以把用这个名字的注解修饰的接口绑定到目标实现类了
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方法中,使用toProvider方法来把一种类型绑定到具体的Provider类。当需要相应类型的对象时,Provider类就会调用其get方法获取所需的对象。
其实,个人感觉在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会默认去调用该类型的无参构造函数。
在configure方法中,将一种类型绑定到另外一种类型的过程中,指定目标类型用那种构造函数生成对象。
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修饰目标类型的构造函数的时候。比如说目标类型是第三方提供的类型,或者说目标类型中有多个构造函数,并且可能会在不同情况采用不同的构造函数。
Built-in绑定指的是不用程序员去指定,Guice会自动去做的绑定。目前,Guice所支持的Built-in绑定只有对java.util.logging.Logger的绑定。个人感觉,所谓的Built-in绑定,只是在比较普遍的东西上为大家带来方便的一种做法。
@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()); } }
当你只是需要在需要的时候,产生相应类型的对象的话,@Provides Methods是个不错的选择。方法返回的类型就是要绑定的类型。这样当需要创建一个该类型的对象时,该provide方法会被调用,从而得到一个该类型的对象。
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; } }需要注意的是Provide方法必须被@Provides所修饰。同时,@Provides方法绑定方式是可以和上面提到的注解绑定混合使用的,如:
@Provides @PayPal CreditCardProcessor providePayPalCreditCardProcessor( @Named("PayPal API key") String apiKey) { PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor(); processor.setApiKey(apiKey); return processor; }这样一来,只有被@PayPal修饰的CreditCardProcessor对象才会使用provide方法来创建对象,同时
和链式绑定不同的是它们的优先级,@ImplementedBy实现的是一种default绑定,当同时存在@ImplementedBy和链式绑定时,链式绑定起作用。
@ImplementedBy(PayPalCreditCardProcessor.class) public interface CreditCardProcessor { ChargeResult charge(String amount, CreditCard creditCard) throws UnreachableException; }等价于:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
在定义类型的时候直接指定子类型的Provider类。
@ProvidedBy(DatabaseTransactionLogProvider.class) public interface TransactionLog { void logConnectException(UnreachableException e); void logChargeResult(ChargeResult result); }等价于:
bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class);并且,和@ImplementedBy类似,@ProvidedBy的优先级也比较低,是一种默认实现,当@ProvidedBy和toProvider函数两种绑定方式并存时,后者有效。
这里需要注意的是,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(); }
有了上面的基础我们再来看Setter注入就非常简单了,只不过在setter方法上增加一个@Inject注解而已。 还是上面的例子,只是有一点修改:
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; } }
四. 对象产生的Scopes
在默认情况下,每次通过Guice去请求对象时,都会得到一个新的对象,这种行为是通过Scopes去配置的。下面我们就以@Singleton举例说明怎么来告诉Guice我们要以@Singleton的方式产生对象:
1. 在定义子类型时声明
@Singleton public class InMemoryTransactionLog implements TransactionLog { /* everything here should be threadsafe! */ }2.在module的configure方法中做绑定时声明
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);这样一共会生成2个Applebees对象,一个给Bar用,一个给Grill用。如果在上面的配置的情况下,还有下面的配置
bind(Applebees.class).in(Singleton.class);这样一来,Bar和Grill就会共享同样的对象了。
Spring已经出来好多年了,当年是作为轻量级J2EE容器和EJB抗衡的,不过随着技术和时间的发展,Spring越来越全面,越来越强大,也就越来越Heavy了。而且,在使用Spring的过程中,因为所有Bean直接的关联都是在XML配置文件中完成的,于是当系统变大之后,XML配置中的内容会非常的多,感觉会很乱。
Google-Guice是最近几年刚刚出来的一种DI框架,它的好处就是简单,轻量级,快。Guice中的Bean之间的关联都是在Module中定义的,如果需要定义不同的关联,则需要定义不同的Module,这点相对来说会麻烦一些,但是这种使用Annotation和Module的方式,对于程序员来说,可能会感觉更为直接。另外,Guice启动的时候比Spring要快很多,据说要快100倍,这主要是因为Spring要去读配置文件,要Parse XML文件。但是个人感觉这种快,其实作用有限,因为这种速度的差异,只是在load Bean的时候,当Bean都已经被load到内存之后,其实就没什么差别了,所以Guice快只是快在启动阶段。
关于在做项目时,到底选择Spring还是选择Guice呢? 个人感觉,如果你原来一直在使用Spring或者Guice,那么其实完全没有必要换,因为这样会带来额外的复杂度。原来熟悉那个用那个就OK了。 如果Spring和Guice都没用过,或者说都用过,那么我觉得就看项目需要了,如果项目需要的仅仅是个DI容器,那么我觉得Guice就足够了,好处就是简单,上手快,启动快;如果项目需要更多功能,要和其他的框架进行兼容,那么个人感觉选择Spring仍然是不错的选择,比较Spring出道时间长,和很多成熟框架之间都有很好的兼容,虽然Guice新的版本已经可以支持AOP功能,并且也可以支持和一些其他框架进行兼容,但是Guice想要在短时间内取代Spring,个人感觉还是非常难的。
Spring | Guice | |
使用XML | 使用将类与类之间的关系隔离到xml中,由容器负责注入被调用的对象,因此叫做依赖注入 | 不使用xml,将类与类之间的关系隔离到Module中,声名何处需要注入,由容器根据Module里的描述,注入被调用的对象。 |
使用Annotation | 使用 支持自定义Annotation标注,对于相同的接口定义的对象引用,为它们标注上不同的自定义Annotation注释,就可以达到同一个类里边的同一个接口的引用,注射给不同的实现,在Module里用标注做区分,灵活性大大增加。 使用Annotation也未必是好事,范型等新特性也未必是好事,目前大多的服务器均不支持jdk1.5,wls要9以前才支持,而目前的客户由于价格原因也很少选用wls9的,至少我们做过的项目中都没有。功能再强,客户不需要,何用? |
|
运行效率 | 装载spring配置文件时,需解析xml,效率低,getBean效率也不高,不过使用环境不会涉及到getBean,只有生产环境的时候会用到getBean,在装载spring应用程序的时候,已经完成全部的注射,所以这个低效率的问题不是问题。 | 使用Annotation,cglib, 效率高与spring最明显的一个区别,spring是在装载spring配置文件的时候把该注入的地方都注入完,而Guice呢,则是在使用的时候去注射,运行效率和灵活性高。 |
类耦合度 | 耦合度低,强调类非侵入,以外部化的方式处理依赖关系,类里边是很干净的,在配置文件里做文章,对类的依赖性极低。 | 高,代码级的标注,DI标记@inject侵入代码中,耦合到了类层面上来,何止侵入,简直侵略,代码耦合了过多guice的东西,大大背离了依赖注入的初衷,对于代码的可维护性,可读性均不利 |
类编写时 | 需要编写xml,配置Bean,配置注入 | 只需声明为@inject,等着被注入, 最后在统一的Module里声明注入方式 |
仅支持IOC | 否,spring目前已经涉猎很多部分 | 是,目前仅仅是个DI容器 |
是否易于代码重构 | 统一的xml配置入口,更改容易 | 配置工作是在Module里进行,和spring异曲同功 |
支持多种注入方式 | 构造器,setter方法 | Field,构造器,setter方法 |
灵活性 | 1,如果同一个接口定义的引用需要注入不同的实现,就要编写不同的Module,烦琐 2,动态注入 如果你想注射的一个实现,你还未知呢,怎么办呢,spring是没办法,事先在配置文件里写死的,而Guice就可以做到,就是说我想注射的这个对象我还不知道注射给谁呢,是在运行时才能得到的的这个接口的实现,所以这就大大提高了依赖注射的灵活性,动态注射。 |
|
与现有框架集成度 | 1, 高,众多现有优秀的框架(如struts1.x等)均提供了spring的集成入口,而且spring已经不仅仅是依赖注入,包括众多方面。 2, Spring也提供了对Hibernate等的集成,可大大简化开发难度。 3, 提供对于orm,rmi,webservice等等接口众多,体系庞大。 |
1,可以与现有框架集成,不过仅仅依靠一个效率稍高的DI,就想取代spring的地位,有点难度。 |
配置复杂度 | 在xml中定位类与类之间的关系,难度低 | 代码级定位类与类之间的关系,难度稍高 |