轻量级依赖注入框架Google Guice(2)BIND

轻量级依赖注入框架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提供的几种重载。

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来说事,当一个类有多个实现的时候,可以精准的指定到采用哪个类来进行注入。

@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

GuiceDagger都是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完成的,而DaggerCompileTime完成了大部分工作。

GuiceModule 中放了很多表达式和语句,其实是放到了一个map中进行维系,并不会立刻执行。所以说起来整个流程花费时间最多的地方就在 getInstance方法的时候,此时会将所有的依赖、注入建立出来。

未完待续… 后面专题介绍Guice的AOP和Scope。


   转载规则


《轻量级依赖注入框架Google Guice(2)BIND》 Malroy 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
初识Metric(指标) 初识Metric(指标)
初识Metric(指标)一、Prometheus简介​ Prometheus受启发于Google的Brogmon监控系统(相似的Kubernetes是从Google的Brog系统演变而来),从2012年开始由前Google工程师在So
2023-03-08
下一篇 
轻量级依赖注入框架Google Guice(1)DI 轻量级依赖注入框架Google Guice(1)DI
轻量级依赖注入框架Google Guice(1)DI一、Google Guice简介​ Google Guice (读作”juice”)是超轻量级的,下一代的,为Java 5及后续版本设计的依赖注入容器。 它在连接对象、访问中间层等方
2022-08-02
  目录