【Java面试】六、Spring框架相关

文章目录

  • 1、单例Bean不是线程安全的
  • 2、AOP
  • 3、Spring中事务的实现
  • 4、Spring事务失效的场景
    • 4.1 情况一:异常被捕获
    • 4.2 情况二:抛出检查异常
    • 4.3 注解加在非public方法上
  • 5、Bean的生命周期
  • 6、Bean的循环引用
  • 7、Bean循环引用的解决:Spring三级缓存
  • 8、构造方法出现循环依赖
  • 9、面试

1、单例Bean不是线程安全的

@Scope注解下,singleton表示Bean在IOC容器中只有一个实例,prototype则表示一个Bean可以有多个实例

在这里插入图片描述

但单例Bean不是线程安全的,Demo代码如下:线程1进来count被改成1,线程2进来,count被改成10,线程交替执行,就乱套了,单例Bean UserController就不线程安全

在这里插入图片描述
多个用户同时请求一个微服务,每个请求对应一个线程,如果并发的这段代码中,对单例的成员属性修改了,则存在线程安全问题。上面例子中,UserController这个Bean的成员属性count在被并发修改(存在可修改的成员变量),此时,单例的Bean不是线程安全的。相反,UserService的这个Bean,是不可变状态(不涉及其成员属性的修改),因此,UserService的这个单例Bean是线程安全的。

最后,补充一句,/getById/{id},id属于局部变量,更无线程安全问题了。

2、AOP

项目相关:做链路追踪埋点,采集所有接口的数据库层、Redis执行时间等信息,采用了AOP实现。

AOP,面向切面编程,将那些公共的逻辑代码,抽取封装,匹配要切入的点,实现统一加功能的操作。减少了系统的重复代码,提高了可维护性。其使用场景如自己做公共日志采集上报、Spring用来实现事务

在这里插入图片描述

3、Spring中事务的实现

Spring中,事务的实现可以编程式,也可以声明式。前者使用TransactionTemplate实现,对业务代码有侵入性。后者使用@Transactional注解。加注解实现事务,底层用的是AOP:

在这里插入图片描述
实现如上:切点表达式匹配@Transactional注解,使用环绕通知,执行前,加开启事务的代码,执行后,加提交事务的代码,出现异常,回滚事务。

4、Spring事务失效的场景

4.1 情况一:异常被捕获

如下:不加try-catch,发生异常后,事务回滚。加了try-catch,发生异常后,被catch捕获处理了。@Transactional对应的AOP没有收到异常,就会去提交事务。导致了异常后面的业务代码没执行,就会出现转账账户扣钱了,但被转账的用户余额不变的情况。

在这里插入图片描述

4.2 情况二:抛出检查异常

以下,读取的文件不存在,抛出了FileNotFound异常,但事务并不会回滚

在这里插入图片描述

因为Spring默认只会回滚非检查异常(RunTimeException),解决办法是,添加属性,指明事务回滚的异常种类,以下写法即只要发生异常就回滚:

@Transactional(rollbackFor=Exception.class)

4.3 注解加在非public方法上

在这里插入图片描述
Spring为方法创建代理,AOP添加事务通知的前提是:方法是public的,此时需要改为public方法

5、Bean的生命周期

首先,Spring容器将xml中的<bean>信息封装成一个个BeanDefinition对象(该接口的相关方法如下,可获取全类名、初始化方法、作用域,其中后面步骤的初始化,也是从这儿BeanDefition拿的方法名)

在这里插入图片描述
接下来:⇒

STEP1:实例化

  • 由BeanDefinition拿到构造方法,通过反射去拿到构造函数来(new Instance)实例化,拿到空对象,即纯净态的Bean
  • 当然实例化也可能是实例工厂、静态工厂等

STEP2:属性赋值

  • 解析自动装配(DI的体现),可byName、byType、constractor
  • 这里当然还有循环依赖的情况

STEP3:初始化

  • 调用那些XXXAware的回调方法
  • 调用初始化生命周期的回调方法(init-method)
  • 如果Bean涉及了AOP,还要为这个Bean创建动态代理

STEP4:销毁

  • 在Spring容器关闭的时候调用
  • 调用销毁生命周期的回调方法(destroy-method)

在这里插入图片描述

注意Bean前置处理器和Bean后置处理器的执行时机,是在Bean的init方法执行前后。一般来说,框架中,Bean创建动态代理,常用后置处理器,即实现BeanPostProcessor。代码验证下:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {public User() {System.out.println("User的构造方法执行了.........");}private String name ;@Value("张三")		//依赖注入public void setName(String name) {System.out.println("setName方法执行了.........");}@Overridepublic void setBeanName(String name) {System.out.println("setBeanName方法执行了.........");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("setBeanFactory方法执行了.........");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("setApplicationContext方法执行了........");}@PostConstructpublic void init() {System.out.println("init方法执行了.................");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("afterPropertiesSet方法执行了........");}@PreDestroypublic void destory() {System.out.println("destory方法执行了...............");}}

关于前置处理器和后置处理器接口的实现:指定如果是user的Bean,就打印一句话。前面提到,框架中,Bean创建动态代理,常用后置处理器。下面就体现了模拟框架动态代理,给user这个Bean做cglib动态代理的效果:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("user")) {System.out.println("postProcessBeforeInitialization方法执行了->user对象初始化方法前开始增强....");}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals("user")) {System.out.println("postProcessAfterInitialization->user对象初始化方法后开始增强....");//cglib代理对象Enhancer enhancer = new Enhancer();//设置需要增强的类enhancer.setSuperclass(bean.getClass());//执行回调方法,增强方法enhancer.setCallback(new InvocationHandler() {@Overridepublic Object invoke(Object o, Method method, Object[] objects) throws Throwable {//执行目标方法return method.invoke(method,objects);}});//创建代理对象return enhancer.create();}return bean;}}

效果:

在这里插入图片描述

6、Bean的循环引用

如下,A这个Bean创建时,依赖B这个Bean。而B这个Bean的创建,又依赖A这个Bean,即循环引用。有点死锁的感觉。

在这里插入图片描述
由Bean的生命周期可知,刚开始,从Beanfinition获取构造方法,反射拿到半成品的A这个Bean,即纯净态的Bean,此时,没有依赖注入,即b属性为null。

接下来,对A这个Bean进行初始化,需要依赖注入,设置b的属性,此时就需要从IoC容器中拿到B这个Bean。很明显,容器中没有,因此,去创建B这个Bean。

同A,纯净态的B,在实例化时,依赖注入,设置a的属性,发现需要A这个Bean。于是又走到了实例化A这条线路。到此,闭环了,走不出来了。

在这里插入图片描述

7、Bean循环引用的解决:Spring三级缓存

Spring框架已经帮开发者解决了大部分的循环引用 ⇒ 三级缓存(三个Map )

在这里插入图片描述

三个Map中存的分别是:单例已经走完全部流程的成熟态Bean、存半成品的Bean、对象工厂

在这里插入图片描述

一级缓存作用是:限制 bean 在 beanFactory 中只存一份,即实现 singleton scope。 仅靠它解决不了循环引用,还是会从1-6步一直循环:

在这里插入图片描述

引入二级缓存后,将原始对象A放入二级缓存,再去找B这个Bean。同理,B的原始对象也会先放入二级缓存。接下来B需要依赖注入A这个Bean时,会去从二级缓存中获取,然后B这个Bean创建成功,存储到一级缓存的单例池中,同时,将B在二级缓存中的半成品删掉。再回到造A的路上,将B从容器中注入到A,A也创建成功,将A在二级缓存中的半成品也删掉。

在这里插入图片描述

到此,一般对象之间的循环引用到二级缓存就解决了。但还有个场景,如果主要注入的A对象是个代理对象,则需要三级缓存:

在这里插入图片描述

8、构造方法出现循环依赖

Spring三级缓存解决了大部分的循环依赖(set注入时的循环依赖),但如果是构造方法的循环依赖,则需自己处理。如下写法,报错:Is there an unresolvable circular reference?

在这里插入图片描述
Bean的生命周期第一步,是通过BeanDefinition获取方法名,反射去拿到构造函数来(new Instance)实例化一个纯净态的Bean,还没到set依赖注入这一步(依赖注入的方式不是set方法,而是构造函数),就发现需要一个B这个Bean,同理B一开始执行构造方法就需要A这个Bean。此时Spring框架的三级缓存不能解决。需要加 @Lazy

在这里插入图片描述

9、面试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/337312.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

结构体相关习题的补充

结构体相关习题的补充 题目1&#xff1a; 如有以下代码&#xff1a; struct student {int num;char name[32];float score; }stu;则下面的叙述不正确的是&#xff1a;( ) A.struct 是结构体类型的关键字 B.struct student 是用户定义的结构体类型 C.num, score 都是结构体…

Python中Web开发-Django框架

大家好&#xff0c;本文将带领大家进入 Django 的世界&#xff0c;探索其强大的功能和灵活的开发模式。我们将从基础概念开始&#xff0c;逐步深入&#xff0c;了解 Django 如何帮助开发人员快速构建现代化的 Web 应用&#xff0c;并探讨一些最佳实践和高级技术。无论是初学者还…

身份认证与口令攻击

身份认证与口令攻击 身份认证身份认证的五种方式口令认证静态口令动态口令(一次性口令)动态口令分类 密码学认证一次性口令认证S/KEY协议改进的S/KEY协议 其于共享密钥的认证 口令行为规律和口令猜测口令规律口令猜测 口令破解操作系统口令破解Windows密码存储机制Windows密码破…

数据结构-堆排序问题

需要在数组里面进行排序&#xff0c;我们可以采取堆排序对其解决问题 版本1&#xff1a; 创建一个数组等大的堆&#xff0c;把数组里面的数值输入到堆里面进行堆排序&#xff0c;但是这样的弊端就是&#xff0c;不是顺序排序 版本2&#xff1a; 每次我们取堆顶然后打印&#xf…

举个栗子!Tableau 技巧(275):散点图的数值重合怎么办?抖动图来咯

散点图是大家经常使用的分析图表&#xff0c;但是如果出现多个数据点具有完全相同的 X 和 Y 值&#xff0c;多个散点重叠并隐藏后&#xff0c;查看数据就很不方便了。 遇到这种情况&#xff0c;该怎么办&#xff1f;其实可以尝试将数据点稍微抖动一下&#xff01;如下图&#…

MT3045 松鼠接松果

思路&#xff1a; 求x的一个区间&#xff0c;使区间中的松果的最大y坐标和最小y坐标的差至少为D。若有多个区间&#xff0c;则取最小的那个。 即使用单调队列不断维护最大值和最小值。 首先L固定不动&#xff0c;R不断右移&#xff1a; 即若函数f(R)max[L,R]-min[L,R] >…

探秘Flask中的表单数据处理

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、Flask中的表单处理机制 三、Flask表单处理实战 四、处理表单数据的注意事项…

万字解析线控底盘技术

文章出处&#xff1a;汽车学堂Automooc 引言 在当今这个由科技驱动的时代&#xff0c;汽车电动化、智能化已成为汽车行业的热门话题。特斯拉的自动驾驶功能、蔚来的换电模式、以及比亚迪的刀片电池技术&#xff0c;这些创新不仅引领着市场趋势&#xff0c;也推动着消费者对智…

Java常用API(三)

一、Arrays类 1.定义 Arrays是一个用于操作数组的工具类。 2.常用方法 1.toString方法 public class Demo {public static void main(String[] args) {//toString 将数组变成字符串int[] arr {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};System.out.println(Arrays.toString(arr));…

DNS 解析过程

文章目录 简介特点查询方式⚡️1. 浏览器缓存2. 系统缓存&#xff08;hosts文件&#xff09;3. 路由器缓存4. 本地域名服务器5. 根域名服务器6. 顶级域名服务器7. 权限域名服务器8. 本地域名服务器缓存并返回9. 操作系统缓存并返回10. 浏览器缓存并访问流程图 总结 简介 DNS&a…

springboot2+mybatis-plus+vue3创建入门小项目[学生管理系统]02[实战篇]

01学习篇 创建一个 vue 项目 创建这个新的文件夹 创建前端项目 eggbox 数据库 SQL CREATE DATABASE IF NOT EXISTS egg DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; USE egg;CREATE TABLE stu (id INT AUTO_INCREMENT, -- 自增主键name VARCHAR(64) NOT NULL, -- 非空…

如何使用前端表格控件实现多数据源整合?

前言 作为表格产品的典型应用场景之一&#xff0c;几乎所有的行业都会存在类 Excel 报表开发这样的应用场景&#xff0c;而在这些应用场景中&#xff0c;经常会遇见下面的这些痛点&#xff1a; 报表数据往往来自多个不同的数据源&#xff0c;需要报表系统能够同时连接多个数据源…

反VC情绪:加密市场需要新的分布式代币发行方式

GME事件 GME事件反应了社交媒体在金融决策中的影响力&#xff0c;散户投资者群体通过集体行动&#xff0c;改变了很多人对股市的看法和参与方式。 GME事件中&#xff0c;meme扮演了核心角色。散户投资者使用各种meme来沟通策略、激励持股行为&#xff0c;创造了一种反对华尔街…

5. MySQL运算符和函数

文章目录 【 1. 算术运算符 】【 2. 逻辑运算符 】2.1 逻辑非 (NOT 或者 !)2.2 逻辑与运算符 (AND 或者 &&)2.3 逻辑或 (OR 或者 ||)2.4 异或运算 (XOR) 【 3. 比较运算符 】3.1 等于 3.2 安全等于运算符 <>3.3 不等于运算符 (<> 或者 !)3.4 小于等于运算符…

AdroitFisherman模块安装日志(2024/5/31)

安装指令 pip install AdroitFisherman-0.0.29.tar.gz -v 安装条件 1:Microsoft Visual Studio Build Tools 2:python 3.10.x 显示输出 Using pip 24.0 from C:\Users\12952\AppData\Local\Programs\Python\Python310\lib\site-packages\pip (python 3.10) Processing c:\u…

QT加载CAD文件(二)LibreCAD源码编译

一、LibreCAD LibreCAD是一个开源软件&#xff0c;不用破解激活&#xff0c;可以打开编辑DXF格式的文档&#xff0c;软件大小只有二十多M&#xff0c;对于一些比较简单的图纸还是可以胜任的。本文主要讲该软件源码编译。如果了解软件的基本使用可以参考https://blog.csdn.net/…

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning 目前&#xff0c;模型最全的网站是HuggingFace&#xff0c;但是国内需要魔法流量才能访问。另外&#xff0c;现在大模型权重文件都较大&#xff0c;也会浪费不少流量&#xff0c;因此这里推荐使用魔搭社区下…

一文学懂Base64编码原理

前言 Base64编码与ASCII编码一样&#xff0c;也是一种编码方式。不同的是ASCII码采用7位二进制数表示&#xff08;包括大小写字母、数字、标点符号和一些不可见字符&#xff09;&#xff0c;而Base64采用6位二进制数表示&#xff08;包括大小写字母、0~9数字、和/&#xff09;…

Java | Leetcode Java题解之第120题三角形最小路径和

题目&#xff1a; 题解&#xff1a; class Solution {public int minimumTotal(List<List<Integer>> triangle) {int n triangle.size();int[] f new int[n];f[0] triangle.get(0).get(0);for (int i 1; i < n; i) {f[i] f[i - 1] triangle.get(i).get(i…

如何修改开源项目中发现的bug?

如何修改开源项目中发现的bug&#xff1f; 目录 如何修改开源项目中发现的bug&#xff1f;第一步&#xff1a;找到开源项目并建立分支第二步&#xff1a;克隆分支到本地仓库第三步&#xff1a;在本地对项目进行修改第四步&#xff1a;依次使用命令行进行操作注意&#xff1a;Gi…