欢迎访问我的GitHub

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

本篇概览

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

关于修饰符匹配

  • 为了说明修饰符匹配,先来看一个注解Default,其源码如下
@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的源码在这里不重要,关键是它被注解Qualifier修饰了,这种被Qualifier修饰的注解,咱们姑且称之为Qualifier修饰符
  • 如果咱们新建一个注解,也用Qualifier来修饰,如下所示,这个MyQualifier也是个Qualifier修饰符
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {
@Nonbinding String value();
}
  • 在quarkus容器中的每一个bean都应该有一个Qualifier修饰符在修饰,如下图红框,如果没有,就会被quarkus添加Default注解

  • 依赖注入时,直接用Qualifier修饰符修饰注入对象,这样quarkus就会去寻找被这个Qualifier修饰符修饰的bean,找到就注入(找不到报错,找到多个也报错,错误逻辑和之前的一样)
  • 所以用修饰符匹配来选择bean的实现类,一共分三步:
  1. 假设有名为HelloQualifier的接口,有三个实现类:HelloQualifierA、HelloQualifierB、HelloQualifierC,业务需求是使用HelloQualifierA
  2. 第一步:自定义一个注解,假设名为MyQualifier,此注解要被Qualifier修饰
  3. 第二步:用MyQualifier修饰HelloQualifierA
  4. 第三步:在业务代码的注入点,用MyQualifier修饰HelloQualifier类型的成员变量,这样成员变量就会被注入HelloQualifierA实例
  • 仅凭文字描述,很难把信息准确传递给读者(毕竟欣宸文化水平极其有限),还是写代码实现上述场景吧,聪明的您一看就懂

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

  • 先按照前面的假设将接口和实现类准备好,造成一个接口有多个实现bean的事实,然后,再用修饰符匹配来准确选定bean

  • 首先是接口HelloQualifier,如下所示

package com.bolingcavalry.service;

public interface HelloQualifier {
String hello();
}
  • 实现类HelloQualifierA,返回自己的类名
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());
}
}
  • 上面的代码中,成员变量helloQualifier的类型是HelloQualifier,quarkus的bean容器中,HelloQualifierA、HelloQualifierB、HelloQualifierC等三个bean都符合注入要求,此时如果执行单元测试,应该会报错:同一个接口多个实现bean的问题
  • 执行单元测试,如下图,黄框中给出了两个线索:第一,错误原因是注入时发现同一个接口有多个实现bean,第二,这些bean都是用Default修饰的,然后是绿框,里面将所有实现bean列出来,方便开发者定位问题

  • 现在准备工作完成了,来看如何用修饰符匹配解决问题:在注入点准确注入HelloQualifierA类型实例

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

  • 使用修饰符匹配,继续按照前面总结的三步走
  • 第一步:自定义一个注解,名为MyQualifier,此注解要被Qualifier修饰
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();
}
  • 第二步:用MyQualifier修饰HelloQualifierA,下图红框是新增的代码

  • 第三步:在业务代码的注入点,用MyQualifier修饰HelloQualifier类型的成员变量,下图红框是新增的代码

  • 改动完成了,再次执行单元测试,顺利通过

修饰符匹配要注意的地方

  • 修饰符匹配的逻辑非常简单:bean定义和bean注入的地方用同一个修饰符即可,使用中有三个地方要注意
  1. 在注入bean的地方,如果有了Qualifier修饰符,可以把@Inject省略不写了
  2. 在定义bean的地方,如果没有Qualifier修饰符去修饰bean,quarkus会默认添加Default
  3. 在注入bean的地方,如果没有Qualifier修饰符去修饰bean,quarkus会默认添加Default

关于默认的@Default

  • 回头看刚才的代码,如果保留HelloQualifierA的MyQualifier修饰,但是删除QualifierTest的成员变量helloQualifier的MyQualifier修饰,会发生什么呢?咱们来分析一下:

  • 首先,QualifierTest的成员变量helloQualifier会被quarkus默认添加Default修饰

  • 其次,HelloQualifierB和HelloQualifierC都会被quarkus默认添加Default修饰

  • 所以,注入helloQualifier的时候,quarkus去找Default修饰的bean,结果找到了两个:HelloQualifierB和HelloQualifierC,因此启动会失败

  • 您可以自行验证结果是否和预期一致

  • 看到这里,您应该掌握了修饰符匹配的用法,也应该发现其不便之处:要新增注解,这样下去随着业务发展,注解会越来越多,有没有什么方法来解决这个问题呢?

  • 方法是有的,就是接下来要看的Named注解

Named注解的属性匹配

  • Named注解的功能与前面的Qualifier修饰符是一样的,其特殊之处在于通过注解属性来匹配修饰bean和注入bean

  • 以刚才的业务代码为例来演示Named注解,修改HelloQualifierA,如下图红框,将@MyQualifier("")换成@Named("A"),重点关注Named注解的属性值,这里等于A

  • 接下来修改注入处的代码,如下图红框,在注入位置也用@Named("A")来修饰,和bean定义处的一模一样

  • 如此,bean定义和bean注入的两个地方,通过Named注解的属性完成了匹配,至于单元测试您可以自行验证,这里就不赘述了

  • 至此,详细您已经知道了Named注解的作用:功能与前面的Qualifier修饰符一样,不过bean的定义和注入处的匹配逻辑是Named注解的属性值

  • 以上就是修饰符匹配的全部内容

根据优先级选择

  • 使用优先级来选择注入是一种简洁的方式,其核心是用Alternative和Priority两个注解修饰所有备选bean,然后用Priority的属性值(int型)作为优先级,该值越大代表优先级越高

  • 在注入位置,quarkus会选择优先级最高的bean注入

  • 接下来编码演示

  • 新增演示用的接口HelloPriority.java

public interface HelloPriority {
String hello();
}
  • HelloPriority的第一个实现类HelloPriorityA.java,注意它的两个注解Alternative和Priority,前者表明这是个可供选择的bean,后者表明了它的优先级,数字1001用于和其他bean的优先级比较,数字越大优先级越高
@ApplicationScoped
@Alternative
@Priority(1001)
public class HelloPriorityA implements HelloPriority {
@Override
public String hello() {
return this.getClass().getSimpleName();
}
}
  • HelloPriority的第二个实现类HelloPriorityB,可见Priority属性值是1002,代表选择的时候优先级比HelloPriorityA更高
@ApplicationScoped
@Alternative
@Priority(1002)
public class HelloPriorityB implements HelloPriority {
@Override
public String hello() {
return this.getClass().getSimpleName();
}
}
  • HelloPriority的第二个实现类HelloPriorityC,可见Priority属性值是1003,代表选择的时候优先级比HelloPriorityA和HelloPriorityB更高
@ApplicationScoped
@Alternative
@Priority(1003)
public class HelloPriorityC implements HelloPriority {
@Override
public String hello() {
return this.getClass().getSimpleName();
}
}
  • 接下来是单元测试,验证注入的bean是否符合预期,理论上注入的应该是优先级最高的HelloPriorityC
@QuarkusTest
public class PriorityTest { @Inject
HelloPriority helloPriority; @Test
public void testSelectHelloInstanceA() {
Assertions.assertEquals(HelloPriorityC.class.getSimpleName(),
helloPriority.hello());
}
}
  • 单元测试结果如下,符合预期

  • 以上就是优先级选择bean的操作,如果这还不够用,那就祭出最后一招:写代码选择bean

写代码选择bean

  • 如果不用修饰符匹配,再回到最初的问题:有三个bean都实现了同一个接口,应该如何注入?
  • 在注入bean的位置,如果用Instance<T>来接收注入,就可以拿到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,可见select(Class).get()是关键,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的方式全部验证完毕,如此丰富的手段,相信可以满足您日常开发的需要

欢迎关注博客园:程序员欣宸

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

quarkus依赖注入之四:选择注入bean的高级手段的更多相关文章

  1. spring几种依赖注入方式以及ref-local/bean,factory-bean,factory-method区别联系

    平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程 ...

  2. Spring学习笔记1—依赖注入(构造器注入、set注入和注解注入)

    什么是依赖注入 在以前的java开发中,某个类中需要依赖其它类的方法时,通常是new一个依赖类再调用类实例的方法,这种方法耦合度太高并且不容易测试,spring提出了依赖注入的思想,即依赖类不由程序员 ...

  3. spring依赖注入之构造函数注入,set方法注入

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...

  4. Spring 依赖注入(基本注入和自动适配注入)

    Spring 依赖注入 Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系. 属性注入 构造注入 内部注入 自动装配 1.属性注入 IService: public in ...

  5. DotNetCore依赖注入实现批量注入

    文章转载自平娃子(QQ:273206491):http://os.pingwazi.cn/resource/batchinjectservice 一.依赖注入 通过依赖注入,可以实现接口与实现类的松耦 ...

  6. Spring依赖注入的Setter注入(通过get和set方法注入)

    Spring依赖注入的Setter注入(通过get和set方法注入) 导入必要的jar包(Spring.jar和commonslogging.jar) 在src目录下建立applicationCont ...

  7. 1.spring:helloword/注入/CDATA使用/其他Bean/null&级联/p命名空间

    新建工程,导入jar,添加spring配置文件(配置文件xxxx.xml)! 1.Helloword实现 Helloword.java public class HelloWord { private ...

  8. spring 是如何注入对象的和bean 创建过程分析

    文章目录: beanFactory 及 bean 生命周期起步 BeanFactory refresh 全过程 BeanFactoryPostProcessor 和 BeanPostProcessor ...

  9. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring DI(依赖注入)的实现方式属性注入和构造注入

    依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念. 当某个 Java 实例需要另一个 Java 实例时,传统的方法是由调用者创建被调用者的 ...

  10. Spring官网阅读(二)(依赖注入及方法注入)

    上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...

随机推荐

  1. 使用 shell 脚本自动申请进京证 (六环外) —— debug 过程

    问题现象 用 shell 脚本写了一个自动办理六环外进京证的工具 <使用 shell 脚本自动申请进京证 (六环外)>,然而运行这个脚本总是返回以下错误信息: { "msg&qu ...

  2. drf重写authenticate方法实现多条件登录(源码分析)

    drf重写authenticate方法实现多条件登录(源码分析) 1. 思路 JWT拓展的登录视图中, 在接受到用户名和密码时, 调用的也是Django的认证系统中提供的authenticate()来 ...

  3. Protobuf: 高效数据传输的秘密武器

    当涉及到网络通信和数据存储时,数据序列化一直都是一个重要的话题:特别是现在很多公司都在推行微服务,数据序列化更是重中之重,通常会选择使用 JSON 作为数据交换格式,且 JSON 已经成为业界的主流. ...

  4. 2021-08-25:给定数组father大小为N,表示一共有N个节点,father[i] = j 表示点i的父亲是点j, father表示的树一定是一棵树而不是森林,queries是二维数组,大小为

    2021-08-25:给定数组father大小为N,表示一共有N个节点,father[i] = j 表示点i的父亲是点j, father表示的树一定是一棵树而不是森林,queries是二维数组,大小为 ...

  5. Spring源码:Bean生命周期(五)

    前言 在上一篇文章中,我们深入探讨了 Spring 框架中 Bean 的实例化过程,该过程包括从 Bean 定义中加载当前类.寻找所有实现了 InstantiationAwareBeanPostPro ...

  6. Bracket Sequence

    F. Bracket Sequence time limit per test 0.5 seconds memory limit per test 256 megabytes input standa ...

  7. C# decimal double 获取一组数字 小数点后最多有几位

    有一组数字,想判断一组数字中最多的有几位小数,乘以10的指定幂,转为整数,此处教大家一个高级的写法,拒接无脑for循环 decimal: decimal[] numbers = new decimal ...

  8. Java中的金钱陷阱

    前言 有多少小伙伴是被标题 骗 吸引进来的呢,我可不是标题党,今天的文章呢确实跟"金钱"有关系. 但是我们说的不是过度追求金钱而掉入陷阱,而是要说一说在Java程序中,各种跟金钱运 ...

  9. 使用C语言实现简单的通用的链表

    在数据结构中,我们已经学习到了简单的静态链表以及单链表和双链表,它们各有优缺点,但是有个共同的问题是他们呢无法存储不同的数据.下面提供了一种方法,可以将不同节点的数据链接起来. 下面的代码都是基础的C ...

  10. 专访泛境科技:如何借助3DCAT实时云渲染打造元宇宙解决方案

    随着5G.VR/AR等技术的发展,元宇宙(Metaverse)这一概念越来越受到关注.元宇宙是一个由虚拟世界构成的网络空间,其中人们可以通过数字化的身份和形象进行各种社交.娱乐.创作和商业活动.元宇宙 ...