Spring 学习笔记
1.Spring AOP 怎么实现的
AOP 即面向切面编程,是通过代理实现的,主要分为静态代理和动态代理,静态代理就是在程序运行前就已经指定并声明了代理类和增强逻辑,运行时就已经被编译为字节码文件了,而动态代理则是在运行过程中,动态生成代理类增强目标类,主要分为 JDK 和 CGLIB 动态代理,一般在项目中多采用注解方式声明切面,切点以及连接点,Spring 会自动扫描切面,通过切点寻找需要代理增强的类,pring 会根据目标 Bean 的类型来选择代理方式:
- 如果目标类实现了接口,Spring 默认采用 JDK 动态代理,生成实现接口的代理类;
- 如果目标类未实现任何接口,或设置了
proxyTargetClass = true
,则使用 CGLIB 动态代理,生成目标类的子类进行方法增强; - CGLIB 是通过继承并重写目标类中非 final 的方法,在子类中织入增强逻辑并调用父类方法完成的。
2.Spring的两大核心是什么?谈一谈你对IOC的理解? 谈一谈你对DI的理解?
Spring 的两大核心是控制反转(IOC)和面向切面编程(AOP),IOC 的目的是将 Bean 的生命周期管理从程序员手中交由 Spring 管理,传统开发都需要程序员 new 对象并管理对象,IOC 则只需要声明依赖,使程序员专注于业务逻辑的实现,DI 依赖注入则是 IOC 控制反转的具体实现,Bean 交由 Spring 容器管理后,就需要在程序员调用的时候注入进来。
IOC 的一般实现方法有 XML 配置和注解配置,注解配置主要是通过 Component, Service,Bean,Controller,Confuguration 等注解;
DI 的一般实现方法有构造器注入,Setter 注入以及字段注入;
3.Spring 的生命周期?
Spring 的生命周期指的是 Spring 容器从创建、初始化 Bean、管理 Bean 到销毁 Bean 的整个过程。这个过程贯穿了从启动到关闭整个应用的生命周期。
阶段 | 说明 |
1. 实例化 | 通过构造方法或反射创建 Bean 对象 |
2. 依赖注入(DI) | Spring 根据配置注入属性值(@Autowired、@Value、@Resource) |
3. 执行 Aware 接口回调 | 如 BeanNameAware、ApplicationContextAware 获取容器信息 |
4. BeanPostProcessor(前置) | 调用 方法 |
5. 初始化 | - 方法执行 - 实现 - 配置的 执行 |
6. BeanPostProcessor(后置) | 调用 方法 |
7. Bean 使用阶段 | Bean 进入工作状态,参与项目逻辑处理 |
8. 销毁前处理 | - 方法执行 - 实现 - 配置的 执行 |
9. Bean 被销毁 | Spring 容器关闭,释放资源 |
4.Spring 支持 bean 的作用域有几种吗? 每种作用域是什么样的?
Bean 的作用域有单例,原型,request,session,globalSession 五种;
单例是默认作用域,在整个程序生命周期内有且仅有一个实例;
原型作用域则是每次注入或调用 getBean() 都会返回一个新对象;
request 作用域则是在单个请求内有且仅有一个实例 Bean,不同请求中的实例不同;
session 作用域则是在同一个 session 内有且仅有一个实例 Bean,session 关闭后所有 session 作用域的 Bean 销毁;
application 则是一个 ServletContext 对应一个 Bean,整个 Web 应用共享一个实例。
@Component
@Scope("prototype") // 也可以是 singleton, request, session 等
public class MyBean {...
}
5.Spring 事务的实现方式和实现原理
Spring 事务可以通过注解 Transactional 来实现,Spring 事务的实现主要依赖于底层数据库的事务
Spring 事务有两种实现方式:
- 编程式事务管理(使用
TransactionTemplate
) - 声明式事务管理(使用
@Transactional
注解)——最常用
- 编程式事务管理(使用
Spring 声明式事务的核心原理是:基于 AOP(面向切面编程)实现方法级别的事务增强。其底层实现包括以下几个关键点:
- 代理机制
Spring 会为标注了@Transactional
的类或方法创建代理对象:
- 代理机制
- 如果类实现了接口,使用 JDK 动态代理;
- 如果没有实现接口,使用 CGLIB 动态代理。
- 事务拦截器(TransactionInterceptor)
拦截器会拦截所有事务方法,在方法执行前后插入统一事务逻辑:
- 事务拦截器(TransactionInterceptor)
- 获取事务属性(事务传播、隔离级别、是否只读等);
- 调用事务管理器(
PlatformTransactionManager
)开启、提交或回滚事务。
- 事务管理器(TransactionManager)
Spring 并不直接操作数据库事务,而是通过事务管理器统一封装底层操作。常用管理器包括:
- 事务管理器(TransactionManager)
DataSourceTransactionManager
(JDBC/MyBatis)JpaTransactionManager
(JPA/Hibernate)
- 数据库事务控制
Spring 的事务最终会调用底层连接(Connection
)来控制事务,例如:
- 数据库事务控制
java复制代码
conn.setAutoCommit(false); // 开启事务
conn.commit(); // 提交事务
conn.rollback(); // 回滚事务
Spring 的声明式事务是基于 AOP 实现的,它通过为 @Transactional 方法创建代理对象,在方法调用前后统一管理事务。其核心是 TransactionInterceptor 拦截器,它依赖 PlatformTransactionManager 与底层数据库事务交互,最终通过 JDBC 的事务操作实现提交与回滚。这样程序员只需要添加注解即可实现完整的事务控制,极大地简化了开发流程。
6.Spring 框架中都用到了哪些设计模式?
提到 Spring 框架中的设计模式,必不可少的就是其两大核心性质,IOC 和 AOP,IOC 则是通过 Spring 容器管理 Bean,就是使用了工厂方法进行 Bean 的依赖注入,以及 Bean 默认都是单例的,所以采用了单例模式;而 AOP 就是采用了代理模式实现目标类的代理增强;
以及 JDBCTemplate,RedisTemplate 都采用了模板方法,Spring 项目启动时一系列的监听时间,采用了观察者模式。
7.你知道的 Spring 的通知类型有哪些,分别在什么时候执行?
before: 方法执行之前调用(不影响方法执行)
after: 方法执行之后调用(无论是否抛异常都会执行)
around: 方法执行前后都能控制,可手动决定是否执行目标方法
afterReturning: 方法正常返回后调用(无异常时才会执行)
afterThrowing: 方法抛出异常后调用
8.Spring 的对象默认是单例的还是多例的? 单例 bean 存不存在线程安全问题呢?
Spring 的对象默认是单例的,单例 Bean 也存在线程安全问题,比如某个有状态的 Bean,允许不同线程修改该状态,在多线程环境下就会出现状态异常的情况。解决方式包括加锁、使用线程安全结构,或通过 @Scope("prototype")、ThreadLocal 等方式避免共享。
9.@Resource 和 @Autowired 依赖注入的区别是什么? @Qualifier 使用场景是什么?
Resource 和 Autowired 都是 Spring 自动依赖注入的注解,Resource 只能修饰属性,而 Autowired 既可以修饰属性又可以修饰方法,修饰属性就是在容器中寻找对应实例并注入,修饰方法时,则是自动执行该方法。再使用 Resource 依赖注入属性时,Spring 会先通过属性名在容器中查找对应的类,找不到再使用类型匹配,Autowired 则相反;Autowired 也可以通过使用 Qualifier 指定类名称来依赖注入,在容器中存在多个同类型 Bean 时,应使用 @Qualifier 明确指定注入的 Bean。
10.Spring 的常用注解
注解名 | 功能分类 | 用于位置 | 作用说明 |
| 组件定义 | 类上 | 通用组件,注册到容器 |
| Web 控制层 | 类上 | MVC 控制器,返回视图 |
| 业务逻辑层 | 类上 | 标识 Service 层组件 |
| 持久层组件 | 类上 | DAO 层组件,支持异常转换 |
| Web 控制层 | 类上 | 等价于 ,返回 JSON |
| 配置类 | 类上 | 声明 Java 配置类,等同 XML 配置 |
| 注册 Bean | 方法上 | 注册一个手动管理的 Bean 到容器 |
| Bean 扫描配置 | 类上 | 指定包路径,自动扫描 等注解 |
| 依赖注入 | 属性/构造器/方法 | 按类型注入 Bean |
| Bean 精确注入 | 属性/参数 | 指定注入 Bean 的名称,配合 使用 |
| 请求映射 | 类/方法上 | 映射请求路径,支持所有 HTTP 方法 |
| 请求映射 | 方法上 | 映射 GET 请求, 简写 |
| 请求映射 | 方法上 | 映射 POST 请求 |
| 请求映射 | 方法上 | 映射 PUT 请求 |
| 请求映射 | 方法上 | 映射 DELETE 请求 |
| 参数绑定 | 方法参数 | 绑定 URL 路径中的变量值 |
| 参数绑定 | 方法参数 | 绑定请求参数(?id=123) |
| 参数绑定 | 方法参数 | 接收 JSON 请求体,自动反序列化成对象 |
| 响应绑定 | 方法/类上 | 将方法返回值序列化为 JSON 响应体 |
| 事务控制 | 类/方法上 | 开启事务支持,自动提交/回滚 |
| 启动配置 | 主类上 | 组合注解,包含 、 、 |
11.Spring 的事务传播行为
传播行为 | 含义说明 | 常见使用场景 |
(默认) | 如果当前存在事务,则加入;否则新建事务 | 常用(最常用的默认值) |
| 挂起当前事务,新建一个事务 | 重要操作不影响主事务 |
| 有事务就加入,没有就以非事务方式执行 | 读操作(可选事务) |
| 以非事务方式执行,挂起当前事务 | 调用不希望有事务的逻辑 |
| 不能处于事务中,若存在事务就抛异常 | 明确禁止事务的代码 |
| 必须在事务中执行,若没有就抛异常 | 依赖事务的逻辑(如更新) |
| 当前有事务,则在嵌套事务中执行(使用保存点),否则新建事务 | 回滚子逻辑不影响主逻辑 |
12.Spring 中的事务隔离级别
Spring 中事务隔离级别通过 @Transactional(isolation = Isolation.XXX) 来设置,实质上是借助底层数据库(如 MySQL、Oracle)提供的隔离机制。所以分为读未提交,读已提交,可重复读,串行化。
Spring 的事务隔离级别通过 @Transactional
设置,最终由底层数据库实现。Spring 提供 5 种隔离级别,分别对应数据库中的标准隔离等级。默认使用数据库配置(MySQL 是 REPEATABLE_READ)。合理设置隔离级别可以避免脏读、不可重复读、幻读等并发问题。
13.拦截器,过滤器,统一异常处理器的区别
过滤器(Filter)是 Servlet 提供的,在 DispatcherServlet 之前执行,作用范围是所有请求(包括静态资源),一般用作登录校验、请求日志、跨域处理(CORS);
而拦截器(Interceptor)是 Spring MVC 提供的,拦截器在 DispatcherServlet 之后,Controller 之前执行,一般用作权限校验、参数封装、请求预处理/后处理;
统一异常处理器(ControlerAdvice)也是是 Spring MVC 提供的,ControlerAdvice 则是在 Controller 执行时抛出异常后,一般用作统一异常转换成友好 JSON 响应;
当一个请求进入到程序中,需要先进入 Spring 容器,再通过过滤器到 Servlet,最后通过拦截器后才能到控制器,同样返回结果就是根据相反方向流动,统一异常处理器则是当程序允许出现了异常,一路抛出直到被统一异常处理器捕获,它根据异常类型采取不一样的策略。
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = RuntimeException.class)public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {return ResponseEntity.status(500).body("系统错误:" + ex.getMessage());}
}
14.有哪些不同类型的依赖注入实现方式?
DI 的一般实现方法有构造器注入,Setter 注入以及字段注入;
@Component
public class OrderService {private final UserService userService;@Autowiredpublic OrderService(UserService userService) {this.userService = userService;}
}@Component
public class OrderService {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}
}@Component
public class OrderService {@Autowiredprivate UserService userService;
}
构造器注入依赖最明确、安全性高、支持 final,是官方推荐方式;Setter 注入适合可选依赖;字段注入虽然最常见但不利于测试和维护,不推荐在生产中使用。
15.Spring 中事务失效的场景?
1. 自调用:同一个类内部调用自己的 @Transactional
方法 , Spring 的事务是通过代理对象调用生效的,自调用直接调用原始对象,不会走代理。在同一个类内部,方法之间直接调用不会通过 Spring AOP 的代理对象,因此 @Transactional 注解失效。如果外部没有事务,内部也不会开启事务;如果外部已有事务,内部方法虽然注解失效,但其逻辑会“加入”外部事务一起提交或回滚。所以,内部方法的事务传播属性不会生效,所有事务行为以外部为准。
@Service
public class UserService {@Transactionalpublic void outer() {inner(); // 事务不生效}@Transactionalpublic void inner() {// 不会被代理拦截}
}public void outer() {// 无 @Transactional,也没事务开启inner(); // inner 虽然有 @Transactional,但事务失效,也不会开启事务
}
@Transactional
public void inner() {// 实际没有事务!!!
}@Transactional // 外部有事务
public void outer() {inner(); // 调用 inner 时没走代理,@Transactional 失效
}@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {// 事务传播属性不生效,只是加入了 outer 的事务
}
2.方法不是 public 修饰的,Spring 事务 AOP 默认只能拦截 public 方法。非 public 方法(如 private, protected)不会被代理增强。
3. 异常被捕获后未抛出,默认只有 RuntimeException 或其子类 异常才会触发事务回滚;如果捕获了异常但没有重新抛出,就无法触发回滚。
@Transactional
public void save() {try {// 报错了} catch (Exception e) {log.error("异常", e);}// 没有回滚
}
4. 数据库不支持事务或操作本身不在事务控制范围内,比如 DDL 语句(如 CREATE, ALTER)在某些数据库下不会被事务控制;非关系型数据库(如 Redis、Mongo)不支持 Spring 的 JDBC 事务。
5. 未被 Spring 容器管理的类上加了 @Transactional
,如果不是 @Service、@Component、@Controller、@Repository 注解的类,Spring 不会创建代理。
6. 事务传播行为设置不当,比如外层无事务,调用内部 REQUIRES_NEW 方法,会新建事务;但外层异常回滚不会影响内层已经提交的事务。
7. 多线程操作中事务不生效,每个线程是独立的,线程中执行方法不会沿用主线程的事务上下文。在异步任务(如 @Async)中事务失效。
16.JDK 动态代理和 CGLIB 动态代理的区别
Spring AOP 是通过 动态代理 实现增强的。根据目标类是否实现接口,选择不同的代理方式:
JDK 动态代理:要求目标类实现接口;Spring 会生成一个实现目标接口的代理类;通过 InvocationHandler#invoke() 拦截方法调用,执行增强逻辑。
CGLIB 动态代理:目标类没有接口,或强制使用 proxyTargetClass=true;通过生成目标类的子类,并重写非 final 方法实现增强;增强逻辑织入子类方法中,调用时先执行增强逻辑再调用原方法。
17.Spring 中的循环引用
Spring 中的循环依赖指多个 Bean 之间互相引用的现象。Spring 通过三级缓存(singletonObjects、earlySingletonObjects 和 singletonFactories)解决了单例作用域和非构造器注入的循环依赖。但对于构造器注入或原型作用域 Bean 的循环依赖,Spring 无法解决,会抛出异常。
@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate A a;
}
Spring 通过 三级缓存机制解决了大部分单例 Bean 的构造器注入(Setter 或字段注入)循环依赖问题。
缓存名称 | 类型 | 描述 |
一级缓存 |
| 成熟 Bean(完全初始化) |
二级缓存 |
| 半成品 Bean(已实例化未初始化) |
三级缓存 |
| Bean 工厂,用于暴露代理或提前引用 |
- Spring 在创建 Bean A 时,发现它依赖 B;
- 开始创建 B,发现 B 又依赖 A;
- Spring 会从三级缓存提前暴露 A 的实例引用(还未注入属性);
- B 获取到 A 的“早期引用”,顺利注入;
- A、B 最终都能完整实例化,注入完成。
18.Spring 有什么好处
1.解耦 + 降低复杂度(IOC/DI)
核心优势:通过控制反转(IoC)和依赖注入(DI)实现对象管理,解耦对象之间的依赖关系;
你只需要关注“用什么”,而不是“怎么创建”;
减少了硬编码,提升了系统可测试性和扩展性。
2.AOP 支持
面向切面编程(AOP)让你可以统一处理横切关注点,如:事务、日志、安全、权限校验等;
可以在不修改业务代码的前提下,动态增强 Bean 的功能。
3.统一事务管理
Spring 提供了声明式事务管理(@Transactional
),简洁、清晰;
支持多种事务管理器(JDBC、JPA、Hibernate、MyBatis)。
4.丰富的模块和生态支持
Spring 不是一个工具,而是一个完整生态体系。
19. 为什么构造器注入不能解决循环依赖?
Spring 的循环依赖解决机制依赖于 提前暴露“半成品” Bean 实例(early reference):
- 构造器注入是在 Bean 还没创建出来之前就必须准备好所有依赖;
- Spring 此时无法提前放入三级缓存,没有机会暴露半成品;
- 所以只要 A 和 B 都通过构造器互相依赖,Bean 根本创建不出来,就死锁或抛异常。