【Guice】Guice入门

学习地址

官方指导文档

慕课网使用Google Guice实现依赖注入

注入

构造函数注入和成员变量注入

注意:在构造函数中注入的参数并不一定都必须在AbstractModule中绑定,Guice会每次新建一个实例注入

绑定(入口)-->Guice-->@Inject(出口)

主要使用构造函数注入,也可以给成员变量注入但是一般不用。使用@Inject构造器注入如下所示:

// 先绑定再注入
// 1、绑定
public class ServerModule extends AbstractModule {
	@Override
	protected void configure() {
	
		bind(OrderService.class).to(OrderServiceImpl.class);
		bind(PaymentService.class).to(PaymentServiceImpl.class);
		bind(PriceService.class).to(PriceServiceImpl.class);
}
// 2、注入
public class OrderServiceImpl implements OrderService {

	// Dependencies,使用构造器注入后不再改变,使用final修饰
	private final PriceService priceService;
	private final PaymentService paymentService;
	private final SessionManager sessionManager;
    private final NoBindService nobindService;

	// States 这种成员变量会被改变,不能添加final修饰
	private Long ordersPaid = 0L;

    // 下面的NoBindService并未在Module中绑定,但是是可以正常注入的,绑定和注入并不是强相关
	@Inject
	public OrderServiceImpl(PriceService priceService,
			PaymentService paymentService,
			SessionManager sessionManager, NoBindService nobindService) {
		super();
		this.priceService = priceService;
		this.paymentService = paymentService;
		this.sessionManager = sessionManager;
        this.nobindService = nobindService;
	}
}

构造函数注入:

  • 使用final来区分dependency和状态
  • 注入时不需要考虑如何实现/绑定

Provider注入

Guice 使用Provider 表示创建对象的工厂以满足依赖要求。Provider是只包含一个方法的接口:

interface Provider<T> {
  /** Provides an instance of T.**/
  T get();
}

应用场景

1、懒加载,如数据库连接比较耗时,可以在需要的时候通过Provider获取,而不是直接使用构造器注入指定

  • databaseConnectionProvider.get()

2、多个实例,使用构造器注入只能选择固定的日志输出器

  • logEntryProvider.get()

注入Provider方式

我们可以向Guice请求Provider,不论如何绑定(注入和绑定是完全分开的过程)

DataBaseConnection dbConn

-->Provider<DataBaseConnection> dbConnProvider

一般无需自己实现Provider,Guice已经提供,且会考虑生命对象周期,但是需要时可以自己实现Provider。

通过@Provides 绑定Provider之后,就可以通过其唯一的get方法获取想要的值。

// 先绑定,再注入
// 1、绑定
public class ServerModule extends AbstractModule {

	@Provides Long generateSesssionId() {
		return System.currentTimeMillis();
	}
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;

	@Inject
	public SessionManager(Provider<Long> sessionIdProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}

}

命名注入

场景,如果绑定了多个同类型的实例,如String等且未进行标识,则注入时就会发生冲突,此时需要使用命名的方式加以区分。

有以下两种注入形式:

@Inject @Named("dbSpec") private String dbSpec;
@Inject @LogFileName private String logFileName;

使用@Named

  • 参数来自配置文件,命令行
  • 为了开发方便

使用属性

  • 通常采用此方法

下面是示例,获取名为@SessionId的Long类型

//先定义注解、再绑定、最后注入
// 1、定义注解
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
public @interface SessionId {

}
// 2、绑定
public class ServerModule extends AbstractModule {
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
}
// 3、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;

	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}

}

绑定

结构

注入和绑定是分开的,

分类

一般通过继承AbstractModule,实现其configure方法进行绑定。

  • 类名绑定
  • 实例绑定 Instance Bindings
  • 连接绑定 Linked Bindings
  • Provider绑定 Provider Bindings
  • 命名绑定 Binding Annotations
  • 泛型绑定
  • 集合绑定

类名绑定和实例绑定

类名绑定是最常见的绑定方式,将类的实现绑定到接口类上;

实例绑定除了可以绑定具体类到它上,还可以直接使用new的实例绑定。


public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 类名绑定 将实现类OrderServiceImpl绑定到接口OrderService上
        // 表示需要OrderService时,注入OrderServiceImpl的一个实例
		bind(OrderService.class).to(OrderServiceImpl.class);
        // 实例绑定 直接将具体的实例绑定
        // 表示需要PriceService时,注入PriceServiceImpl实例
        bind(PriceService.class).toInstance(new PriceServiceImpl());

	}
}

连接绑定

连接绑定可以不断将子类绑定到父类(接口)上。可以是链式结构。下面的示例中,当请求PriceService时,最终会返回new 出来的PriceServiceImpl实例。可以按下面的绑定持续绑定,只要不形成环,最终Guice会找到最后绑定的值。

public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 类名绑定 将实现类PriceServiceImpl绑定到接口PriceService上
        // 表示需要PriceService时,注入PriceServiceImpl的一个实例
		bind(PriceService.class).to(PriceServiceImpl.class);
        // 实例绑定 直接将具体的实例绑定
        // 表示需要PriceServiceImpl时,注入PriceServiceImpl实例
        bind(PriceServiceImpl.class).toInstance(new PriceServiceImpl());
	}
}

Provider绑定

当你创建对象时,使用@Provides方法。该方法必须定义在一个module中,同时要使用@Provides注解。返回值是绑定类型。,当injector需要该类型实例时,会调用该方法。

方式一、在module中像上面的几种绑定一样绑定,然后直接使用Provider即可,如下示例

// 1、先绑定
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
        // 类名绑定 将实现类PriceServiceImpl绑定到接口PriceService上
        // 表示需要PriceService时,注入PriceServiceImpl的一个实例
				bind(PriceService.class).to(PriceServiceImpl.class);
	}
}
// 2、直接注入Provider
public class OrderServiceImpl implements OrderService {

	// Dependencies,使用构造器注入后不再改变,使用final修饰
	private final Provider<PriceService> priceServiceProvider;
	
	@Inject
	public OrderServiceImpl(Provider<PriceService> priceServiceProvider,) {
		super();
		this.priceServiceProvider = priceServiceProvider;
	}
}

方式二、使用@Provides注解,这种方式比较常用 ,可以带参数或不带参数

//1、先绑定(在AbstarctModule中)
public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
    bind(PriceService.class).to(PriceServiceImpl.class);
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
    // 可以带命名绑定
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
    // 可以带参数,Guice会自动注入参数,默认每次是个新的实例
	@Provides List<String>  getCurrencyList(PriceService priceService) {
		return priceService.getCurrencyList();
	}
    
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;
    // 不使用Provider包裹也可以直接注入List<String>
	private final Provider<List<String>> currencyListProvider;
	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider, Provider<List<String>> currencyListProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
        this.currencyList = currencyList;

	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}
    public List<String> getCurrencyList() {
		return currencyListProvider.get();
	}

}

当@Provides方法变得复杂,可以考虑将他们移动到自己的类中。provider类实现了Guice的Provider接口。

Provider的实现类包含它的所有依赖,通过使用@Inject注解构造器接收依赖参数。通过实现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;
  }
}

最后,通过.toProvider进行绑定,这样就可以在TransactionLog实例中获取DatabaseTransactionLogProvider。

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

命名绑定

当想要指定同一类型的不同绑定时需要使用可以指定名称。有两种方式:一是自定义注解命名,二是使用@Named注解

//1、先绑定(在AbstarctModule中)
public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
    bind(PriceService.class).to(PriceServiceImpl.class);
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
    // 可以带命名绑定
	@Provides @SessionId Long generateSesssionId() {
		return System.currentTimeMillis();
	}
    // 可以带参数
	@Provides @Named("currencyList") List<String>  getCurrencyList(PriceService priceService) {
		return priceService.getCurrencyList();
	}
    
}
// 2、注入
public class SessionManager {
	private final Provider<Long> sessionIdProvider;
    // 不使用Provider包裹也可以直接注入List<String>
	private final Provider<List<String>> currencyListProvider;
	@Inject
	public SessionManager(
			@SessionId Provider<Long> sessionIdProvider,  @Named("currencyList") Provider<List<String>> currencyListProvider) {
		super();
		this.sessionIdProvider = sessionIdProvider;
        this.currencyList = currencyList;

	}

	public Long getSessionId() {
		return sessionIdProvider.get();
	}
    public List<String> getCurrencyList() {
		return currencyListProvider.get();
	}

}

如果不通过@Provides添加命名绑定@SessionId配置也可以通过在module的configure的bind方式绑定,但是这种方式一旦实例化就不会改变,注意与@Provides方式的区别。

public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
        bind(Long.class).annotateWith(SessionId.class).toInstance(System.currentTimeMillis());
 bind(Long.class).annotateWith(Nameds.named("appId")).toInstance(123L);
  }
}

泛型绑定

public class ServerModule extends AbstractModule {
    
    @Override
  protected void configure() {
      // 也可以添加命名绑定,使用annotateWith
        bind(new TypeLiteral<List<String>>(){}).toInstance(Arrays.asList("CNY","USD"));
  }
}

集合绑定

上面泛型绑定的例子也是集合绑定,集合绑定还可以使用Multibinder在不同的module中配置到同一个集合中。

// 1、不同的module中使用Multibinder绑定set集合
public class GlobalModule extends AbstractModule {

	@Override
	protected void configure() {
		// Adds EUR, USD support
        // 此处也可以不单独提取currencyBinder,直接使用Multibinder.newSetBinder(binder(), String.class).addBinding...的形式,也是可以根据类型获取到set<String>
		Multibinder<String> currencyBinder =
				Multibinder.newSetBinder(binder(), String.class);
		currencyBinder.addBinding().toInstance("EUR");
		currencyBinder.addBinding().toInstance("USD");
	}

}
public class ChinaModule extends AbstractModule {

	@Override
	protected void configure() {
		// Adds CNY support
		Multibinder.newSetBinder(binder(), String.class)
			.addBinding().toInstance("CNY");

	}

}
// 组合 module
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		install(new ChinaModule());
		install(new GlobalModule());
	}
}
// 2、注入Set
public class PriceServiceImpl implements PriceService {
	private final Set<String> supportedCurrencies;
	@Inject
	public PriceServiceImpl(
			Set<String> supportedCurrencies) {
		super();
		this.supportedCurrencies = supportedCurrencies;

	}

	@Override
	public Set<String> getSupportedCurrencies() {
		return supportedCurrencies;
	}
}

Module的组织

Module主要有以下三种关系

1、并列

Guice,createInjector(module1,module2,...)

2、嵌套

通过如下语句将一个module嵌套另一个module中

install(module)

3、覆盖

一个module中的绑定可能覆盖另一个module的绑定。下面的意思是如果遇到冲突绑定,则用后面module的配置

Modules.override(module1).with(module2)

Guice的启动

Guice通过如下方式启动。

Injector injector = Guice.createInjector(new ServerModule());

OrderService orderService = injector.getInstance(OrderService.class);

Module中配置的各种bind是何时被运行的?

  • Module里存放了很多表达式,启动时并不会将bind的实例初始化
  • Module不被”运行“
  • Guice.createInjector()时记录所有表达式

系统何时初始化?

  • Guice中没有”初始化“概念,没有Spring 的Configuration Time
  • injector.getInstance()时根据表达式调用构造函数

因此在执行 Guice.createInjector时会比较快,真正花时间的是调用injector.getInstance()方法调用构造函数,要善于使用Provider,将比较耗时的操作使用Provider进行懒加载,即使用时再调用。

生命周期/作用域

如果不显式指定Scope,Guice默认为每次注入生成新的实例。

但是像数据库连接实例,通常只需要一个,需要显式配置其为Singleton。

作用域分类

默认作用域

一般实例,无状态,构造速度快,通常使用默认即可

Singleton作用域

  • 有状态的实例
  • 构造速度慢的实例
  • 需要线程安全的实例
  • 如:数据库,网络连接

Session/Request作用域

  • 含有session/request信息的实例
  • 有状态的实例
  • 如SessionState等

绑定方式

有三种方式配置作用域,以下三种选其一即可。

方式一、可以通过在类上添加@Singleton注解声明作用域为单例,其他两种类似@SessionScoped/@RequestScoped,后两种需要servlet环境

@Singleton
public class SingletonDemo{}

方式二、通过在module的configure方法的bind中,使用in语句

public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		
		// 绑定map
			bind(new TypeLiteral<Cache<String, String>>(){})
			.to(GuiceDemoCache.class).in(Singleton.class);
	}
}

方式三、通过@Provides方法属性指定

@Provides
@Singleton
Cache<String,String> getCache(){
    return new GuiceDemoCache();
}

实例

// 1、类定义
@Singleton
public class GuiceDemoCache
	extends AbstractCache<String, String> {

	private final Map<String, String> keyValues =
			new ConcurrentHashMap<>();

	@Override
	public String getIfPresent(Object key) {
		return keyValues.get(key);
	}

	@Override
	public void put(String key, String value) {
		keyValues.put(key, value);
	}
}
// 2、绑定
public class ServerModule extends AbstractModule {

	@Override
	protected void configure() {
		install(new ChinaModule());
		install(new GlobalModule());

		bind(OrderService.class).to(OrderServiceImpl.class);
		bind(PaymentService.class).to(PaymentServiceImpl.class);
		bind(PriceService.class).to(PriceServiceImpl.class);
		// 绑定map
		bind(new TypeLiteral<Cache<String, String>>(){})
			.to(GuiceDemoCache.class);

	}

}
// 3、注入
public class PaymentServiceImpl implements PaymentService {
	private final Cache<String, String> cache;

	@Inject
	public PaymentServiceImpl(Cache<String, String> cache) {
		super();
		this.cache = cache;
	}

	@Override @Logged
	public void pay(long orderId, long price, Long sessionId) {
		// TODO Auto-generated method stub
	}

	void putCache(String key, String value) {
		cache.put(key, value);
	}
}

public class PriceServiceImpl implements PriceService {
	private final Set<String> supportedCurrencies;
	private final Cache<String, String> cache;

	@Inject
	public PriceServiceImpl(
			Set<String> supportedCurrencies,
			Cache<String, String> cache) {
		super();
		this.supportedCurrencies = supportedCurrencies;
		this.cache = cache;
	}

	@Override
	public Set<String> getSupportedCurrencies() {
		return supportedCurrencies;
	}

	@Override
	public long getPrice(long orderId) {
		throw new UnsupportedOperationException();
	}

	String getCachedValue(String key) {
		return cache.getIfPresent(key);
	}
}

AOP

直接学习视频 Guice AOP

Guice vs Spring

1、Guice不是Spring的重制版本

2、Spring绑定

  • 配置文件体现完整装配结构
  • 大量使用字符串:实例名:属性名
  • 在Config Time完成组装

3、Guice绑定

  • Java代码描述绑定规则
  • 每个注入/绑定处仅描述局部依赖
  • 没有Config Time

4、Guice的优点

  • 代码量少
  • 性能优异
  • 支持泛型
  • Constructor绑定:Immutable objects,不再需要getter/setter
  • 强类型
  • 易于重构

5、Guice的缺点

  • Module和绑定规则不易于理解
  • 文档教程少,社区资源少
  • 无法方便搭出特殊结构:如循环依赖
  • Guice仅仅是依赖注入框架,而Spring更重量级,涵盖更多

6、从Spring 迁移到Guice?

  • 不建议这么做
  • Spring 与Guice整合,两者可以兼容

7、新项目需要选择依赖注入方案?

  • 可以尝试Guice
  • 与其他组件/解决方案整合:注意对象生命周期
本博客用于技术学习,所有资源都来源于网络,部分是转发,部分是个人总结。欢迎共同学习和转载,转载请在醒目位置标明原文。如有侵权,请留言告知,及时撤除。
posted @ 2020-11-27 09:19  风动静泉  阅读(566)  评论(0编辑  收藏  举报