Spring高频面试题

2023最新版(持续更新)

  • 一、Spring高频面试题
    • 1. Spring框架中的单例bean是线程安全的吗?
    • 2. 什么是AOP,你们项目中有没有用到AOP?Spring中的事务是如何实现的?
    • 3.Spring中事务时效的场景有哪些?
    • 4. Spring中bean的生命周期
    • 5. Spring中bean的循环引用
    • 6. Spring中常见的注解

一、Spring高频面试题

1. Spring框架中的单例bean是线程安全的吗?

前置需要了解的知识:

  • 在Spring框架中我们可以通过@Scope(“singleton”)来将bean设置为单例;
  • singleton:bean在每个SpringIOC容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
    Spring高频面试题_第1张图片

成员变量需要考虑线程安全问题,局部变量不需要考虑线程安全问题。
Spring高频面试题_第2张图片

Spring bean并没有可变状态(例如Service类与Dao类),所以在某种程度上来说Spring的单例bean是线程安全的。

无可变状态的类(无状态的类):判断当前的成员变量能不能被修改,如果不能被修改,则称之为无状态的类,这种无状态的成员变量无线程安全问题。

总结:
Spring框架中的单例bean不是线程安全的!
Spring框架中有一个@Scope注解,默认值是singleton,是单例的。因为一般在spring的bean都是注入无状态的对象,并不会产生线程安全问题,如果在bean中定义了可修改的成员变量,需要考虑线程安全问题,可以使用多例或者加锁来解决。

对面试官的回答:
Spring框架中的单例bean不是线程安全的。
当出现多个用户同时请求一个服务的场景,容器会给每一个请求分配一个线程,多线程会并发的执行该请求对应的业务逻辑(成员方法),如果业务逻辑中有对该单例bean状态的修改(对该单例bean的成员属性进行修改),则必须考虑线程同步问题。

Spring框架并没有对单例bean进行任何的多线程封装处理,关于单例bean的线程安全问题和并发问题需要我们开发者自己去搞定。

比如:我们通常在项目中使用的Spring bean都是不可变状态的(例如Service类与Dao类),所以在某种程度上说Spring的单例bean是线程安全的。

但是如果你的bean是有多种状态(属性可以被修改)的话,就需要自己去保证线程的安全。比较简单的办法就是将单例bean变成多利bean,将@Scope(“”)注解中的值从singleton变更为prototype。

补充:线程安全与并发问题的异同?

并发问题是指多个线程同时访问共享资源时可能出现的问题,例如死锁、竞态条件等。并发问题通常需要使用同步机制来解决,例如锁、信号量等。
线程安全问题是指在多线程环境下,对共享资源的访问不会导致数据不一致或程序崩溃等问题。线程安全问题通常需要使用线程安全的数据结构或同步机制来解决,例如使用线程安全的集合类、使用 synchronized 关键字等。
因此,可以说并发问题是线程安全问题的一种表现形式,但并不是所有的线程安全问题都是并发问题。

2. 什么是AOP,你们项目中有没有用到AOP?Spring中的事务是如何实现的?

什么是AOP?
AOP称为面向切面编程,他主要的应用场景是将哪些与业务无关,但却对多个对象产生影响的公共行为和逻辑(例如事务,日志等),抽取并封装为一个可重用的模块,这个模块被命名为切面(Aspect),减少系统中的重复代码,降低模块间的耦合度,提高系统的可维护性。

spring事务的底层用到了AOP进行实现,而AOP的底层用到了动态代理去实现。

你们项目中有没有用到AOP?

常见的AOP使用场景:

  • 记录操作日志
  • 缓存的处理
  • Spring中内置的事务处理

记录操作日志实现核心:使用aop中的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库中

Spring中的事务是如何实现的?
其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后,根据执行情况提交或者回滚;

3.Spring中事务时效的场景有哪些?

场景一:异常捕获处理
Spring高频面试题_第3张图片
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉了异常,则spring的事务无法洞悉,也就没有进行回滚。
解决方案:在catch块中手动将异常抛出,让spring的 事务能够察觉到即可完成在出现异常是进行事务回滚。

场景二:抛出检查异常
Spring高频面试题_第4张图片
失效的原因:spring默认只会回滚非检查异常(Runtime异常),上方截图代码中抛出的是一个检查异常,因此事务失效;
解决方案:配置rollbackFor属性

@Transaction(rollbackFor = Exception.class)
该配置描述:只要是异常都会进行回滚;

场景三:非public方法导致事务失效
Spring高频面试题_第5张图片
事务失效原因:Spring为方法创建代理,添加事务通知,前提条件是该方法是public的;
解决方案:将方法改为public的即可;

4. Spring中bean的生命周期

前置需要了解的知识
BeanDefinition:
Spring容器在实例化时,会将xml配置的的信息封装成BeanDefinition对象,Spring根据BeanDefinition来创建bean,该对象中有很多的属性来描述bean;
Spring高频面试题_第6张图片

bean的生命周期图解:
Spring高频面试题_第7张图片
回答面试官:

  1. 通过beanDefinition获取bean的定义信息
  2. 调用构造函数,实例化bean对象
  3. bean的依赖注入(通常情况下是通过set方法注入)
  4. 处理一系列的以Aware接口,实现其内部的方法,然后会执行它们
  5. bean的后置处理器BeanProcessor-前置
  6. 初始化方法:InitializingBean、init-method(自定义的一些初始化方法)
  7. bean的后置处理器BeanProcessor-后置
  8. 销毁bean

5. Spring中bean的循环引用

前置需要了解的知识
循环引用出现的场景:
Spring高频面试题_第8张图片
产生循坏依赖的原因,与spring中bean的生命周期有关,直接上图理解:
Spring高频面试题_第9张图片

为了帮助记忆,你可以理解为三个人在搞对象,三角恋,A喜欢B,心里只有B,而B心里只有C,C心里只有A,他们都是非心里那个它不娶,导致三人最后一起在养老院里玩耍
Spring高频面试题_第10张图片

spring中有解决循环依赖的方案:三级缓存,如下图
Spring高频面试题_第11张图片

缓存名称 源码名称 作用
一级缓存 singletonObjects 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
二级缓存 earlySingletonObjects 缓存早期的bean对象,此时的bean生命周期还没有走完
三级缓存 singletonFactories 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

三级缓存解决循环依赖:
一级缓存的作用:限制bean在beanFactory中只存一份,即实现singleton scope,它解决不了循环依赖;

打破循环依赖,需要一级缓存与二级缓存同时使用
二级缓存充当了一个中间人的存在(暂存区)

解决循环依赖的过程描述:

1.首先实例化A对象,此时会得到原始的实例对象A(当前的A对象是一个半成品对象)
2.将半成品对象A存入到二级缓存(earlySingletonObjects )中
3.此时A对象需要注入B对象,B对象此刻还不存在
4.实例化B,对象B的半成品也存入到二级缓存当中,此时二级缓存中有:对象A与对象B的半成品
5.此时对象B需要注入对象A,而对象A还没有初始化完成,单可以从二级缓存中拿出半成品对象A注入到对象B中,此时对象B就可以创建成功,将初始化后的对象存入单例池(一级缓存)中,并清除二级缓存中的半成品对象B;
6.正在初始化的对象A也能够从单例池获取B对象从而创建成功,并将创建成功的对象A存入到一级缓存中,并清除二级缓存中的半成品对象A;Spring高频面试题_第12张图片
注意:一二级缓存只能解决非代理对象的循环依赖问题,对于代理对象的循环依赖问题需要三级缓存才能解决。

代理类出现循环依赖需要三级缓存才能解决:

1.实例化类A,会得到一个原始对象A,原始对象A生成一个ObjectFactory对象:A-ObjectFactory,并放入到三级缓存中;
2.此时对象A需要注入对象B,对象B还未存在;
3.实例化B获得原始对象B,用原始对象B生成一个ObjectFactory对象:B-ObjectFactory,并放入到三级缓存中;Spring高频面试题_第13张图片
4.对象B此时需要注入对象A,但是对象A未初始化完,因此需要去三级缓存中获取A-ObjectFactory,通过A-ObjectFactory去生成对象A的代理(或普通的对象A),放入二级缓存中(此时生成的对象A的代理是一个半成品),对象B从二级缓存中拿出对象A的代理进行注入;
5.对象B完成初始化,存入到一级缓存中;
6.此时对象A可以注入对象B了,对象A(代理对象)也初始化成功,并放入一级缓存中
Spring高频面试题_第14张图片

三级缓存解决产生循环依赖问题是在bean的初始化阶段的循环依赖问题 ,而无法结局bean生成时的循环依赖问题:
通过构造器注入依赖出现的问题,通过三级缓存解决不了,因此需要手动解决:
Spring高频面试题_第15张图片

面试时的回答
spring中bean的循环引用

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或者两个以上的bean相互持有对方,最终形成闭环,比如A依赖于B,B依赖于A;
  • 循环依赖在spring中是允许存在的,spring框架依据三级缓存已经解决了大部分的循环依赖
    • 一级缓存:单例池,缓存已经经历了完整的生命周期、已经初始化完成的bean;
    • 二级缓存:缓存早期的bean对象(生命周期未走完的bean);
    • 三级缓存:缓存的是ObjectFactory,表示工厂,用于创建某个对象;

(然后复述三级缓存解决循环依赖的流程,最好俩都说,记不起来就挑想得起来的说)

构造方法出现了循环依赖怎么解决?
原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的依赖注入
解决方案: 使用@Lazy进行懒加载,什么时候需要对象再进行bean的创建
Spring高频面试题_第16张图片

6. Spring中常见的注解

注解 说明
@Component、@Controller、@Service、@Repository 使用在类上用于实例化bean
@Autowired 使用在字段上用于根据类型依赖注入
Qualifier 结合@Autowired一起使用,用于根据名称进行依赖注入
@Scop 标注Bean的作用范围
@Configuration 指定当前类是一个配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定在初始化容器时要扫描的包
@Bean 使用在方法上,标注将该方法的返回值存储到Spring容器中
@Import 使用@Import导入的类会被Spring加载到IOC容器中
@Aspect、@Before、@After、@Around、@Poincut 用于切面编程(AOP)

你可能感兴趣的:(面试题汇总,spring,java,后端)