【Guice】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
- 与其他组件/解决方案整合:注意对象生命周期