⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 zhuanlan.zhihu.com/p/24924391 「GinoBeFunny」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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

学习目标

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

Guice概述

  • Guice是Google开源的依赖注入类库,通过Guice减少了对工厂方法和new的使用,使得代码更易交付、测试和重用;
  • Guice可以帮助我们更好地设计API,它是个轻量级非侵入式的类库;
  • 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));

最佳实践

  • 最小化可变性:尽可能注入的是不可变对象;
  • 只注入直接依赖:不用注入一个实例来获取真正需要的实例,增加复杂性且不易测试;
  • 避免循环依赖
  • 避免静态状态:静态状态和可测试性就是天敌;
  • 采用@Nullable:Guice默认情况下禁止注入null对象;
  • 模块的处理必须要快并且无副作用
  • 在Providers绑定中当心IO问题:因为Provider不检查异常、不支持超时、不支持重试;
  • 不用在模块中处理分支逻辑
  • 尽可能不要暴露构造器
文章目录
  1. 1. 学习目标
  2. 2. Guice概述
  3. 3. 快速开始
  4. 4. 绑定
    1. 4.1. 连接绑定
    2. 4.2. 注解绑定
    3. 4.3. 实例绑定
    4. 4.4. @Provides方法绑定
    5. 4.5. Provider绑定
    6. 4.6. 无目标绑定
    7. 4.7. 构造器绑定
  5. 5. 范围
  6. 6. 注入
  7. 7. 辅助注入
  8. 8. 最佳实践