欢送拜访我的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@Qualifierpublic @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;@ApplicationScopedpublic 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;@QuarkusTestpublic 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>
@QuarkusTestpublic 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;@ApplicationScopedpublic 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;@QuarkusTestpublic 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的形式全副验证结束,如此丰盛的伎俩,置信能够满足您日常开发的须要

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

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