欢迎访问我的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,在注入的时候并未实例化,只有它的方法首次执行的时候才会实例化,如下图

  • 接下来细看每个作用域

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实例化,符合预期,另外还有意外收获,稍后马上就会提到

  • 另外,请重点关注蓝框和蓝色注释文字,这是意外收获,居然看到了代理类的日志,看样子代理类是继承了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,会空指针异常的

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

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

Dependent

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

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

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

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

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

quarkus依赖注入之二:bean的作用域的更多相关文章

  1. Spring的依赖注入和管理Bean

    采用Spring管理Bean和依赖注入 1.实例化spring容器 和 从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] Ap ...

  2. Java 系列之spring学习--依赖注入(二)

    一.依赖注入的三种方式 接口注入,set注入,构造函数注入 二.构造函数注入 2.1.测试类 package test; public class test01 { public String msg ...

  3. spring注解(Component、依赖注入、生命周期、作用域)

    1.注解 注解就是一个类,使用@加上注解名称,开发中可以使用注解取代配置文件 2.@Component 取代<bean  class="">,@Component 取代 ...

  4. Unity 依赖注入之二

    1. 构造子注入 1.1 构造子注入初级代码 container.RegisterType<IMyWork, MyWork>(new InjectionConstructor(new Bo ...

  5. 轻松了解Spring中的控制反转和依赖注入(二)

    紧接上一篇文章<轻松了解Spring中的控制反转和依赖注入>讲解了SpringIOC和DI的基本概念,这篇文章我们模拟一下SpringIOC的工作机制,使我们更加深刻的理解其中的工作. 类 ...

  6. 重新整理 .net core 实践篇————依赖注入应用[二]

    前言 这里介绍一下.net core的依赖注入框架,其中其代码原理在我的另一个整理<<重新整理 1400篇>>中已经写了,故而专门整理应用这一块. 以下只是个人整理,如有问题, ...

  7. 05 Spring框架 依赖注入(二)

    上一节我们讲了三种信息的注入,满足一个类的属性信息的注入,但是如果我们需要向一个实例中注入另一个实例呢?就像我们创建一个学生类,里边有:姓名,性别,年龄,成绩等几个属性(我习惯把类的域叫做属性),但是 ...

  8. spring不依赖注入得到实体bean

    如题,我们一般用spring的ioc,通过配置注入接口得到这个实现类,现在通过研究公司平台框架发现还有一种方法得到spring文件配置的bean方法,举个例子(注:这个ApplicationConte ...

  9. Java Web系列:Spring依赖注入基础

    一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...

  10. 再学习之Spring(依赖注入)

    一.概述 Spring框架是以 简化Java EE应用程序的开发 为目标而创建的.Spring可以实现很多功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入和面向切面编程.几乎Sprin ...

随机推荐

  1. SSH客户端常用工具SecureCRT操作

    目录 1.1 SecureCRT工具介绍 1.2 SecureCRT工具安装 1.3配置SecureCRT连接Linux主机 1.4调整SecureCRT终端显示和回滚缓冲区大小 1.5调整字体及光标 ...

  2. 工作中,Oracle常用函数

    目录 1.序言 2.Oracle函数分类 3.数值型函数 3.1 求绝对值函数 3.2 求余函数 3.3 判断数值正负函数 3.4 三角函数 3.5 返回以指定数值为准整数的函数 3.6 指数.对数函 ...

  3. 【CSS】使元素在父元素中居中显示的几种方法

    在页面元素布局时经常会有把元素居中的需求,大多都是用弹性盒或者定位,下面来说一下使用方法 一.使用边距进行固定位置 这种方法需要把父元素和子元素的宽度固定,然后利用二者宽高之差添加边距移动元素的位置 ...

  4. #Python 缺失值的检测与处理,处理部分

  5. EasyExcel自适应列宽

    import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.Head; import com. ...

  6. 2022-07-18:以下go语言代码输出什么?A:Groutine;B:Main;C:Goroutine;D:GoroutineMain。 package main import ( “f

    2022-07-18:以下go语言代码输出什么?A:Groutine:B:Main:C:Goroutine:D:GoroutineMain. package main import ( "f ...

  7. 2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据。数据库不能停,并且还有增删改操作。请问如何操作?

    2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据.数据库不能停,并且还有增删改操作.请问如何操作?福哥答案2020-01-20: 陌陌答案:用pt_onl ...

  8. 2022-03-17:所有黑洞的中心点记录在holes数组里, 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9), 并且所有黑洞的中心点都在左下角(0,0),右上角(

    2022-03-17:所有黑洞的中心点记录在holes数组里, 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9), 并且所有黑洞的中心点都在左下角(0,0),右上角( ...

  9. 2021-05-09:给定数组hard和money,长度都为N;hard[i]表示i号的难度, money[i]表示i号工作的收入;给定数组ability,长度都为M,ability[j]表示j号人的

    2021-05-09:给定数组hard和money,长度都为N:hard[i]表示i号的难度, money[i]表示i号工作的收入:给定数组ability,长度都为M,ability[j]表示j号人的 ...

  10. vue平铺日历组件之按住ctrl、shift键实现跨月、跨年多选日期的功能

    已经好久没有更新过博客了,大概有两三年了吧,因为换了工作,工作也比较忙,所以也就没有时间来写技术博客,期间也一直想写,但自己又比较懒,就给耽误了.今天这篇先续上,下一篇什么时候写,我也不知道,随心所欲 ...