欢送拜访我的GitHub

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

对于bean的作用域(scope)

  • 官网材料:https://lordofthejars.github.io/quarkus-cheat-sheet/#_injection
  • 作为《quarkus依赖注入》系列的第二篇,持续学习一个重要的知识点:bean的作用域(scope),每个bean的作用域是惟一的,不同类型的作用域,决定了各个bean实例的生命周期,例如:何时何处创立,又何时何处销毁
  • bean的作用域在代码中是什么样的?回顾前文的代码,如下,<font color="blue">ApplicationScoped</font>就是作用域,表明bean实例以单例模式始终存活(只有利用还存活着),这是业务开发中罕用的作用域类型:
@ApplicationScopedpublic class ClassAnnotationBean {    public String hello() {        return "from " + this.getClass().getSimpleName();    }}
  • 作用域有多种,如果按起源辨别一共两大类:quarkus内置和扩大组件中定义,本篇聚焦quarkus的内置作用域
  • 上面是整顿好的作用域一览,接下来会一一解说
graph LRL1(作用域) --> 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官网称之为<font color="blue">normal scope</font>,包含:ApplicationScoped、RequestScoped、SessionScoped三种
  • 伪作用域称之为<font color="red">pseudo scope</font>,包含:Singleton、RequestScoped、Dependent两种
  • 接下来,用一段最平时的代码来揭示惯例作用域和伪作用域的区别
  • 上面的代码中,ClassAnnotationBean的作用域ApplicationScoped就是<font color="blue">normal scope</font>,如果换成<font color="red">Singleton</font>就是<font color="red">pseudo scope</font>了
@ApplicationScopedpublic 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. 第二种:<font color="blue">get</font>办法第一次被调用的时候,classAnnotationBean真正发挥作用,这时ClassAnnotationBean被实例化
  • 所以,一共有两个工夫点:注入时和get办法首次执行时,作用域不同,这两个工夫点做的事件也不同,上面用表格来解释
工夫点惯例作用域伪作用域
注入的时候注入的是一个代理类,此时ClassAnnotationBean并未实例化触发ClassAnnotationBean实例化
get办法首次执行的时候1. 触发ClassAnnotationBean实例化
2. 执行惯例业务代码
1. 执行惯例业务代码
  • 至此,您应该明确两种作用域的区别了:伪作用域的bean,在注入的时候实例化,惯例作用域的bean,在注入的时候并未实例化,只有它的办法首次执行的时候才会实例化,如下图

  • 接下来细看每个作用域

ApplicationScoped

  • ApplicationScoped算是最罕用的作用域了,它润饰的bean,在整个利用中只有一个实例

RequestScoped

  • 这是与以后http申请绑定的作用域,它润饰的bean,在每次http申请时都有一个全新实例,来写一段代码验证
  • 首先是bean类RequestScopeBean.java,留神作用域是<font color="blue">RequestScoped</font>,如下,在构造方法中打印日志,这样能够通过日志行数晓得实例化次数
package com.bolingcavalry.service.impl;import io.quarkus.logging.Log;import javax.enterprise.context.RequestScoped;@RequestScopedpublic 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,要留神的是注解<font color="blue">RepeatedTest</font>,有了此注解,<font color="red">testGetEndpoint</font>办法会反复执行,次数是注解的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;@QuarkusTestclass 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实例化,合乎预期,另外还有意外播种,稍后马上就会提到

  • 另外,请<font color="red">重点关注</font>蓝框和蓝色正文文字,这是意外播种,竟然看到了代理类的日志,看样子代理类是继承了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,在整个利用中都是只有一个实例,然而它们也是有区别的:<font color="blue">ApplicationScoped润饰的bean有代理类包裹,Singleton润饰的bean没有代理类</font>
  • Singleton润饰的bean没有代理类,所以在应用的时候,对bean的成员变量间接读写都没有问题(safely),而ApplicationScoped润饰的bean,请不要间接读写其成员变量,比拟拿都是代理的货色,而不是bean的类本人的成员变量
  • Singleton润饰的bean没有代理类,所以理论应用中性能会略好(slightly better performance)
  • 在应用<font color="blue">QuarkusMock</font>类做单元测试的时候,不能对Singleton润饰的bean做mock,因为没有代理类去执行相干操作
  • quarkus官网举荐应用的是<font color="red">ApplicationScoped</font>
  • Singleton被quarkus划分为<font color="blue">伪作用域</font>,此时再回头品尝下图,您是否豁然开朗:成员变量classAnnotationBean如果是Singleton,是没有代理类的,那就必须在@Inject地位实例化,否则,在get办法中classAnnotationBean就是null,会空指针异样的

  • 运行代码验证是否有代理类,找到方才的RequestScopeBean.java,将作用域改成<font color="blue">Singleton</font>,运行单元测试类RequestScopeControllerTest.java,后果如下图红框,只有RequestScopeBean本人构造方法的日志
  • 再将作用域改成<font color="blue">ApplicationScoped</font>,如下图蓝框,代理类日志呈现

Dependent

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

<img src="https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/%E6%B5%81%E7%A8%8B%E5%9B%BE%20(18).jpg" alt="流程图 (18)" style="zoom:50%;" />

Dependent的非凡能力

  • Dependent的特点是每个注入点的bean实例都不同,针对这个特点,quarkus提供了一个非凡能力:bean的实例中能够获得注入点的元数据
  • 对应上图的例子,就是HelloDependent的代码中能够获得它的使用者:DependentClientA和DependentClientB的元数据
  • 写代码验证这个非凡能力
  • 首先是HelloDependent的定义,将作用域设置为<font color="blue">Dependent</font>,而后留神其构造方法的参数,这就是非凡能力所在,是个<font color="red">InjectionPoint</font>类型的实例,这个参数在实例化的时候由quarkus容器注入,通过此参数即可得悉应用HelloDependent的类的身份
@Dependentpublic class HelloDependent {    public HelloDependent(InjectionPoint injectionPoint) {        Log.info("injecting from bean "+ injectionPoint.getMember().getDeclaringClass());    }    public String hello() {        return this.getClass().getSimpleName();    }}
  • 而后是HelloDependent的应用类DependentClientA
@ApplicationScopedpublic class DependentClientA {    @Inject    HelloDependent hello;    public String doHello() {        return hello.hello();    }}
  • DependentClientB的代码和DependentClientA截然不同,就不贴出来了
  • 最初写个单元测试类验证HelloDependent的非凡能力
@QuarkusTestpublic 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的扩大组件丰富多彩,本人也能依照官网指引制作,所以扩大组件对应的作用域也随着组件的不同而各不相同,就不在此列举了,就举一个例子吧:quarkus-narayana-jta组件中定义了一个作用域javax.transaction.TransactionScoped,该作用域润饰的bean,每个事物对应一个实例
  • 至此,quarkus作用域的理解和实战曾经实现,这样一来,不论是应用bean还是创立bean,都能按业务须要来精确管制其生命周期了

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

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