Google Guice 依赖注入框架:简明教程

Guice是一个轻量级的依赖注入框架。用于对象之间的依赖的注入。
本文的所有例子基于Guice 4.0

Guice的使用:

Guice的使用涉及接口,接口的实现类,接口与其实现类的绑定关系三个方面。
定义一个接口:
 
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支持的注入方式

Guice支持3种注入方式:属性注入,方法注入,构造器注入

上文给出了属性注入的方式,下面看一下方法注入和构造器注入。

方法注入:

方法注入支持任意方法名,但是建议使用setter方法。

方法注入支持多个参数的注入。

构造器注入:

构造器注入是推荐的注入方式,使用一个构造器注入所有依赖,可以避免忘记注入某个依赖。

尽量使用final修饰属性,可以避免属性引用被重新赋值导致的问题,这种问题一般很难被发现。

注意:

1.如果类中有构造器但是没有标注@Inject,需要保证类中有一个public的无参构造器,Guice使用反射机制调用这个构造器创建实例。

2.如果类中有多个构造器,只能有一个构造器使用@Inject标注。Guice不支持多个构造器标注@Inject。如果有多个构造器都需要注入依赖,则使用下文中的Provider绑定或构造器绑定。

Guice的绑定方式

Guice提供了多种绑定方式,用来解决绑定依赖,以及创建绑定过程中碰到的问题。

1.最简单的绑定:链接绑定

语法:bind(接口类.class).to(实现类.class)

链接绑定绑定接口到实现类。任何使用接口的地方都可以得到自动注入的实现类。

链接绑定可以被传递。上面这种情况,所有请求TransactionLog 的地方,注入器(injector)会注入MySqlDatabaseTransactionLog,而不是DatabaseTransactionLog。

2.一个接口有多个实现类的绑定:注解绑定

借助注解区分接口的不同实现类。两种方式:自定义注解或使用Guice提供的@Named注解

自定义注解:
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface PayPal {}
  • @BindingAnnotation 告诉Guice这是一个绑定注解。如果在一个位置(FIELDPARAMETERMETHOD),使用多个绑定注解,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方法将自定义注解与特定实现类联系起来。
使用Guice提供的Named注解

 


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);

 

3.绑定接口的一个具体实例:实例绑定

一般的绑定都是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绑定

4.需要代码方式创建对象,使用:Provide绑定

接口的具体实现类,需要在代码中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功能。

5.构造器绑定

适用于以下情况:

1.绑定依赖到第三方等无法添加@Inject注解的类

2.类中有多个构造器参与依赖注入,且不使用属性注入/方法注入时。

以上2种情况都可以用provider,直接使用java代码创建对象的方式注入。

但是provider直接创建对象无法应用Guice提供的AOP功能.

使用构造器绑定非常简单,在Module中调用toConstructor方法即可。

TransactionLog绑定的实现类为DatabaseTransactionLog,且Guice会调用对应的含有一个DatabaseConnection参数的构造器,构造实现类。

DatabaseConnection将根据绑定关系注入正确的实现类,完成依赖注入。

6.内建绑定

有一些绑定是Guice已经准备好的,不需要在Module中指定绑定关系。

  • java.util.logging.Logger,Guice自动注入合适的实现类。这个我们一般不用,我们一般用slf4j,log4j等。
  • Injector 注入器

  • Providers 实例提供者

  • TypeLiterals 

  • Stage 舞台,环境 TOOL,DEVELOPMENT,PRODUCTION

  • MembersInjectors 注册实例的依赖

7.没有目标(target)的绑定

除了在Module中绑定接口的实现类之外,还可以使用@ImplementedBy or @ProvidedBy标记接口,提供实现类。

这种情况下,可以不用在Module中指定绑定关系,也可以在Module中指定没有目标的绑定,此时绑定关系中没有to语句:

注意:如果带有标注,则必须指定具体实现类。

 8.自动被Guice注入的绑定:实时绑定(Just-in-time Bindings)

在Module中指定的绑定称为显式绑定,如果没有指定显式绑定,Guice会尝试实时绑定(Just-in-time Bindings)。

实时绑定包括3种:除了@ImplementedBy和@ProvidedBy这2种之外,还有一种是通过合适的构造器绑定。

  • @ImplementedBy

等同于:

bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);

 

注意:使用这个标注不是一个很好的设计。一个接口不应该知道它的实现类。

  • @ProvidedBy

等同于:

 

bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);

注意:如果显式绑定和实时绑定同时存在,则以显式绑定为准。

合适的构造器

合适的构造器是指:类中的构造器必须有@Inject标注

实例的作用范围(Scope)

几种常见的作用范围

  1. No Scope 每次创建新实例
  2. Singleton  整个应用中只有一个
  3. RequestScoped  每个servlet请求创建一个实例
  4. SessionScoped   每个Servlet会话创建一个实例
  5. 自定义范围   自定义

应用作用范围,使其生效

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

Provider可以通过get方法创建自定义的对象,Guice可以通过实时绑定,自动注入Provider。

注入Provider一般用来解决几种问题:需要一个对象的多个实例时,按条件延迟加载时,混合多个作用范围时。

需要一个对象的多个实例时:

有时候在一个方法中需要一个对象的多个实例,比如一次收费的摘要和详情应该保存在不同的记录中。

按条件延迟加载时:

有时候需要在某些条件下才需要获取对象:比如收费失败时才获取数据库连接并记录。

这种情况一般用于比较昂贵的对象或连接资源的对象。

混合多个作用范围时:

一个对象注入了范围比自己窄的对象是错误的!

比如一个单例的对象需要一个请求范围的当前用户对象,Guice注入的对象只是当前用户的,这个注入不变,下一个用户使用的就不是自己的用户信息了。

使用provider可以安全的解决这个问题。

Guice对AOP的支持

Guice通过支持方法拦截器来支持AOP.

为了支持方法拦截,必须要有2个组件支持:匹配器(Matcher)和拦截器(Interceptors)

注意:为了拦截到方法层面,匹配器需要2个配合,一个用来匹配类层次,另外一个用来匹配要拦截的那个方法。

明白了以上2个组件之后,使用AOP就非常简单了:

定义一个类实现org.aopalliance.intercept.MethodInterceptor拦截器接口,然后在Module中绑定拦截器即可。

Module中指定绑定关系和匹配器:


匹配器支持的几种匹配:

小结:


你可能感兴趣的:(java,基础,Guice)