欢迎访问我的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. [Pytorch框架] 2.1.3 神经网络包nn和优化器optm

    文章目录 PyTorch 基础 : 神经网络包nn和优化器optm 定义一个网络 损失函数 优化器 PyTorch 基础 : 神经网络包nn和优化器optm torch.nn是专门为神经网络设计的模块 ...

  2. 快速傅里叶变换FFT学习笔记

    点值表示法 我们正常表示一个多项式的方式,形如 \(A(x)=a_0+a_1x+a_2x^2+...+a_nx^n\),这是正常人容易看懂的,但是,我们还有一种表示法. 我们知道,\(n+1\)个点可 ...

  3. HTML中link标签的那些属性

    在HTML中, link 标签是一个自闭合元素,通常位于文档的 head 部分.它用于建立与外部资源的关联,如样式表.图标等. link 标签具有多个属性,其中 rel 和 href 是最常用的. r ...

  4. [转] Windows下Hook DirectX

    首先说,这篇文章是很久以前为了玩成某游戏的HOOK找到的资料,虽然一直没用上,但是还是让我保留下来了.直接贴上了..看不懂也不要问我,我都没看. 也许看得懂的人对他们来说这是一个思路,不懂的就当垃圾文 ...

  5. #Powerbi函数学习 SELECTEDVALUE与ISFILTERED

    Power BI中的DAX函数ISFILTERED可以用来判断一个表或者一个列是否被筛选器所影响. 这个函数的语法很简单,就是ISFILTERED(<table_or_column_name&g ...

  6. 2021-04-10:给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null。【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

    2021-04-10:给定两个可能有环也可能无环的单链表,头节点head1和head2.请实现一个函数,如果两个链表相交,请返回相交的 第一个节点.如果不相交,返回null.[要求]如果两个链表长度之 ...

  7. GRPC与 ProtoBuf 的理解与总结

    转载请注明出处: 1.GRPC 官网:https://www.grpc.io/ gRPC 官方文档中文版:http://doc.oschina.net/grpc RPC 框架的目标就是让远程服务调用更 ...

  8. Shiro 授权绕过 (CVE-2022-32532)

    Shiro 授权绕过 (CVE-2022-32532) 一.产品简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 1.9.1 之前的 Apache ...

  9. 读文献先读图——主成分分析 PCA 图

    上周五彩斑斓的气泡图 有让你眼花缭乱吗? 本周,化繁为简的PCA图 你值得拥有!  数据分析| 科研制图﹒PCA 图 关键词:主成分分析.降维 1665 年的鼠疫 牛顿停课在家提出了万有引力 ;183 ...

  10. 手把手教你如何在 Linux 上源码安装最新版本 R

    如果你使用的 Linux 系统 GCC 版本太低,又没有 root 权限(即使有 root 权限又担心升级 GCC 带来的风险):同时你又不想额外多安装多一个 Anaconda 或者 Minicond ...