欢送拜访我的GitHub
这里分类和汇总了欣宸的全副原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《quarkus依赖注入》系列的第四篇,在利用中,一个接口有多个实现是很常见的,那么依赖注入时,如果类型是接口,如何精确抉择实现呢?前文介绍了五种注解,用于通过配置项、profile等伎俩抉择注入接口的实现类,面对复杂多变的业务场景,有时候仅靠这两种伎俩是不够的,最好是有更自在灵便的形式来抉择bean,这就是本篇的内容,通过注解、编码等更多形式抉择bean
- 本篇波及的抉择bean的伎俩有以下四种:
- 修饰符匹配
- Named注解的属性匹配
- 依据优先级抉择
- 写代码抉择
对于修饰符匹配
- 为了阐明修饰符匹配,先来看一个注解<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的实现类,一共分三步:
- 假如有名为<font color="blue">HelloQualifier</font>的接口,有三个实现类:HelloQualifierA、HelloQualifierB、HelloQualifierC,业务需要是应用HelloQualifierA
- 第一步:自定义一个注解,假如名为<font color="blue">MyQualifier</font>,此注解要被<font color="red">Qualifier</font>润饰
- 第二步:用<font color="blue">MyQualifier</font>润饰HelloQualifierA
- 第三步:在业务代码的注入点,用<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的形式全副验证结束,如此丰盛的伎俩,置信能够满足您日常开发的须要
欢送关注思否:程序员欣宸
学习路上,你不孤独,欣宸原创一路相伴...