Google Guice学习

学习动力:公司项目使用

官方文档:https://github.com/google/guice/wiki/Motivation

学习阶段:入门

 

主要部份:

  1. 简介
  2. Bindings方式
  3. Scopes设定
  4. Injecting Providers
  5. 最佳实践 

 

 简介

Guice通过注解方式提供以来注入,一种方式是通过@Injuct注入构造函数

class BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog transactionLog;

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

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

 

然后通过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);
  }
}
View Code

 

最后通过Injector.getInstance(),通过接口类和Module获得实现类,减少了和具体类之间的耦合,让代码结构更加稳定,易于测试。

 

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.
     */
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }
View Code

 

 

 

 Bindings方式

1.Linked Bindings方式

Linked Bindings将一个类型与它的实现类绑定。甚至可以将一个具体类型与它的子类绑定。绑定关系可以串联。

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
    bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  }
}
View Code

 

2.Binding Annotations方式

有时候我们需要将多个实现类绑定到同一个类型上面。比如在BillingService中,我们需要将PayPal credit card processor和Google Checkout processor 绑定到同一个CreditCardProcessor类型上。  Binding Annotations提供几种方式来实现,项目中使用比较多的是这种@Named。我们在需要注入的地方这样写:

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
      TransactionLog transactionLog) {
    ...
  }
View Code

 

然后在Module中这样绑定:

    bind(CreditCardProcessor.class)
        .annotatedWith(Names.named("Checkout"))
        .to(CheckoutCreditCardProcessor.class);
View Code

 

3.Instance Bindings

对于无法继承的类(比如String和Integer),可以使用toInstance()来绑定。

    bind(String.class)
        .annotatedWith(Names.named("JDBC URL"))
        .toInstance("jdbc:mysql://localhost/pizza");
    bind(Integer.class)
        .annotatedWith(Names.named("login timeout seconds"))
        .toInstance(10);
View Code

 

4.@Provides Methods

使用@Provides可以通过创建一个方法来提供所需类型的实现,当需要这个类型的时候,injector就会调用这个方法。

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;
  }
}
View Code

 

5.Provider Bindings

当@Provides 方法越来越复杂的时候,你会考虑使用一个Provider类来管理。Guice提供了Provider这个接口。Provider自身可能也有需要注入的组件。

下面是一个例子

public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  private final Connection connection;

  @Inject
  public DatabaseTransactionLogProvider(Connection connection) {
    this.connection = connection;
  }

  public TransactionLog get() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setConnection(connection);
    return transactionLog;
  }
}
View Code

然后我们使用toProvider()将Provider和需要注入的类型绑定。

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);
  }
View Code

 

 

6.Untargeted Bindings

当需要绑定具体类,或是使用@ImplementedBy和@ProvidedBy的类型时,可以直接bind(),不加上to()

    bind(MyConcreteClass.class);
    bind(AnotherConcreteClass.class).in(Singleton.class);
View Code

 

7.Constructor Bindings

当需要注入的对象来自第三方库,或是有多个构造函数的时候,我们不能使用@Injuct,这时可以使用@Provides可以解决问题!

但是因为一个什么原因(我没看懂),这也提供了一种toConstructor()来绑定。

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    try {
      bind(TransactionLog.class).toConstructor(
          DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
    } catch (NoSuchMethodException e) {
      addError(e);
    }
  }
}
View Code

 

 

Scopes设定

依赖注入框架既然帮忙注入对象,自然也要管理对象的生命周期Scope。Guice中通过注解来设定对象的Scope。

Guice中典型的生命周期有for the lifetime of an application (@Singleton), a session (@SessionScoped), or a request (@RequestScoped)

可以在相应的实现类上标注。

@Singleton
public class InMemoryTransactionLog implements TransactionLog {
  /* everything here should be threadsafe! */
}
View Code

或是通过跟在bind()后的in()来设定。

bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

或是在@Provides方法上标注。

  @Provides @Singleton
  TransactionLog provideTransactionLog() {
    ...
  }
View Code

当类型上的Scope和bind()方法后in()的Scope冲突时,bind()的Scope会被使用。

还有一些比较复杂的概念,延迟加载,需要深入理解的时候在再看吧。

 

Injecting Providers

我理解这一章的内容是使用Provider来替代接口作为注入的入口的好处。

当你需要多次获得一个新的注入对象时,使用Provider

public class LogFileTransactionLog implements TransactionLog {

  private final Provider<LogFileEntry> logFileProvider;

  @Inject
  public LogFileTransactionLog(Provider<LogFileEntry> logFileProvider) {
    this.logFileProvider = logFileProvider;
  }

  public void logChargeResult(ChargeResult result) {
    LogFileEntry summaryEntry = logFileProvider.get();
    summaryEntry.setText("Charge " + (result.wasSuccessful() ? "success" : "failure"));
    summaryEntry.save();

    if (!result.wasSuccessful()) {
      LogFileEntry detailEntry = logFileProvider.get();
      detailEntry.setText("Failure result: " + result);
      detailEntry.save();
    }
  }
View Code

 

当你需要延迟加载一些重要对象,特别是它并不总是需要加载时,使用Provider

public class DatabaseTransactionLog implements TransactionLog {

  private final Provider<Connection> connectionProvider;

  @Inject
  public DatabaseTransactionLog(Provider<Connection> connectionProvider) {
    this.connectionProvider = connectionProvider;
  }

  public void logChargeResult(ChargeResult result) {
    /* only write failed charges to the database */
    if (!result.wasSuccessful()) {
      Connection connection = connectionProvider.get();
    }
  }
View Code

 

如果你不希望对象的生命周期混杂,比如一个Singleton的Logger和一个RequestScope的User在一起使用就可能会出现问题。

@Singleton
public class ConsoleTransactionLog implements TransactionLog {

  private final AtomicInteger failureCount = new AtomicInteger();
  private final Provider<User> userProvider;

  @Inject
  public ConsoleTransactionLog(Provider<User> userProvider) {
    this.userProvider = userProvider;
  }

  public void logConnectException(UnreachableException e) {
    failureCount.incrementAndGet();
    User user = userProvider.get();
    System.out.println("Connection failed for " + user + ": " + e.getMessage());
    System.out.println("Failure count: " + failureCount.incrementAndGet());
  }
View Code

 

 

最佳实践

1.尽量注入不可变对象

 如果可以,我们使用构造方法来注入不可变的对象,它们简单,易于分享,可以被组合。

public class RealPaymentService implements PaymentService { 

   private final PaymentQueue paymentQueue; 
   private final Notifier notifier;  

   @Inject 
   RealPaymentRequestService( 
       PaymentQueue paymentQueue, 
       Notifier notifier) { 
     this.paymentQueue = paymentQueue; 
     this.notifier = notifier; 
   }

   ...
View Code

2.仅注入直接依赖的对象

避免注入一个对象,仅仅是为了使用它获得另一个对象。

public class ShowBudgets { 
   private final Account account; 

   @Inject 
   ShowBudgets(Customer customer) { 
     account = customer.getPurchasingAccount(); 
   } 
View Code

更好的方法是,在Module中使用@Provides 通过Customer创建一个Account对象

public class CustomersModule extends AbstractModule { 
  @Override public void configure() {
    ...
  }

  @Provides 
  Account providePurchasingAccount(Customer customer) { 
    return customer.getPurchasingAccount();
  }
View Code

这样,需要注入的地方代码会更简单

public class ShowBudgets { 
   private final Account account; 

   @Inject 
   ShowBudgets(Account account) { 
     this.account = account; 
   } 
View Code

 

3.解决循环依赖 

下面这样的就是循环依赖,Store -> Boss -> Clerk -> Store ...

public class Store {
    private final Boss boss;
    //...

    @Inject public Store(Boss boss) {
        this.boss = boss;
        //...
    }
    public void incomingCustomer(Customer customer) {...}
    public Customer getNextCustomer() {...}
}

public class Boss {
    private final Clerk clerk;
    @Inject public Boss(Clerk clerk) {
        this.clerk = clerk;
    }
}

public class Clerk {
    private final Store shop;
    @Inject Clerk(Store shop) {
        this.shop = shop;
    }

    void doSale() {
        Customer sucker = shop.getNextCustomer();
        //...
    }
}
View Code

文档里有解决的办法。https://github.com/google/guice/wiki/CyclicDependencies

 

4.避免在Module写入具体的逻辑

public class FooModule {
  private final String fooServer;

  public FooModule() {
    this(null);
  }

  public FooModule(@Nullable String fooServer) {
    this.fooServer = fooServer;
  }

  @Override protected void configure() {
    if (fooServer != null) {
      bind(String.class).annotatedWith(named("fooServer")).toInstance(fooServer);
      bind(FooService.class).to(RemoteFooService.class);
    } else {
      bind(FooService.class).to(InMemoryFooService.class);
    }
  }
}
View Code

解决方法是将FooModule分成RemoteFooModule 和 InMemoryFooModule。

 

 

 

 

 

posted @ 2017-02-16 15:25  andrew-chen  阅读(458)  评论(0编辑  收藏  举报