欢送拜访我的 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
@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 的实现类,一共分三步:
- 假如有名为 <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;
@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 的形式全副验证结束,如此丰盛的伎俩,置信能够满足您日常开发的须要
欢送关注思否:程序员欣宸
学习路上,你不孤独,欣宸原创一路相伴 …