乐趣区

关于后端:quarkus依赖注入之四选择注入bean的高级手段

欢送拜访我的 GitHub

这里分类和汇总了欣宸的全副原创 (含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《quarkus 依赖注入》系列的第四篇,在利用中,一个接口有多个实现是很常见的,那么依赖注入时,如果类型是接口,如何精确抉择实现呢?前文介绍了五种注解,用于通过配置项、profile 等伎俩抉择注入接口的实现类,面对复杂多变的业务场景,有时候仅靠这两种伎俩是不够的,最好是有更自在灵便的形式来抉择 bean,这就是本篇的内容,通过注解、编码等更多形式抉择 bean
  • 本篇波及的抉择 bean 的伎俩有以下四种:
  1. 修饰符匹配
  2. Named 注解的属性匹配
  3. 依据优先级抉择
  4. 写代码抉择

对于修饰符匹配

  • 为了阐明修饰符匹配,先来看一个注解 <font color=”blue”>Default</font>,其源码如下
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
@Qualifier
public @interface Default {
    public static final class Literal extends AnnotationLiteral<Default> implements Default {public static final Literal INSTANCE = new Literal();
            private static final long serialVersionUID = 1L;
    }
}
  • Default 的源码在这里不重要,要害是它被注解 <font color=”blue”>Qualifier</font> 润饰了,这种被 <font color=”blue”>Qualifier</font> 润饰的注解,咱们权且称之为 <font color=”red”>Qualifier 修饰符 </font>
  • 如果咱们新建一个注解,也用 <font color=”blue”>Qualifier</font> 来润饰,如下所示,这个 MyQualifier 也是个 <font color=”red”>Qualifier 修饰符 </font>
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {@Nonbinding String value();
}
  • 在 quarkus 容器中的每一个 bean 都应该有一个 <font color=”red”>Qualifier 修饰符 </font> 在润饰,如下图红框,如果没有,就会被 quarkus 增加 <font color=”blue”>Default</font> 注解
  • 依赖注入时,间接用 <font color=”red”>Qualifier 修饰符 </font> 润饰注入对象,这样 quarkus 就会去寻找被这个 <font color=”red”>Qualifier 修饰符 </font> 润饰的 bean,找到就注入(找不到报错,找到多个也报错,谬误逻辑和之前的一样)
  • 所以用 <font color=”blue”> 修饰符匹配 </font> 来抉择 bean 的实现类,一共分三步:
  1. 假如有名为 <font color=”blue”>HelloQualifier</font> 的接口,有三个实现类:HelloQualifierA、HelloQualifierB、HelloQualifierC,业务需要是应用 HelloQualifierA
  2. 第一步:自定义一个注解,假如名为 <font color=”blue”>MyQualifier</font>,此注解要被 <font color=”red”>Qualifier</font> 润饰
  3. 第二步:用 <font color=”blue”>MyQualifier</font> 润饰 HelloQualifierA
  4. 第三步:在业务代码的注入点,用 <font color=”blue”>MyQualifier</font> 润饰 HelloQualifier 类型的成员变量,这样成员变量就会被注入 HelloQualifierA 实例
  • 仅凭文字描述,很难把信息精确传递给读者(毕竟欣宸文化程度极其无限),还是写代码实现上述场景吧,聪慧的您一看就懂

编码演示修饰符匹配:筹备工作

  • 先依照后面的假如将接口和实现类筹备好,造成一个接口有多个实现 bean 的事实,而后,再用修饰符匹配来精确选定 bean
  • 首先是接口 <font color=”blue”>HelloQualifier</font>,如下所示
package com.bolingcavalry.service;

public interface HelloQualifier {String hello();
}
  • 实现类 <font color=”blue”>HelloQualifierA</font>,返回本人的类名
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloQualifier;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloQualifierA implements HelloQualifier {
    @Override
    public String hello() {return this.getClass().getSimpleName();}
}
  • 实现类 HelloQualifierB、HelloQualifierC 的代码和下面的 HelloQualifierA 雷同,都是返回本人类名,就不贴出来了
  • 对于应用 HelloQualifier 类型 bean 的代码,咱们就在单元测试类中注入吧,如下所示:
package com.bolingcavalry;

import com.bolingcavalry.service.HelloQualifier;
import com.bolingcavalry.service.impl.HelloQualifierA;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;

@QuarkusTest
public class QualifierTest {

    @Inject
    HelloQualifier helloQualifier;

    @Test
    public void testQualifier() {Assertions.assertEquals(HelloQualifierA.class.getSimpleName(),
                helloQualifier.hello());
    }
}
  • 下面的代码中,成员变量 <font color=”blue”>helloQualifier</font> 的类型是 HelloQualifier,quarkus 的 bean 容器中,HelloQualifierA、HelloQualifierB、HelloQualifierC 等三个 bean 都合乎注入要求,此时如果执行单元测试,应该会报错:同一个接口多个实现 bean 的问题
  • 执行单元测试,如下图,黄框中给出了两个线索:第一,谬误起因是注入时发现同一个接口有多个实现 bean,第二,这些 bean 都是用 <font color=”blue”>Default</font> 润饰的,而后是绿框,外面将所有实现 bean 列出来,不便开发者定位问题
  • 当初筹备工作实现了,来看如何用修饰符匹配解决问题:<font color=”blue”> 在注入点精确注入 HelloQualifierA 类型实例 </font>

编码演示修饰符匹配:实现匹配

  • 应用修饰符匹配,持续依照后面总结的三步走
  • 第一步:自定义一个注解,名为 <font color=”blue”>MyQualifier</font>,此注解要被 <font color=”red”>Qualifier</font> 润饰
package com.bolingcavalry.annonation;

import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {@Nonbinding String value();
}
  • 第二步:用 <font color=”blue”>MyQualifier</font> 润饰 HelloQualifierA,下图红框是新增的代码
  • 第三步:在业务代码的注入点,用 <font color=”blue”>MyQualifier</font> 润饰 HelloQualifier 类型的成员变量,下图红框是新增的代码
  • 改变实现了,再次执行单元测试,顺利通过

修饰符匹配要留神的中央

  • 修饰符匹配的逻辑非常简单:bean 定义和 bean 注入的中央用同一个修饰符即可,应用中有三个中央要留神
  • 在注入 bean 的中央,如果有了 <font color=”red”>Qualifier 修饰符 </font>,能够把 <font color=”blue”>@Inject</font> 省略不写了
  • 在定义 bean 的中央,如果没有 <font color=”red”>Qualifier 修饰符 </font> 去润饰 bean,quarkus 会默认增加 <font color=”blue”>Default</font>
  • 在注入 bean 的中央,如果没有 <font color=”red”>Qualifier 修饰符 </font> 去润饰 bean,quarkus 会默认增加 <font color=”blue”>Default</font>

对于默认的 @Default

  • 回头看方才的代码,如果保留 HelloQualifierA 的 <font color=”blue”>MyQualifier</font> 润饰,然而删除 QualifierTest 的成员变量 helloQualifier 的 <font color=”blue”>MyQualifier</font> 润饰,会产生什么呢?咱们来剖析一下:
  • 首先,QualifierTest 的成员变量 helloQualifier 会被 quarkus 默认增加 <font color=”blue”>Default</font> 润饰
  • 其次,HelloQualifierB 和 HelloQualifierC 都会被 quarkus 默认增加 <font color=”blue”>Default</font> 润饰
  • 所以,注入 helloQualifier 的时候,quarkus 去找 <font color=”blue”>Default</font> 润饰的 bean,后果找到了两个:HelloQualifierB 和 HelloQualifierC,因而启动会失败
  • 您能够自行验证后果是否和预期统一
  • 看到这里,您应该把握了修饰符匹配的用法,也应该发现其不便之处:要新增注解,这样上来随着业务倒退,注解会越来越多,有没有什么办法来解决这个问题呢?
  • 办法是有的,就是接下来要看的 <font color=”blue”>Named</font> 注解

Named 注解的属性匹配

  • Named 注解的性能与后面的 <font color=”red”>Qualifier 修饰符 </font> 是一样的,其非凡之处在于通过注解属性来匹配润饰 bean 和注入 bean
  • 以方才的业务代码为例来演示 Named 注解,批改 <font color=”blue”>HelloQualifierA</font>,如下图红框,将 <font color=”blue”>@MyQualifier(“”)</font> 换成 <font color=”red”>@Named(“A”)</font>,重点关注 Named 注解的属性值,这里等于 <font color=”red”>A</font>
  • 接下来批改注入处的代码,如下图红框,在注入地位也用 <font color=”red”>@Named(“A”)</font> 来润饰,和 bean 定义处的截然不同
  • 如此,bean 定义和 bean 注入的两个中央,通过 Named 注解的属性实现了匹配,至于单元测试您能够自行验证,这里就不赘述了
  • 至此,具体您曾经晓得了 Named 注解的作用:性能与后面的 <font color=”red”>Qualifier 修饰符 </font> 一样,不过 bean 的定义和注入处的匹配逻辑是 Named 注解的属性值
  • 以上就是修饰符匹配的全部内容

依据优先级抉择

  • 应用优先级来抉择注入是一种简洁的形式,其外围是用 <font color=”blue”>Alternative</font> 和 <font color=”blue”>Priority</font> 两个注解润饰所有备选 bean,而后用 Priority 的属性值(int 型)作为优先级,该值越大代表优先级越高
  • 在注入地位,quarkus 会抉择优先级最高的 bean 注入
  • 接下来编码演示
  • 新增演示用的接口 HelloPriority.java
public interface HelloPriority {String hello();
}
  • HelloPriority 的第一个实现类 HelloPriorityA.java,留神它的两个注解 <font color=”blue”>Alternative</font> 和 <font color=”blue”>Priority</font>,前者表明这是个可供选择的 bean,后者表明了它的优先级,数字 1001 用于和其余 bean 的优先级比拟,数字越大优先级越高
@ApplicationScoped
@Alternative
@Priority(1001)
public class HelloPriorityA implements HelloPriority {
    @Override
    public String hello() {return this.getClass().getSimpleName();}
}
  • HelloPriority 的第二个实现类 HelloPriorityB,可见 Priority 属性值是 <font color=”blue”>1002</font>,代表抉择的时候优先级比 HelloPriorityA 更高
@ApplicationScoped
@Alternative
@Priority(1002)
public class HelloPriorityB implements HelloPriority {
    @Override
    public String hello() {return this.getClass().getSimpleName();}
}
  • HelloPriority 的第二个实现类 HelloPriorityC,可见 Priority 属性值是 <font color=”blue”>1003</font>,代表抉择的时候优先级比 HelloPriorityA 和 HelloPriorityB 更高
@ApplicationScoped
@Alternative
@Priority(1003)
public class HelloPriorityC implements HelloPriority {
    @Override
    public String hello() {return this.getClass().getSimpleName();}
}
  • 接下来是单元测试,验证注入的 bean 是否合乎预期,实践上注入的应该是优先级最高的 <font color=”red”>HelloPriorityC</font>
@QuarkusTest
public class PriorityTest {

    @Inject
    HelloPriority helloPriority;

    @Test
    public void testSelectHelloInstanceA() {Assertions.assertEquals(HelloPriorityC.class.getSimpleName(),
                                helloPriority.hello());
    }
}
  • 单元测试后果如下,合乎预期
  • 以上就是优先级抉择 bean 的操作,如果这还不够用,那就祭出最初一招:写代码抉择 bean

写代码抉择 bean

  • 如果不必修饰符匹配,再回到最后的问题:有三个 bean 都实现了同一个接口,应该如何注入?
  • 在注入 bean 的地位,如果用 <font color=”blue”>Instance\<T></font> 来接管注入,就能够拿到 T 类型的所有 bean,而后在代码中得心应手的应用这些 bean
  • 新增演示用的接口 HelloInstance.java
package com.bolingcavalry.service;

public interface HelloInstance {String hello();
}
  • HelloInstance 的第一个实现类 HelloInstanceA.java
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloInstance;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloInstanceA implements HelloInstance {
    @Override
    public String hello() {return this.getClass().getSimpleName();}
}
  • HelloInstance 的另外两个实现类 HelloInstanceB、HelloInstanceC,代码与 HelloInstanceA 一样,就不贴出来了
  • 接下来的单元测试类演示了如何应用 Instance 承受注入,以及业务代码如何应用指定的实现类 bean,可见 <font color=”blue”>select(Class).get()</font> 是要害,select 办法指定了实现类,而后 get 取出该实例
package com.bolingcavalry;

import com.bolingcavalry.service.HelloInstance;
import com.bolingcavalry.service.impl.HelloInstanceA;
import com.bolingcavalry.service.impl.HelloInstanceB;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@QuarkusTest
public class InstanceTest {

    @Inject
    Instance<HelloInstance> instance;

    @Test
    public void testSelectHelloInstanceA() {
        Class<HelloInstanceA> clazz = HelloInstanceA.class;

        Assertions.assertEquals(clazz.getSimpleName(),
                instance.select(clazz).get().hello());
    }

    @Test
    public void testSelectHelloInstanceB() {
        Class<HelloInstanceB> clazz = HelloInstanceB.class;

        Assertions.assertEquals(clazz.getSimpleName(),
                instance.select(clazz).get().hello());
    }
}
  • 执行单元测试,顺利通过,合乎预期
  • 至此,间断两篇对于注入 bean 的形式全副验证结束,如此丰盛的伎俩,置信能够满足您日常开发的须要

    欢送关注思否:程序员欣宸

学习路上,你不孤独,欣宸原创一路相伴 …

退出移动版