quarkus依赖注入之二:bean的作用域

这篇具有很好参考价值的文章主要介绍了quarkus依赖注入之二:bean的作用域。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎访问我的GitHub

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

关于bean的作用域(scope)

  • 官方资料:https://lordofthejars.github.io/quarkus-cheat-sheet/#_injection

  • 作为《quarkus依赖注入》系列的第二篇,继续学习一个重要的知识点:bean的作用域(scope),每个bean的作用域是唯一的,不同类型的作用域,决定了各个bean实例的生命周期,例如:何时何处创建,又何时何处销毁

  • bean的作用域在代码中是什么样的?回顾前文的代码,如下,ApplicationScoped就是作用域,表明bean实例以单例模式一直存活(只要应用还存活着),这是业务开发中常用的作用域类型:

@ApplicationScoped
public class ClassAnnotationBean {

    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}
  • 作用域有多种,如果按来源区分一共两大类:quarkus内置和扩展组件中定义,本篇聚焦quarkus的内置作用域
  • 下面是整理好的作用域一览,接下来会逐个讲解
graph LR L1(作用域) --> L2-1(内置) L1 --> L2-2(扩展组件) L2-1 --> L3-1(常规作用域) L2-1 --> L3-2(伪作用域) L3-1 --> L4-1(ApplicationScoped) L3-1 --> L4-2(RequestScoped) L3-1 --> L4-3(SessionScoped) L3-2 --> L4-4(Singleton) L3-2 --> L4-5(Dependent) L2-2 --> L3-6(例如 : TransactionScoped)

常规作用域和伪作用域

  • 常规作用域,quarkus官方称之为normal scope,包括:ApplicationScoped、RequestScoped、SessionScoped三种
  • 伪作用域称之为pseudo scope,包括:Singleton、RequestScoped、Dependent两种
  • 接下来,用一段最平常的代码来揭示常规作用域和伪作用域的区别
  • 下面的代码中,ClassAnnotationBean的作用域ApplicationScoped就是normal scope,如果换成Singleton就是pseudo scope
@ApplicationScoped
public class ClassAnnotationBean {

    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}
  • 再来看使用ClassAnnotationBean的代码,如下所示,是个再平常不过的依赖注入
@Path("/classannotataionbean")
public class ClassAnnotationController {

    @Inject
    ClassAnnotationBean classAnnotationBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                classAnnotationBean.hello());
    }
}
  • 现在问题来了,ClassAnnotationBean是何时被实例化的?有以下两种可能:
  1. 第一种:ClassAnnotationController被实例化的时候,classAnnotationBean会被注入,这时ClassAnnotationBean被实例化

  2. 第二种:get方法第一次被调用的时候,classAnnotationBean真正发挥作用,这时ClassAnnotationBean被实例化

  • 所以,一共有两个时间点:注入时和get方法首次执行时,作用域不同,这两个时间点做的事情也不同,下面用表格来解释
时间点 常规作用域 伪作用域
注入的时候 注入的是一个代理类,此时ClassAnnotationBean并未实例化 触发ClassAnnotationBean实例化
get方法首次执行的时候 1. 触发ClassAnnotationBean实例化
2. 执行常规业务代码
1. 执行常规业务代码
  • 至此,您应该明白两种作用域的区别了:伪作用域的bean,在注入的时候实例化,常规作用域的bean,在注入的时候并未实例化,只有它的方法首次执行的时候才会实例化,如下图

quarkus依赖注入之二:bean的作用域

  • 接下来细看每个作用域

ApplicationScoped

  • ApplicationScoped算是最常用的作用域了,它修饰的bean,在整个应用中只有一个实例

RequestScoped

  • 这是与当前http请求绑定的作用域,它修饰的bean,在每次http请求时都有一个全新实例,来写一段代码验证
  • 首先是bean类RequestScopeBean.java,注意作用域是RequestScoped,如下,在构造方法中打印日志,这样可以通过日志行数知道实例化次数
package com.bolingcavalry.service.impl;

import io.quarkus.logging.Log;
import javax.enterprise.context.RequestScoped;

@RequestScoped
public class RequestScopeBean {

    /**
     * 在构造方法中打印日志,通过日志出现次数对应着实例化次数
     */
    public RequestScopeBean() {
        Log.info("Instance of " + this.getClass().getSimpleName());
    }

    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}
  • 然后是使用bean的代码,是个普通的web服务类
package com.bolingcavalry;

import com.bolingcavalry.service.impl.RequestScopeBean;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/requestscope")
public class RequestScopeController {

    @Inject
    RequestScopeBean requestScopeBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                requestScopeBean.hello());
    }
}
  • 最后是单元测试代码RequestScopeControllerTest.java,要注意的是注解RepeatedTest,有了此注解,testGetEndpoint方法会重复执行,次数是注解的value属性值,这里是10次
package com.bolingcavalry;

import com.bolingcavalry.service.impl.RequestScopeBean;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
class RequestScopeControllerTest {

    @RepeatedTest(10)
    public void testGetEndpoint() {
        given()
                .when().get("/requestscope")
                .then()
                .statusCode(200)
                // 检查body内容,是否含有ClassAnnotationBean.hello方法返回的字符串
                .body(containsString("from " + RequestScopeBean.class.getSimpleName()));
    }
}
  • 由于单元测试中接口会调用10次,按照RequestScoped作用域的定义,RequestScopeBean会实例化10次,执行单元测试试试吧
  • 执行结果如下图,红框4显示每次http请求都会触发一次RequestScopeBean实例化,符合预期,另外还有意外收获,稍后马上就会提到

quarkus依赖注入之二:bean的作用域

  • 另外,请重点关注蓝框和蓝色注释文字,这是意外收获,居然看到了代理类的日志,看样子代理类是继承了RequestScopeBean类,于是父类构造方法中的日志代码也执行了,还把代理类的类名打印出来了
  • 从日志可以看出:10次http请求,bean的构造方法执行了10次,代理类的构造方法只执行了一次,这是个重要结论:bean类被多次实例化的时候,代理类不会多次实例化

SessionScoped

  • SessionScoped与RequestScoped类似,区别是范围,RequestScoped是每次http请求做一次实例化,SessionScoped是每个http会话,以下场景都在session范围内,共享同一个bean实例:
  1. servlet的service方法
  2. servlet filter的doFileter方法
  3. web容器调用HttpSessionListener、AsyncListener、ServletRequestListener等监听器

Singleton

  • 提到Singleton,聪明的您是否想到了单例模式,这个scope也是此意:它修饰的bean,在整个应用中只有一个实例

  • Singleton和ApplicationScoped很像,它们修饰的bean,在整个应用中都是只有一个实例,然而它们也是有区别的:ApplicationScoped修饰的bean有代理类包裹,Singleton修饰的bean没有代理类

  • Singleton修饰的bean没有代理类,所以在使用的时候,对bean的成员变量直接读写都没有问题(safely),而ApplicationScoped修饰的bean,请不要直接读写其成员变量,比较拿都是代理的东西,而不是bean的类自己的成员变量

  • Singleton修饰的bean没有代理类,所以实际使用中性能会略好(slightly better performance)

  • 在使用QuarkusMock类做单元测试的时候,不能对Singleton修饰的bean做mock,因为没有代理类去执行相关操作

  • quarkus官方推荐使用的是ApplicationScoped

  • Singleton被quarkus划分为伪作用域,此时再回头品味下图,您是否恍然大悟:成员变量classAnnotationBean如果是Singleton,是没有代理类的,那就必须在@Inject位置实例化,否则,在get方法中classAnnotationBean就是null,会空指针异常的

quarkus依赖注入之二:bean的作用域

  • 运行代码验证是否有代理类,找到刚才的RequestScopeBean.java,将作用域改成Singleton,运行单元测试类RequestScopeControllerTest.java,结果如下图红框,只有RequestScopeBean自己构造方法的日志
    quarkus依赖注入之二:bean的作用域

  • 再将作用域改成ApplicationScoped,如下图蓝框,代理类日志出现

quarkus依赖注入之二:bean的作用域

Dependent

  • Dependent是个伪作用域,它的特点是:每个依赖注入点的对象实例都不同
  • 假设DependentClinetA和DependentClinetB都用@Inject注解注入了HelloDependent,那么DependentClinetA引用的HelloDependent对象,DependentClinetB引用的HelloDependent对象,是两个实例,如下图,两个hello是不同的实例
quarkus依赖注入之二:bean的作用域

Dependent的特殊能力

  • Dependent的特点是每个注入点的bean实例都不同,针对这个特点,quarkus提供了一个特殊能力:bean的实例中可以取得注入点的元数据
  • 对应上图的例子,就是HelloDependent的代码中可以取得它的使用者:DependentClientA和DependentClientB的元数据
  • 写代码验证这个特殊能力
  • 首先是HelloDependent的定义,将作用域设置为Dependent,然后注意其构造方法的参数,这就是特殊能力所在,是个InjectionPoint类型的实例,这个参数在实例化的时候由quarkus容器注入,通过此参数即可得知使用HelloDependent的类的身份
@Dependent
public class HelloDependent {

    public HelloDependent(InjectionPoint injectionPoint) {
        Log.info("injecting from bean "+ injectionPoint.getMember().getDeclaringClass());
    }

    public String hello() {
        return this.getClass().getSimpleName();
    }
}

  • 然后是HelloDependent的使用类DependentClientA
@ApplicationScoped
public class DependentClientA {

    @Inject
    HelloDependent hello;

    public String doHello() {
        return hello.hello();
    }
}
  • DependentClientB的代码和DependentClientA一模一样,就不贴出来了

  • 最后写个单元测试类验证HelloDependent的特殊能力

@QuarkusTest
public class DependentTest {

    @Inject
    DependentClientA dependentClientA;

    @Inject
    DependentClientB dependentClientB;

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

        Assertions.assertEquals(clazz.getSimpleName(), dependentClientA.doHello());
        Assertions.assertEquals(clazz.getSimpleName(), dependentClientB.doHello());
    }
}
  • 运行单元测试,如下图红框,首先,HelloDependent的日志打印了两次,证明的确实例化了两个HelloDependent对象,其次日志的内容也准确的将注入点的类的信息打印出来

quarkus依赖注入之二:bean的作用域

扩展组件的作用域

  • quarkus的扩展组件丰富多彩,自己也能按照官方指引制作,所以扩展组件对应的作用域也随着组件的不同而各不相同,就不在此列举了,就举一个例子吧:quarkus-narayana-jta组件中定义了一个作用域javax.transaction.TransactionScoped,该作用域修饰的bean,每个事物对应一个实例

  • 至此,quarkus作用域的了解和实战已经完成,这样一来,不论是使用bean还是创建bean,都能按业务需要来准确控制其生命周期了

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

学习路上,你不孤单,欣宸原创一路相伴...文章来源地址https://www.toymoban.com/news/detail-616944.html

到了这里,关于quarkus依赖注入之二:bean的作用域的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • quarkus依赖注入之十:学习和改变bean懒加载规则

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的第十篇,来看一个容易被忽略的知识点:bean的懒加载,咱们先去了解quarkus框架下的懒加载规则,然后更重要的是掌握如何改变规则,以达到提前实例化的目标 总的来

    2024年02月14日
    浏览(25)
  • quarkus依赖注入之七:生命周期回调

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇的知识点是bean的生命周期回调:在bean生命周期的不同阶段,都可以触发自定义代码的执行 触发自定义代码执行的具体方式,是用对应的注解去修饰要执行的方法,如下图所示: 有两种模式可以

    2024年02月14日
    浏览(28)
  • quarkus依赖注入之八:装饰器(Decorator)

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的第八篇,目标是掌握quarkus实现的一个CDI特性:装饰器(Decorator) 提到装饰器,熟悉设计模式的读者应该会想到装饰器模式,个人觉得下面这幅图很好的解释了装饰器

    2024年02月14日
    浏览(32)
  • quarkus依赖注入之六:发布和消费事件

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本文是《quarkus依赖注入》系列的第六篇,主要内容是学习事件的发布和接收 如果您用过Kafka、RabbitMQ等消息中间件,对消息的作用应该不会陌生,通过消息的订阅和发布可以降低系统之间的耦合性,

    2024年02月14日
    浏览(27)
  • quarkus依赖注入之五:拦截器(Interceptor)

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本文是《quarkus依赖注入》系列的第五篇,经过前面的学习,咱们熟悉了依赖注入的基本特性,接下来进一步了解相关的高级特性,先从本篇的拦截器开始 如果您熟悉spring的话,对拦截器应该不会陌

    2024年02月14日
    浏览(35)
  • quarkus依赖注入之十二:禁用类级别拦截器

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的第十二篇,继续学习拦截器的另一个高级特性:禁用类级别拦截器 本篇由以下内容构成 编码验证类拦截器和方法拦截器的叠加效果 用注解 NoClassInterceptors 使类拦截器

    2024年02月13日
    浏览(26)
  • quarkus依赖注入之十三:其他重要知识点大串讲(终篇)

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的终篇,前面十二篇已覆盖quarkus依赖注入的大部分核心内容,但依然漏掉了一些知识点,今天就将剩下的内容汇总,来个一锅端,轻松愉快的结束这个系列 总的来说,

    2024年02月13日
    浏览(25)
  • quarkus依赖注入之十一:拦截器高级特性上篇(属性设置和重复使用)

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的第十一篇,之前的[《拦截器》]学习了拦截器的基础知识,现在咱们要更加深入的了解拦截器,掌握两种高级用法:拦截器属性和重复使用拦截器 先来回顾拦截器的基

    2024年02月13日
    浏览(25)
  • Springboot依赖注入Bean的三种方式,final+构造器注入Bean

    @Autowired注解的一大使用场景就是Field Injection。 通过Java的反射机制实现,所以private的成员也可以被注入具体的对象 优点 代码少,简洁明了。 新增依赖十分方便,不需要修改原有代码 缺点 容易出现空指针异常。Field 注入允许构建对象实例时依赖的对象为空,导致空指针异常

    2024年02月02日
    浏览(39)
  • 基于Xml方式Bean的配置-Bean的依赖注入以及·自动装配

    Bean的依赖注入方式 注入方式 配置方式 通过Bean的set方法注入 通过构造Bean的方法进行注入 其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其它Bean的id,value用于指定属性值 注入数据类型 普通数据类型:String、int、boolean,通过value属性指定 引用数据类型

    2024年02月07日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包