轻量级依赖注入框架Google Guice(2)BIND
一、前言
前面介绍了Google Guice
的注入,跟它相辅相成的就是绑定。绑定的话,可以说是相当的重要,因为就是先有绑定这一个操作,然后才能进行注入。详细的绑定图解如下:
就像图中所述,多个模块Module可以并列、组合和嵌套。最终完成绑定,然后进行注入。
具体的绑定方法在下面逐一介绍。
二、常见的几种绑定方式
1. 类名绑定
通过传入类名进行绑定,最常见的绑定方式,类似于spring的@Autowired
。
// 最常见的绑定方式,类似于spring的@Autowired
bind(PriceService.class).to(PriceServiceImpl.class);
2. 实例绑定、常量绑定( Constant Bindings)
Guice
提供了一种使用值对象或常量创建绑定的方法,或者直接绑定到某个具体的实例。
// 绑定到某个具体的实例
bind(PriceService.class).to(new PriceServiceImpl());
// 绑定到某个具体的数值
bind(Long.class).toInstance(1234L);
3. 链接绑定( Linked binding)
在链接绑定中,Guice
将类型映射到其实现。
// 最常见的绑定方式,类似于spring的@Autowired
bind(PriceService.class).to(PriceServiceImpl.class);
// 我们还可以将具体类映射到它的子类。 见下面的例子
bind(PriceServiceImpl.class).to(PriceServiceMock.class);
// 还可以构建匿名对象
bind(PriceServiceImpl.class).toInstance(new PriceServiceImpl() {
@Override
public long getPrice(long orderId) {
return 1234L;
}
});
4. @Provides Annotation
通过@Provides
注解进行绑定,前面也提到了相关的用法。
@Provides
@Named("getSupported")
List<String> generateSupportedCurrencies() {
// 通过Provides进行绑定
return Lists.newArrayList("HJJ", "HYC");
}
当然也可以使用我们前面案例提供的方式。
// 绑定到某个生成函数
bind(Long.class).toProvider(() -> 1234L);
下面是toProvider
提供的几种重载。
当然绑定的时候也可以传入参数,如果参数前面已经注入,则不需要再重复注入,如下。
@Provides Long generateSessionId(PriceService priceService){
return priceService.getPrice();
}
5. @Named binding
命名绑定,和命名注入是一样的逻辑。
@Provides
@Named("getSupported")
List<String> generateSupportedCurrencies() {
// 通过Provides + Named进行绑定
return Lists.newArrayList("HJJ", "HYC");
}
// 这个是直接绑定的例子
bind(Long.class).annotatedWith(Names.named("price")).toProvider(() -> 1234L);
当然和命名注入一样,可以自定义注解
,这里也不再赘述了。
6. 泛型的绑定
泛型的绑定需要用到TypeLiteral
,下述例子进行绑定泛型列表的成员变量。
// 绑定泛型列表
bind(new TypeLiteral<List<String>>(){})
.annotatedWith(Names.named("getSupported"))
.toInstance(Arrays.asList("CNY","ENR","USD"));
7. 集合Set绑定
集合的绑定将用到前面导入的包guice-multibindings
来进行实现。
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
<version>${guice.version}</version>
</dependency>
使用的话则是通过Multibinder
来进行对象构建。
// 绑定相关实例
Multibinder.newSetBinder(binder(), String.class)
.addBinding().toInstance("HJJ");
Multibinder.newSetBinder(binder(), String.class)
.addBinding().toInstance("HYC");
8. Map的绑定
Map的绑定是通过MapBinder
来进行构建的,跟set比较类似,通过不同的binder
进行构造。
// 跟set比较类似,通过不同的binder进行构造
MapBinder.newMapBinder(binder(), keyType, valueType)
9. 即时绑定( Just-in-time Bindings)
由于绑定是在绑定模块中定义的,因此只要需要注入依赖关系,Guice就会使用它们。 如果不存在绑定,它可以尝试创建即时绑定。 绑定模块中存在的绑定称为显式绑定,具有更高的优先级,而即时绑定称为隐式绑定。 如果存在两种类型的绑定,则考虑使用显式绑定进行映射。
以下是三种即时绑定的示例。
就拿@ImplementedBy
来说事,当一个类有多个实现的时候,可以精准的指定到采用哪个类来进行注入。
@ProvidedBy
是同样的道理,标注其提供者,就不多赘述了。
值得注意的是,使用了@ImplementedBy
就不需要使用bind()
语句了。 类似@ImplementedBy
注解,如果某个类型既使用了bind()
语句,又使用了@ProvidedBy
注解,那么bind()
语句优先。
三、Module之间的关系
创建一个Injector
可以使用多个任意多个Module
,因此需要明确它们的关系。 我们初步将其分为3种关系。
1. 并列:默认顺序传递就是此关系
// 可变参数,可以放很多个模块
Guice.createInjector(new MainModule(), .......);
2. 嵌套:大的Module可以嵌套任意多个子Module
public class ServerModule extends AbstractModule {
@Override
protected void configure() {
install(new MainModule());
}
}
值得注意的是,可以嵌套多个模块,然而主模块里面的成员会是其中所以模块的并集。例如下述情况:
A模块:
public class ChinaModule extends AbstractModule {
@Override
public void configure() {
// TODO suit China Yuan
Multibinder.newSetBinder(binder(), String.class)
.addBinding().toInstance("CNY");
}
}
B模块:
public class GlobalModule extends AbstractModule {
@Override
public void configure() {
// TODO USD,ENY
Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
multibinder.addBinding().toInstance("USD");
multibinder.addBinding().toInstance("ENY");
}
}
主模块:
public class ServerModule extends AbstractModule {
@Override
public void configure() {
// 加载其他Module
install(new ChinaModule());
install(new GlobalModule());
}
}
由此可见,主模块
加载了A和B两个模块,最终输出的变量结果是 USD、ENY、CNY。
这样灵活的装配模式,也是值得我们做低代码开发平台
的学习。
3. 覆盖:如果有冲突的话后者覆盖前者,没有的话就都生效
当需要优先使用后者时,可以采用模块覆盖
的形式。
相关细节以及完整代码,如下
// 用后者覆盖前者
Module finalModule = Modules.override(new MainModule()).with(new ServerModule());
四、Guice和Dagger2
Guice
和Dagger
都是Java的依赖注入框架,他们有很多相似性,所以放到一起比较一下:
相同点:
- 基于Java
- 由Google维护(Dagger最早是Square开发的,Dagger2已经过继给了Google)
- 兼容JSR-330注解规范
- 因为兼容JSR-330,所以需要修改源码添加注解实现注入,相对于Spring通过外部配置文件的方式对源码有侵入性
不同点
Guice历史更悠久,早在JSR-330之前就诞生并影响了JSR-330标准的制定,Dagger是在JSR-330之后出现的
Guice在运行时通过反射创建依赖;Dagger在编译期提前生成依赖创建的代码
Dagger比较适合在Android上使用,因为移动平台对性能更敏感,希望反射越少越好
Dagger的API更简单,stacetrace更友好
通过对比可见,最主要区别在于Guice
的依赖注入是Runtime
完成的,而Dagger
是CompileTime
完成了大部分工作。
Guice
在 Module
中放了很多表达式和语句,其实是放到了一个map中进行维系,并不会立刻执行。所以说起来整个流程花费时间最多的地方就在 getInstance
方法的时候,此时会将所有的依赖、注入建立出来。
未完待续… 后面专题介绍Guice的AOP和Scope。