Kava Pava Gava Tava Nava Zava Java

everything about Java
随笔 - 15, 文章 - 0, 评论 - 1, 引用 - 0
数据加载中……

Google Guice 用户手册之 阅读笔记

目的:

         直接调用new - 
                直接耦合具体的实现类。这是最不灵活的方式,直接紧密耦合,当需要置换具体实施方式的时候(比如在测试中不能用真正的服务)会遇到麻烦。

        使用 Factory
                用户调用工厂 .getInstance() 方法得到具体实现。间接耦合。缺点是每次使用前都要设置工厂,以便得到想要的实现。每个接口都要有相应的工厂。如果对象增加依赖,要记得在每一处需要的地方(比如每个 Unit Test)设置工厂。如果忘记设置/初始化工厂,仅仅在要用到服务的时候才会出错。

        初始化时注入
                在初始化一个对象的时候给出所有的依赖对象。好处是(1)间接耦合,对象仅仅关心接口。(2)当对象需要新增加一个依赖的时候,编译器会强制每个用户给出该依赖的实现对象。缺点是,现在对象的用户需要关心所有的初始化和依赖。

Dependency Injection with Guice

首先,是一个配置或者说映射。谢天谢地,Google 也恨 XML。所以,Google 用一个 java class 来做配置模块。

public class BillingModule extends AbstractModule {
  @Override 
  
protected void configure() {
    bind(TransactionLog.
class).to(DatabaseTransactionLog.class);
    bind(CreditCardProcessor.
class).to(PaypalCreditCardProcessor.class);
    bind(BillingService.
class).to(RealBillingService.class);
  }
}

看起来很简单。就是把接口映射到具体的实现的类。再来看需要被注入依赖实例的类:

public class RealBillingService implements BillingService {
  
private final CreditCardProcessor processor;
  
private final TransactionLog transactionLog;

  @Inject
  
public RealBillingService(CreditCardProcessor processor,
      TransactionLog transactionLog) {
    
this.processor = processor;
    
this.transactionLog = transactionLog;
  }

  
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
     
  }
}

也很简单,一个 @Inject 就可以了。最后,最终用户需要从配置模块生成 Injector,再从 Injector 得到需要的实例。

  public static void main(String[] args) {
    Injector injector 
= Guice.createInjector(new BillingModule());
    BillingService billingService 
= injector.getInstance(BillingService.class);
    
  }

Bindings

首先是各种 Binding,也就是说如何配置想注入的对象。Module 是由 Bindings 组成的。有以下几种Binding:

Linked Binding:
        例子: bind(Interface.class).to(Implementation.class)
        用处: 把实现连接到接口,或者把子类连接到父类(慎用)。甚至可以 A 连接到 B,B 连接到 C 这样地串联起来。这样,当要 A 的时候,得到 C。

Binding Annotations

        先定义一个标注
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface PayPal {}
    然后在定义 @Inject 的时候同时使用标注
  @Inject
 
public RealBillingService(@PayPal CreditCardProcessor processor,
     
TransactionLog transactionLog) {
   
...
 
}
    最后,在 Binding 的时候使用标注来说明想要注入的是哪个实例

    bind(CreditCardProcessor.class)
       
.annotatedWith(PayPal.class)
       
.to(PayPalCreditCardProcessor.class);
    如果不想自己写标注,也能容忍字符匹配,可以用@named标注:
  @Inject
 
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
     
TransactionLog transactionLog) {
   
...
 
}
    bind(CreditCardProcessor.class)
       
.annotatedWith(Names.named("Checkout"))
       
.to(CheckoutCreditCardProcessor.class);
        也可以自己写带有参数的标注。先写标注接口,再写标注实例,注意实现 equals() 和 hashCode(),就可以用在 annotatedWith 里面了。

        用处:当不同的地方要不同的实例的时候。

Instance Binding

        提供一个简单的实例,而不是去初始化。通常就是简单类型,做配置用。
    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 Methods

在配置模块里面写 @Provides 方法。
public
class BillingModule extends AbstractModule {
 
@Override
 
protected void configure() {
   
...
 
}

 @Provides

 
TransactionLog provideTransactionLog() {
    // 这个方法生成实例并且返回。
  }
}


  @Provides 也可以带有标注,或者有参数的标注比如 @Named("Checkout")。这样这个标注就会被绑定在这个@Provider方法上了。
 
@Provides @PayPal
 
CreditCardProcessor providePayPalCreditCardProcessor(
     
@Named("PayPal API key") String apiKey) {
    // 具体实现。。。

 
}
    总之,Guice 以 @Provider 函数返回的类型以及标注来判断该调用哪个函数来得到实例。

Provider Bindings

    @Provider 写得太多,模块就会变得太大,而且太杂乱。这时候就可以写 Provider。Provider 需要实现这个简单的接口:

    public interface Provider<T> {
      T
get();
    }
Provider 也可以有自己的依赖关系。然后,就可以在Binding 里面使用了。
 
    public class BillingModule extends AbstractModule {
     
@Override
     
protected void configure() {
        bind
(TransactionLog.class)
           
.toProvider(DatabaseTransactionLogProvider.class);
     
}
Untargetted Bindings

        这个最简单了,不需要指派绑定到的具体实现。这个需要接口指定了默认的 @ImplementedBy (类)或者 @ProvidedBy (Provider类)。
    bind(MyConcreteClass.class);
    bind
(AnotherConcreteClass.class).in(Singleton.class);
Built-in Bindings 

        这些是Guice提供的“拿来就用”的绑定,很方便。比如java.util.logging.Logger。Guice 还会帮助做些设置。其他的似乎 Guice 也不推荐或者详细说明,就不研究了。
  @Inject
 
public ConsoleTransactionLog(Logger logger) {
   
this.logger = logger;
 
}
Just-in-Time Bindings

        就是没有在模块里面说明,而是在需要注入的时候决定的。首先是默认构造函数,如果一个类有默认构造函数(就是没有参数的),那么Guice就用它。其次是接口指定了默认的 @ImplementedBy (类)或者 @ProvidedBy (Provider类)。

@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
 
ChargeResult charge(String amount, CreditCard creditCard)
     
throws UnreachableException;
}
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
 
void logConnectException(UnreachableException e);
 
void logChargeResult(ChargeResult result);
}
这种默认的接口实现可以在使用时,由明确的 Binding 取代。而如果没有被取代,就用默认。

Scope

上面讲了绑定。下面讲 Scope。Scope 就是在什么范围内可以共享同一个实例。比如@Singleton就在整个应用中使用同一个实例。Scope 可以这样指定:

标注在实现类上:
    @Singleton
    public class InMemoryTransactionLog implements TransactionLog {
     
/* everything here should be threadsafe! */
    }
在Binding中明确表示
    bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
在 @Provides 方法中
  @Provides @Singleton
 
TransactionLog provideTransactionLog() {
   
...
 
}
注意 Scope 是按照要得到的类型(接口类型)而不是具体实现的类而计算的。比如,Applebees 实现了 Bar 和 Grill 两个接口,下面将会用两个 Applebees 实例。

  bind(Bar.class).to(Applebees.class).in(Singleton.class);
  bind
(Grill.class).to(Applebees.class).in(Singleton.class);
想只用一个实例,就这样加上一条规则:
  bind(Applebees.class).in(Singleton.class);
在 in 子句里面可以用 RequestScoped.class 或者 ServletScopes.REQUEST。用前者比较好,因为表示的不只是 Servlet 中的 Scope。

默认的 Scope 是每一次 Guice 都生成一个新实例。其他Scope 还有 Singleton, Request, Session。

确定为 Singleton 的实例可能在启动的时候生成,这样可以早发现问题,但可能增加启动时间。

选择 Scope 要看实例是不是有状态,该状态要在什么范围共享,以及创建实例要花费的代价。 @Singleton 和 @SessionScoped 必须线程安全。而 @RequestScoped 不需要。

注入

注入可以在多处进行。首先,是前面用过的在构造函数中(推荐,编译器检查,好测试):

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;
 
}
可以在方法中(仅仅类型影响Guice,方法名字不重要。比构造函数灵活。):
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;
 
}
也可以在 Field 中(不推荐)
    public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
     
@Inject Connection connection;
    
     
public TransactionLog get() {
       
return new DatabaseTransactionLog(connection);
     
}
}
Method 和 Field 还可选。如果找不到,Guice 就不注入。
  @Inject(optional=true)
 
public void setApiKey(@Named("PayPal API key") String apiKey) {
   
this.apiKey = apiKey;
 
}
另外,对于一个已经初始化了的实例,Guice的injector还有一个injectMembers方法。
Static Injections 手册对于新写的代码不推荐,不再研究。
Automatic Injection :对于作为参数传给了 toInstance()方法的实例(Instance Binding),还有传给了 toProvider()方法的实例(Provider Binding),如果需要,Guice都会自动去注入。
另外,也可以将一个 Provider 注入给用户,由用户自己决定什么时候去调用 Provider 去取得实例。这样可以获得多个实例,或者实现 lazy loading。
AOP
对于有Guice生成的实例,Guice还可以进行方法拦截。具体不再叙述,看手册的例子。

posted on 2009-12-30 18:55 bing 阅读(2184) 评论(1)  编辑  收藏

评论

# re: Google Guice 用户手册之 阅读笔记  回复  更多评论   

不错,不错,我也写了一篇文章:http://www.blogjava.net/xylz/archive/2009/12/22/306955.html
2009-12-30 23:27 | xylz

只有注册用户登录后才能发表评论。


网站导航: