zoukankan      html  css  js  c++  java
  • 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。

     

  • 相关阅读:
    MVC知识点01
    MVC知识点02
    ADO.NET基础01
    WinForm,MVC知识点
    C#基础01
    28、对多次使用的RDD进行持久化或Checkpoint
    27、优化数据结构
    26、高性能序列化类库
    25、诊断内存的消耗
    24、Checkpoint原理剖析
  • 原文地址:https://www.cnblogs.com/andrew-chen/p/6404354.html
Copyright © 2011-2022 走看看