一、Singleton作用域下Bean的线程安全
Spring 容器可以创建 JavaBean 实例,也可以指定其特定的作用域,Spring Bean的作用域有多种,其中最常用的为:
|-----------|----------------------------------------------------------------------|
| 作用域 | 描述 |
| singleton | 在spring IoC容器仅存在一个Bean实例,整个 Web 应用程序只创建和管理一个 Bean类的实例,bean作用域范围的默认值。 |
| prototype | 应用程序代码每次对Bean发出请求时,Spring 容器都会创建一个新的 Bean 实例来处理请求。 |
那么问题来了,Spring框架中的单例bean是线程安全的吗?
在 Spring 框架中,单例 bean 本身不保证线程安全。
这是因为:
-
Spring 的单例模式仅保证在容器中只存在一个 bean 实例,这个实例会被多个线程共享访问
-
线程安全问题的核心在于:
- 如果单例 bean 是无状态的(没有成员变量或状态数据),通常是线程安全的
- 如果单例 bean 是有状态的(包含可修改的成员变量),则存在线程安全隐患
示例:
如下代码,bean默认是单例的,使用jmeter测试,每秒发送100次请求,查看number是否达到100:
java
@RestController
@RequestMapping("/user")
@Tag(name = "用户接口")
@Slf4j
//@Scope("prototype")
public class UserController {
@Resource
private UserMapper userMapper;
// 成员变量
private int number = 0;
@Operation(summary = "单例bean线程安全测试")
@GetMapping("/threadSafe")
public String threadSafe() {
System.out.println("number:" + ++number);
return "success";
}
}
运行结果:
也是可以看到发生了线程安全问题,证明单例的bean不是线程安全的。
解决单例 bean 线程安全问题的常见方式:
- 避免使用成员变量存储状态
- 使用 ThreadLocal 存储线程私有状态
- 对共享资源进行同步控制(如 synchronized 关键字)
- 改用原型 (Prototype) 作用域
下面我们将作用域改为Prototype,再次运行查看结果:
可以看到因为每次都会创建一个新的bean实例,所以每次的number都为0,可见prototype是线程安全的。
二、AOP相关
Spring中有两个核心:控制反转(IOC)和面向切面(AOP)。
什么是AOP?
AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为"切面"(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
常见的AOP使用场景有:操作日志记录、性能统计和权限控制等等。
关于AOP的详细了解可参考下面文章:面向切面编程-CSDN博客
示例:
案例说明:使用AOP完成操作日志记录
实体类:
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "操作日志实体类")
@TableName("operate_log")
public class OperateLog implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "操作日志ID")
@TableId(type = IdType.AUTO)
private Long id;
@Schema(description = "操作人ID")
private Long operateUser;
@Schema(description = "操作时间")
private LocalDateTime operateTime;
@Schema(description = "操作类名")
private String className;
@Schema(description = "操作方法名")
private String methodName;
@Schema(description = "操作耗时")
private Long costTime;
}
注解:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
切面类:
java
/**
* @author <hL>
* 切面类,用于记录日志
*/
@Component
@Aspect
@Slf4j
public class LogAspect {
@Autowired
private OperateLogMapper logManager;
@Around("@annotation(com.hl.ssmdemo.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID
Long operateUser = 1L;
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//方法运行时间
long begin = System.currentTimeMillis();
//调用原始目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
//操作耗时
Long costTime = end - begin;
//记录操作日志
OperateLog operateLog =
OperateLog.builder()
.operateUser(operateUser)
.operateTime(operateTime)
.className(className)
.methodName(methodName)
.costTime(costTime)
.build();
log.info("AOP记录操作日志: {}" , operateLog);
logManager.insert(operateLog);
return result;
}
}
测试:
在添加用户接口上添加该注解,当调用方法执行后,会将记录方法操作日志:
java
@Log
@Operation(summary = "添加用户")
@PostMapping
public String addUser(@RequestBody User user) {
int insert = userMapper.insert(user);
if (insert > 0) {
return "添加用户成功";
}
return "添加用户失败";
}

三.Spring事务相关
Spring 中的事务实现核心基于AOP(面向切面编程) 和事务管理器 ,通过声明式或编程式方式实现事务控制,核心是将事务管理逻辑与业务逻辑解耦。
关于Spring事务管理的详细了解可参考下面文章:Spring事务管理_java 中的事务管理及其在 spring 中的实现方式-CSDN博客
1.两种事务管理的方式
Spring支持编程式事务管理和声明式事务管理两种方式。
- 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用
- 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

2.事务失效的场景
在 Spring 中,事务失效是开发中常见的问题,主要有以下常见场景:
1. 方法不是 public 修饰的
原因:
Spring 的事务管理基于 AOP 实现,而默认的 AOP 代理(如 JDK 动态代理、CGLIB )对非 public 方法(private、protected、default)的增强会被忽略。这是因为 Spring 的事务拦截器(TransactionInterceptor
)在设计时遵循 Java 语言的访问权限规范,默认只对 public 方法生效。
解决方法:
- 将事务方法改为
public
修饰(推荐,符合 Spring 设计规范)。
2. this调用导致代理失效
原因 :
Spring 事务通过代理对象实现,事务逻辑(开启、提交、回滚)被织入代理对象的方法中。当同一个类中方法 A 直接调用方法 B 时,调用的是目标对象(this)的方法,而非代理对象的增强方法,因此事务逻辑不会被执行。
解决方法:
- 通过代理对象调用:将当前类注入自身(或从 Spring 容器中获取代理对象),再调用事务方法。
案例:
java
@Service
public class UserService {
// 注入自身的代理对象
@Autowired
private UserService userService;
public void saveUser() {
// 通过代理对象调用,事务生效
userService.doSave();
}
@Transactional
public void doSave() {
// 数据库操作
}
}
3.未被 Spring 管理的类
原因 :
@Transactional
的生效依赖 Spring 容器对 Bean 的管理。如果类未被 Spring 扫描(未加@Service
、@Component
等注解),或未通过@Bean
注册到容器中,Spring 无法为其创建代理对象,事务自然失效。
解决方法:
- 确保类被 Spring 管理:添加
@Service
、@Component
等注解,并保证所在包被@ComponentScan
扫描到。 - 若通过 XML 配置,需在配置文件中声明 Bean。
4. 异常被捕获且未重新抛出
原因 :
Spring 事务管理器通过检测方法抛出的异常来决定是否回滚。如果异常被try-catch
捕获且未重新抛出,事务管理器会认为方法执行成功,从而提交事务。
解决方法:
- 捕获异常后重新抛出
案例:
java
@Transactional
public void saveUser() {
try {
// 数据库操作
int i = 1/0;
} catch (Exception e) {
// 处理异常后重新抛出
throw new RuntimeException("操作失败", e);
}
}
5. 异常类型不匹配
原因 :
Spring 事务默认只对RuntimeException
和Error
及其子类回滚。如果方法抛出的是受检异常(如SQLException
、IOException
),或@Transactional(rollbackFor = ...)
指定的异常类型与实际抛出的异常不匹配,事务不会回滚。
解决方法:
- 明确指定需要回滚的异常类型或注解上配置
rollbackFor
属性为Exception
案例:
java
// 对所有Exception及其子类回滚
@Transactional(rollbackFor = Exception.class)
public void saveUser() throws SQLException {
throw new SQLException(); // 事务会回滚
}
四、Bean的生命周期
Spring Bean 的生命周期是指从 Bean 的创建、初始化、使用到最终销毁的完整过程。Spring 容器会负责管理 Bean 的整个生命周期,这个过程大致可分为4 个阶段 和多个关键节点。

1.Bean 生命周期的核心阶段
1. 1 实例化(Instantiation)
- 作用:创建 Bean 的实例(内存分配),相当于调用了 Bean 的构造方法。
- 细节 :Spring 容器通过反射(如
Class.newInstance()
)创建 Bean 对象,此时 Bean 的属性尚未赋值。 - 注意 :若有多个构造方法,Spring 会根据参数匹配选择合适的构造方法(依赖
@Autowired
或 XML 配置的构造参数)。
1.2 属性赋值(Population)
- 作用:为 Bean 的属性设置值(依赖注入,DI)。
- 细节 :Spring 会根据配置(
@Autowired
、@Resource
、XML 的<property>
等),将依赖的其他 Bean 或基本数据类型注入到当前 Bean 的属性中。 - 注意:若属性是其他 Bean,会先触发依赖 Bean 的生命周期(可能存在循环依赖问题,Spring 通过三级缓存解决)。
1.3 初始化(Initialization)
- 作用:对 Bean 进行额外配置或增强,使其达到可使用状态。
- 关键节点 (按执行顺序):
- Aware 接口回调 :若 Bean 实现了
Aware
系列接口(如BeanNameAware
、BeanFactoryAware
、ApplicationContextAware
),Spring 会调用对应的方法注入相关资源:BeanNameAware.setBeanName()
:注入当前 Bean 在容器中的名称BeanFactoryAware.setBeanFactory()
:注入 BeanFactory 容器ApplicationContextAware.setApplicationContext()
:注入 ApplicationContext 容器
- BeanPostProcessor 前置处理 :调用容器中所有
BeanPostProcessor
的postProcessBeforeInitialization()
方法,可对 Bean 进行预处理(如 AOP 代理的准备)。 - 初始化方法执行 :
- 若 Bean 实现了
InitializingBean
接口,调用其afterPropertiesSet()
方法。 - 若在配置中指定了自定义初始化方法(如
@Bean(initMethod="myInit")
或 XML 的init-method
),调用该方法。
- 若 Bean 实现了
- BeanPostProcessor 后置处理 :调用所有
BeanPostProcessor
的postProcessAfterInitialization()
方法,可对 Bean 进行最终增强(如 AOP 代理的创建就在此阶段)。
- Aware 接口回调 :若 Bean 实现了
1.4 销毁(Destruction)
- 作用:容器关闭时,释放 Bean 占用的资源。
- 关键节点 (按执行顺序):
- 若 Bean 实现了
DisposableBean
接口,调用其destroy()
方法。 - 若在配置中指定了自定义销毁方法(如
@Bean(destroyMethod="myDestroy")
或 XML 的destroy-method
),调用该方法。
- 若 Bean 实现了
- 触发时机 :容器关闭时(如
ApplicationContext.close()
),单例 Bean 会被销毁;原型(Prototype)Bean 由用户手动管理,容器不负责销毁。
2.示例
案例说明:在Spring项目中验证bean的生命周期。
项目结构:

引入spring依赖:
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
添加配置类,用于扫描注册bean:
java
@Configuration
@ComponentScan("com.hl.lifecycle")
public class SpringConfig {
}
自定义BeanPostProcessor:
其中后置处理器使用CGLIB代理增强bean。
java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("user")) {
System.out.println("postProcessBeforeInitialization方法执行了->user对象初始化方法前开始增强....");
}
return bean;
}
@Override
public 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() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//执行目标方法
return method.invoke(method,objects);
}
});
//创建代理对象
return enhancer.create();
}
return bean;
}
}
创建bean:
查看该bean生命周期的执行情况。
java
@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方法执行了.........");
}
@Override
public void setBeanName(String name) {
System.out.println("setBeanName方法执行了.........");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("setBeanFactory方法执行了.........");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("setApplicationContext方法执行了........");
}
@PostConstruct
public void init() {
System.out.println("init方法执行了.................");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet方法执行了........");
}
@PreDestroy
public void destory() {
System.out.println("destory方法执行了...............");
}
}
获取bean测试:
java
public class UserTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = ctx.getBean(User.class);
System.out.println(user);
}
}
执行如下:

五、循环引用
Spring 中的循环引用(Circular References)指两个或多个 Bean 之间相互依赖的情况(如 A 依赖 B,B 依赖 A;或 A→B→C→A)。

示例:
java
// ServiceA依赖ServiceB
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
// ServiceB依赖ServiceA
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}

此时ServiceA
和ServiceB
形成了循环依赖,Spring 需要解决 "先创建 A 还是先创建 B" 的矛盾。
1.解决方案
Spring 对单例 Bean 的循环引用通过三级缓存机制解决,核心是 "提前暴露未完全初始化的 Bean",让依赖方可以提前引用。
Spring 在DefaultSingletonBeanRegistry
中维护了三个缓存(Map),用于存储不同状态的 Bean:

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

二级缓存作用:可以解决一般对象的循环依赖。

三级缓存作用:可以解决代理对象的循环依赖。

2.Spring 无法解决的循环引用场景
并非所有循环引用都能被 Spring 解决,以下场景会导致循环引用失败,需要我们手动解决:
2.1. 原型(Prototype)Bean 的循环引用
原型 Bean 的作用域是 "每次获取都创建新实例",Spring 不会缓存原型 Bean,因此无法提前暴露未完成的实例,循环引用时会直接抛出BeanCurrentlyInCreationException
。
解决方法:避免原型 Bean 的循环依赖,或改为单例 Bean。
2.2. 构造器注入的循环引用
若循环依赖通过构造器注入实现,Spring 无法解决。因为构造器注入在 "实例化阶段" 就需要依赖对象,而此时依赖方尚未创建,无法提前暴露。
java
@Service
public class ServiceA {
// 构造器注入ServiceB
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
// 构造器注入ServiceA
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
解决方法 :在构造器注入时添加**@Lazy
**注解,延迟依赖对象的初始化
java
@Service
public class ServiceA {
public ServiceA(@Lazy ServiceB serviceB) { // 延迟加载B
this.serviceB = serviceB;
}
}
六、相关面试问题
1.Spring框架中的单例bean是线程安全的吗?
单例bean不是线程安全的。Spring中并没有封装关于单例bean多线程处理的任何操作,关于多线程的安全操作需要我们自己维护。
线程安全的核心问题在于:
第一,如果单例bean是无状态的,即没有成员变量或状态数据,通常bean是线程安全的。
第二,如果单例bean是有状态的,即包含可修改的成员变量,此bean是可能有线程安全风险的。
2.介绍一下AOP
AOP即面向切面编程,用于将那些与业务无关但却与多个对象产生影响的公共行为和逻辑封装起来,抽取到一个切面中,实现代码的复用,降低耦合。常见的使用场景有:操作日志记录、性能统计和权限控制等等。
3.Spring中的事务是如何实现的?
在spring中事物的核心是AOP完成的。它通过对方法前后进行拦截,在方法执行前开启事物,在方法执行完毕后,根据执行情况决定提交还是回滚事物。
4.Spring中事务失效的场景有哪些?
在项目开发中,Spring中事务失效的场景有很多,我遇到的失效场景有以下几种:
第一,非public调用导致的事物失效,这是因为 Spring 的事务拦截器默认只对public生效。
第二,this调用导致的事物失效,解决方法是通过代理对象调用,如将自身注入,然后调用事物方法。
第三,异常类型不匹配导致的事物失效,若抛出的是检查型异常,在
@Transactional
注解上配置rollbackFor
属性为Exception。
5.请你解释一下Spring中bean的生命周期?
Spring bean的生命周期是指bean在spring容器中创建、初始化、使用到销毁的全过程。其大致分为四个阶段包含多个关键点:
第一阶段为实例化阶段,通过
BeanDefinition获取bean的定义信息,然后调用构造函数实例化bean。
第二阶段为依赖注入阶段,进行bean的依赖注入,例如通过setter方法或@Autowride注解。
第三阶段为初始化阶段,包含四个关键点:第一,处理实现了Aware接口的bean;第二,执行
BeanPostProcessor的前置处理器;第三,调用初始化方法,如实现了InitializingBean接口或自定义的init方法;第四,执行BeanPostProcessor的后置处理器。
第四阶段为销毁阶段,销毁bean,释放 Bean 占用的资源。
6.Spring中的循环引用?
Spring中的循环引用是指两个或两个以上的bean相互依赖,形成闭环。Spring中允许相互依赖,并提供了三级缓存可以解决大多数循环引用的问题。
一级缓存,单例池,缓存已完成初始化的bean对象。
二级缓存,缓存生命周期尚未完成的早期bean对象。
三级缓存,缓存beanfactory,用于创建bean。
7.你知道Spring中解决循环引用的具体流程吗?
Spring中使用三级缓存来解决循环引用,其具体流程如下:
第一,实例化A对象并创建
ObjectFactory存入三级缓存中。
第二,A初始化时需要使用B,B开始实例化并创建
ObjectFactory存入三级缓存中。
第三,B需要使用A,通过三级缓存获取
ObjectFactory,ObjectFactory创建A对象并存入二级缓存中。
第四,B完成初始化创建成功,存入一级缓存。
第五,由于B创建完成,A可以直接注入B,完成创建并存入一级缓存。
第六,清除二级缓存中的临时对象。
8.构造方法出现了循环依赖怎么解决?
可以使用@Lazy懒加载注解,延迟bean的加载直至开始使用。