Google 开源的依赖注入库,比 Spring 更小更快!

 2021-09-22 22:39  阅读(121)
文章分类:框架设计 文章标签:Guice

Guice是Google开源的一个依赖注入类库,相比于Spring IoC来说更小更快。Elasticsearch大量使用了Guice,本文简单的介绍下Guice的基本概念和使用方式。

学习目标

1、 概述:了解Guice是什么,有什么特点;
2、 快速开始:通过实例了解Guice;
3、 核心概念:了解Guice涉及的核心概念,如绑定(Binding)、范围(Scope)和注入(Injection);
4、 [最佳实践:官方推荐的最佳实践;][1_Guice_ 2_Guice 3_Guice_Binding_Scope_Injection _4]

Guice概述

**1、**Guice是Google开源的依赖注入类库,通过Guice减少了对工厂方法和new的使用,使得代码更易交付、测试和重用;
**2、**Guice可以帮助我们更好地设计API,它是个轻量级非侵入式的类库;
**3、**Guice对开发友好,当有异常发生时能提供更多有用的信息用于分析;

快速开始

假设一个在线预订Pizza的网站,其有一个计费服务接口:

    
        
         public interface BillingService { 
         
     
         /** 
         * 通过信用卡支付。无论支付成功与否都需要记录交易信息。 
         * 
         * @return 交易回执。支付成功时返回成功信息,否则记录失败原因。 
         */ 
         Receipt chargeOrder(PizzaOrder order, CreditCard creditCard); 
         } 
        

使用new的方式获取信用卡支付处理器和数据库交易日志记录器:

    
        
         public class RealBillingService implements BillingService { 
         public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { 
         CreditCardProcessor processor = new PaypalCreditCardProcessor(); 
         TransactionLog transactionLog = new DatabaseTransactionLog(); 
         
     
         try { 
         ChargeResult result = processor.charge(creditCard, order.getAmount()); 
         transactionLog.logChargeResult(result); 
         
     
         return result.wasSuccessful() 
         ? Receipt.forSuccessfulCharge(order.getAmount()) 
         : Receipt.forDeclinedCharge(result.getDeclineMessage()); 
         } catch (UnreachableException e) { 
         transactionLog.logConnectException(e); 
         return Receipt.forSystemFailure(e.getMessage()); 
         } 
         } 
         } 
        

使用new的问题是使得代码耦合,不易维护和测试。比如在UT里不可能直接用真实的信用卡支付,需要Mock一个CreditCardProcessor。相比于new,更容易想到的改进是使用工厂方法,但是工厂方法在测试中仍存在问题(因为通常使用全局变量来保存实例,如果在用例中未重置可能会影响其他用例)

更好的方式是通过构造方法注入依赖:

    
        
         public class RealBillingService implements BillingService { 
         private final CreditCardProcessor processor; 
         private final TransactionLog transactionLog; 
         
     
         public RealBillingService(CreditCardProcessor processor, 
         TransactionLog transactionLog) { 
         this.processor = processor; 
         this.transactionLog = transactionLog; 
         } 
         
     
         public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { 
         try { 
         ChargeResult result = processor.charge(creditCard, order.getAmount()); 
         transactionLog.logChargeResult(result); 
         
     
         return result.wasSuccessful() 
         ? Receipt.forSuccessfulCharge(order.getAmount()) 
         : Receipt.forDeclinedCharge(result.getDeclineMessage()); 
         } catch (UnreachableException e) { 
         transactionLog.logConnectException(e); 
         return Receipt.forSystemFailure(e.getMessage()); 
         } 
         } 
         } 
        

对于真实的网站应用可以注入真正的业务处理服务类:

    
        
         public static void main(String[] args) { 
         CreditCardProcessor processor = new PaypalCreditCardProcessor(); 
         TransactionLog transactionLog = new DatabaseTransactionLog(); 
         BillingService billingService 
         = new RealBillingService(processor, transactionLog); 
         ... 
         } 
        

而在测试用例中可以注入Mock类:

    
        
         public class RealBillingServiceTest extends TestCase { 
         
     
         private final PizzaOrder order = new PizzaOrder(100); 
         private final CreditCard creditCard = new CreditCard("1234", 11, 2010); 
         
     
         private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog(); 
         private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); 
         
     
         public void testSuccessfulCharge() { 
         RealBillingService billingService 
         = new RealBillingService(processor, transactionLog); 
         Receipt receipt = billingService.chargeOrder(order, creditCard); 
         
     
         assertTrue(receipt.hasSuccessfulCharge()); 
         assertEquals(100, receipt.getAmountOfCharge()); 
         assertEquals(creditCard, processor.getCardOfOnlyCharge()); 
         assertEquals(100, processor.getAmountOfOnlyCharge()); 
         assertTrue(transactionLog.wasSuccessLogged()); 
         } 
         } 
        

那通过Guice怎么实现依赖注入呢?首先我们需要告诉Guice如果找到接口对应的实现类,这个可以通过模块来实现:

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

这里的模块只需要实现Module接口或继承自AbstractModule,然后在configure方法中设置绑定(后面会继续介绍)即可。

然后只需在原有的构造方法中增加@Inject注解即可注入

    
        
         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) { 
         try { 
         ChargeResult result = processor.charge(creditCard, order.getAmount()); 
         transactionLog.logChargeResult(result); 
         
     
         return result.wasSuccessful() 
         ? Receipt.forSuccessfulCharge(order.getAmount()) 
         : Receipt.forDeclinedCharge(result.getDeclineMessage()); 
         } catch (UnreachableException e) { 
         transactionLog.logConnectException(e); 
         return Receipt.forSystemFailure(e.getMessage()); 
         } 
         } 
         } 
        

最后,再看看main方法中是如何调用的:

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

绑定

连接绑定

连接绑定是最常用的绑定方式,它将一个类型和它的实现进行映射。下面的例子中将TransactionLog接口映射到它的实现类DatabaseTransactionLog。

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

连接绑定还支持链式,比如下面的例子最终将TransactionLog接口映射到实现类MySqlDatabaseTransactionLog。

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

注解绑定

通过一个类型可能存在多个实现,比如在信用卡支付处理器中存在PayPal的支付和Google支付,这样通过连接绑定就搞不定。这时我们可以通过注解绑定来实现:

    
        
         @BindingAnnotation 
         @Target({ FIELD, PARAMETER, METHOD }) 
         @Retention(RUNTIME) 
         public @interface PayPal {} 
         
     
         public class RealBillingService implements BillingService { 
         
     
         @Inject 
         public RealBillingService(@PayPal CreditCardProcessor processor, 
         TransactionLog transactionLog) { 
         ... 
         } 
         } 
         
     
         // 当注入的方法参数存在@PayPal注解时注入PayPalCreditCardProcessor实现 
         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) { 
         ... 
         } 
         } 
         
     
         // 当注入的方法参数存在@Named注解且值为Checkout时注入CheckoutCreditCardProcessor实现 
         bind(CreditCardProcessor.class).annotatedWith(Names.named("Checkout")).to(CheckoutCreditCardProcessor.class); 
        

实例绑定

将一个类型绑定到一个具体的实例而非实现类,这个通过是在无依赖的对象(比如值对象)中使用。如果toInstance包含复杂的逻辑会导致启动速度,此时应该通过@Provides方法绑定。

    
        
         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方法绑定

模块中定义的、带有@Provides注解的、方法返回值即为绑定映射的类型。

    
        
         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; 
         } 
         
     
         @Provides @PayPal 
         CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) { 
         PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor(); 
         processor.setApiKey(apiKey); 
         return processor; 
         } 
         } 
        

Provider绑定

如果使用@Provides方法绑定逻辑越来越复杂时就可以通过Provider绑定(一个实现了Provider接口的实现类)来实现。

    
        
         public interface Provider<T> { 
         T get(); 
         } 
         
     
         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; 
         } 
         } 
         
     
         public class BillingModule extends AbstractModule { 
         @Override 
         protected void configure() { 
         bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class); 
         } 
         } 
        

无目标绑定

当我们想提供对一个具体的类给注入器时就可以采用无目标绑定。

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

构造器绑定

3.0新增的绑定,适用于第三方提供的类或者是有多个构造器参与依赖注入。通过@Provides方法可以显式调用构造器,但是这种方式有一个限制:无法给这些实例应用AOP。

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

范围

默认情况下,Guice每次都会返回一个新的实例,这个可以通过范围(Scope)来配置。常见的范围有单例(@Singleton)、会话(@SessionScoped)和请求(@RequestScoped),另外还可以通过自定义的范围来扩展。

范围的注解可以应该在实现类、@Provides方法中,或在绑定的时候指定(优先级最高):

    
        
         @Singleton 
         public class InMemoryTransactionLog implements TransactionLog { 
         /* everything here should be threadsafe! */ 
         } 
         
     
         // scopes apply to the binding source, not the binding target 
         bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class); 
         
     
         @Provides @Singleton 
         TransactionLog provideTransactionLog() { 
         ... 
         } 
        

另外,Guice还有一种特殊的单例模式叫饥饿单例(相对于懒加载单例来说):

    
        
         // Eager singletons reveal initialization problems sooner, 
         // and ensure end-users get a consistent, snappy experience. 
         bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton(); 
        

注入

依赖注入的要求就是将行为和依赖分离,它建议将依赖注入而非通过工厂类的方法去查找。注入的方式通常有构造器注入、方法注入、属性注入等。

    
        
         // 构造器注入 
         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; 
         } 
         } 
         
     
         // 方法注入 
         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; 
         } 
         } 
         
     
         // 属性注入 
         public class DatabaseTransactionLogProvider implements Provider<TransactionLog> { 
         @Inject Connection connection; 
         
     
         public TransactionLog get() { 
         return new DatabaseTransactionLog(connection); 
         } 
         } 
         
     
         // 可选注入:当找不到映射时不报错 
         public class PayPalCreditCardProcessor implements CreditCardProcessor { 
         private static final String SANDBOX_API_KEY = "development-use-only"; 
         private String apiKey = SANDBOX_API_KEY; 
         
     
         @Inject(optional=true) 
         public void setApiKey(@Named("PayPal API key") String apiKey) { 
         this.apiKey = apiKey; 
         } 
         } 
        

辅助注入

辅助注入(Assisted Inject)属于Guice扩展的一部分,它通过@Assisted注解自动生成工厂来加强非注入参数的使用。

    
        
         // RealPayment中有两个参数startDate和amount无法直接注入 
         public class RealPayment implements Payment { 
         public RealPayment( 
         CreditService creditService, // from the Injector 
         AuthService authService, // from the Injector 
         Date startDate, // from the instance's creator 
         Money amount); // from the instance's creator 
         } 
         ... 
         } 
         
     
         // 一种方式是增加一个工厂来构造 
         public interface PaymentFactory { 
         public Payment create(Date startDate, Money amount); 
         } 
         
     
         public class RealPaymentFactory implements PaymentFactory { 
         private final Provider<CreditService> creditServiceProvider; 
         private final Provider<AuthService> authServiceProvider; 
         
     
         @Inject 
         public RealPaymentFactory(Provider<CreditService> creditServiceProvider, 
         Provider<AuthService> authServiceProvider) { 
         this.creditServiceProvider = creditServiceProvider; 
         this.authServiceProvider = authServiceProvider; 
         } 
         
     
         public Payment create(Date startDate, Money amount) { 
         return new RealPayment(creditServiceProvider.get(), 
         authServiceProvider.get(), startDate, amount); 
         } 
         } 
         
     
         bind(PaymentFactory.class).to(RealPaymentFactory.class); 
         
     
         // 通过@Assisted注解可以减少RealPaymentFactory 
         public class RealPayment implements Payment { 
         @Inject 
         public RealPayment( 
         CreditService creditService, 
         AuthService authService, 
         @Assisted Date startDate, 
         @Assisted Money amount); 
         } 
         ... 
         } 
         
     
         // Guice 2.0 
         //bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class)); 
         // Guice 3.0 
         install(new FactoryModuleBuilder().implement(Payment.class, RealPayment.class).build(PaymentFactory.class)); 
        

最佳实践

来源:https://sourl.cn/H4waaS

点赞(2)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Google 开源的依赖注入库,比 Spring 更小更快!

相关推荐