public interface Log { void log(); }
定义一个实现类:
public class DbLog implements Log { public void log() { System.out.println("dblog"); } }
定义一个Module管理接口与实现类的绑定关系:
public class LinkedBindingModule extends AbstractModule { @Override protected void configure() { bind(Log.class).to(DbLog.class); } }
Module(模块)是Guice用来管理一组绑定关系的地方。自定义模块需要继承AbstractModule类并覆盖configure方法,在configure方法中设置绑定关系。
Guice创建时可使用多个模块,则注入器可以注入多个模块中指定的绑定关系。
如何使用:
使用@Inject注解标注需要注入的属性,Guice会根据绑定关系将接口的具体实现类注入。
开启Guice并获取Log接口的实现类:
输出结果:
Guice支持3种注入方式:属性注入,方法注入,构造器注入
上文给出了属性注入的方式,下面看一下方法注入和构造器注入。
方法注入:
方法注入支持任意方法名,但是建议使用setter方法。
方法注入支持多个参数的注入。
构造器注入:
构造器注入是推荐的注入方式,使用一个构造器注入所有依赖,可以避免忘记注入某个依赖。
尽量使用final修饰属性,可以避免属性引用被重新赋值导致的问题,这种问题一般很难被发现。
注意:
1.如果类中有构造器但是没有标注@Inject,需要保证类中有一个public的无参构造器,Guice使用反射机制调用这个构造器创建实例。
2.如果类中有多个构造器,只能有一个构造器使用@Inject标注。Guice不支持多个构造器标注@Inject。如果有多个构造器都需要注入依赖,则使用下文中的Provider绑定或构造器绑定。
Guice提供了多种绑定方式,用来解决绑定依赖,以及创建绑定过程中碰到的问题。
语法:bind(接口类.class).to(实现类.class)
链接绑定绑定接口到实现类。任何使用接口的地方都可以得到自动注入的实现类。
链接绑定可以被传递。上面这种情况,所有请求TransactionLog
的地方,注入器(injector)会注入MySqlDatabaseTransactionLog,而不是DatabaseTransactionLog。
借助注解区分接口的不同实现类。两种方式:自定义注解或使用Guice提供的@Named注解
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) public @interface PayPal {}
@BindingAnnotation
告诉Guice这是一个绑定注解。如果在一个位置(FIELD, PARAMETER, METHOD),使用多个绑定注解,Guice会报错。@Target({FIELD, PARAMETER, METHOD})注解生效的位置
@Retention(RUNTIME)
这个注解在运行时起作用使用自定义注解:
public class RealBillingService implements BillingService { @Inject public RealBillingService(@PayPal CreditCardProcessor processor, TransactionLog transactionLog) { ... }
Module中绑定注解和特定实现类:
bind(CreditCardProcessor.class) .annotatedWith(PayPal.class) .to(PayPalCreditCardProcessor.class);
使用annotatedWith方法将自定义注解与特定实现类联系起来。
public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }
在Module中联系注解和特定实现类
bind(CreditCardProcessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
一般的绑定都是class到class的绑定。Guice提供对绑定具体实例的支持,结合Names注解,可以方便的注入某些参数。
bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
注意:
1.实例绑定仅用于自身不包含依赖需要绑定的场景。
2.实例绑定中的实例不应该包含复杂耗时的操作,那样会使系统启动变慢。如果有复杂的操作,可以使用下面的绑定:Provides绑定
接口的具体实现类,需要在代码中new出来或者其他方式创建了对象。在Module中创建一个方法并标注@Provides注解即可。
每当需要注入TransactionLog,注入器就会调用这个方法provideTransactionLog.
考虑另外一种情况:@Provides所在的方法中也需要注入参数怎么办?Guice支持!直接在方法参数中标注即可。Guice会在调用@Provides方法前,注入对应依赖。
再考虑另外一种情况:当前方法返回值只是接口的某一个特定实现怎么办?Guice支持!直接在方法上标注@PayPal或@Named("PayPal")即可。
注意:@Provides方法不支持抛出异常,所有异常都会被包装在ProvisionException中。
如果需要抛出特定异常,可以使用Guice的扩展:ThrowingProviders extension @CheckedProvides标注的方法。
@Provides方法只能在Module中定义,如果方法比较复杂,比较多则可以考虑分解到单独的类中。
Guice提供Provider接口实现这个功能。
接口定义如下:
public interface Provider{ T get(); }
Provide类通过泛型支持特定的接口。
这种Provide需要在Module中使用.toProvider 方法绑定联系。
注意:使用Provider绑定,无法应用Guice提供的AOP功能。
适用于以下情况:
1.绑定依赖到第三方等无法添加@Inject注解的类
2.类中有多个构造器参与依赖注入,且不使用属性注入/方法注入时。
以上2种情况都可以用provider,直接使用java代码创建对象的方式注入。
但是provider直接创建对象无法应用Guice提供的AOP功能.
使用构造器绑定非常简单,在Module中调用toConstructor方法即可。
TransactionLog绑定的实现类为DatabaseTransactionLog,且Guice会调用对应的含有一个DatabaseConnection参数的构造器,构造实现类。
DatabaseConnection将根据绑定关系注入正确的实现类,完成依赖注入。
有一些绑定是Guice已经准备好的,不需要在Module中指定绑定关系。
除了在Module中绑定接口的实现类之外,还可以使用@ImplementedBy
or @ProvidedBy标记接口,提供实现类。
这种情况下,可以不用在Module中指定绑定关系,也可以在Module中指定没有目标的绑定,此时绑定关系中没有to语句:
注意:如果带有标注,则必须指定具体实现类。
8.自动被Guice注入的绑定:实时绑定(Just-in-time Bindings)
在Module中指定的绑定称为显式绑定,如果没有指定显式绑定,Guice会尝试实时绑定(Just-in-time Bindings)。
实时绑定包括3种:除了@ImplementedBy和@ProvidedBy这2种之外,还有一种是通过合适的构造器绑定。
等同于:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
注意:使用这个标注不是一个很好的设计。一个接口不应该知道它的实现类。
等同于:
bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class);
注意:如果显式绑定和实时绑定同时存在,则以显式绑定为准。
合适的构造器
合适的构造器是指:类中的构造器必须有@Inject标注
Guice默认指定No Scope.
下面看下如何指定单例:
1.使用@Singleton注解
2.显式指定
3.使用@Provides标注的方法
注意:实现Provider接口的提供者,指定Singleton无效。
如果一个实现类继承2个接口,且应用中只需要一个,如何绑定?
在实现类中指定@Singleton注解或指定实现类为单例:bind(Applebees.class).in(Singleton.class);
作用范围为RequestScoped ,SessionScoped 比较简单,直接指定即可:
bind(UserPreferences.class) .toProvider(UserPreferencesProvider.class) .in(ServletScopes.REQUEST);
延迟加载会加快开发,测试等进度,但是也容易隐藏问题。
使用asEagerSingleton方法,可以强制Guice立即实例化类并注入:
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();
在不同的环境下,延迟加载的作用机制:
如果是一个有状态的对象,必须明显的指出它的作用范围,比如整个应用有效,则标注@Singleton,每个请求有效则标注@RequestScoped等等。
如果一个对象是无状态的,并且创建新对象开销不大,那么就让Guice创建新的。
单例模式比较重要,可以减低对象的创建减少垃圾回收,但是单例模式的类的初始化需要线程同步。所以非单例的对象引用单例的对象,需要添加volatile参数,保证初始化完成后对所有线程可见,且不会被多次执行。
单例模式常在以下情况下使用:
单例和SessionScoped的对象必须是线程安全的。所有注入到线程安全的对象的其他实例也必须是线程安全的。
RequestScoped的对象不是线程安全的,比较容易犯的错误是单例对象注入了非线程安全的对象,比如SessionScoped的对象注入了RequestScoped的对象。
好的解决方案是范围宽的对象需要注入范围窄的对象时,注入Provider,祥见下面注入Provider.
Provider可以通过get方法创建自定义的对象,Guice可以通过实时绑定,自动注入Provider。
注入Provider一般用来解决几种问题:需要一个对象的多个实例时,按条件延迟加载时,混合多个作用范围时。
有时候在一个方法中需要一个对象的多个实例,比如一次收费的摘要和详情应该保存在不同的记录中。
有时候需要在某些条件下才需要获取对象:比如收费失败时才获取数据库连接并记录。
这种情况一般用于比较昂贵的对象或连接资源的对象。
一个对象注入了范围比自己窄的对象是错误的!
比如一个单例的对象需要一个请求范围的当前用户对象,Guice注入的对象只是当前用户的,这个注入不变,下一个用户使用的就不是自己的用户信息了。
使用provider可以安全的解决这个问题。
Guice通过支持方法拦截器来支持AOP.
为了支持方法拦截,必须要有2个组件支持:匹配器(Matcher)和拦截器(Interceptors)
注意:为了拦截到方法层面,匹配器需要2个配合,一个用来匹配类层次,另外一个用来匹配要拦截的那个方法。
明白了以上2个组件之后,使用AOP就非常简单了:
定义一个类实现org.aopalliance.intercept.MethodInterceptor拦截器接口,然后在Module中绑定拦截器即可。
Module中指定绑定关系和匹配器:
匹配器支持的几种匹配:
小结: