关于后端:quarkus依赖注入之二bean的作用域

43次阅读

共计 7313 个字符,预计需要花费 19 分钟才能阅读完成。

欢送拜访我的 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 实例以单例模式始终存活(只有利用还存活着),这是业务开发中罕用的作用域类型:
@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 官网称之为 <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> 了
@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. 第二种:<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;

@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,要留神的是注解 <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;

@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 实例化,合乎预期,另外还有意外播种,稍后马上就会提到
  • 另外,请 <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 的类的身份
@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 的扩大组件丰富多彩,本人也能依照官网指引制作,所以扩大组件对应的作用域也随着组件的不同而各不相同,就不在此列举了,就举一个例子吧:quarkus-narayana-jta 组件中定义了一个作用域 javax.transaction.TransactionScoped,该作用域润饰的 bean,每个事物对应一个实例
  • 至此,quarkus 作用域的理解和实战曾经实现,这样一来,不论是应用 bean 还是创立 bean,都能按业务须要来精确管制其生命周期了

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

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

正文完
 0