Spring 面试题笔记
个人复习笔记,覆盖 Spring 核心容器、AOP、事务、MVC 的高频面试题。
目录
- [IOC 与依赖注入](#IOC 与依赖注入)
- [Bean 生命周期与作用域](#Bean 生命周期与作用域)
- 循环依赖
- [AOP 面向切面](#AOP 面向切面)
- [Spring 事务](#Spring 事务)
- [Spring MVC](#Spring MVC)
- [Spring 中的设计模式](#Spring 中的设计模式)
- 事件机制与其他
IOC 与依赖注入
什么是 IOC?什么是 DI?
- IOC(控制反转) :对象的创建、管理、销毁的控制权由程序员手写
new反转给 Spring 容器,是一种设计思想,DI 是它的实现方式。 - DI(依赖注入):容器在创建 Bean 时,自动把依赖的其他 Bean 注入进来(构造器、Setter、字段)。
好处:解耦(依赖面向接口,实现可替换)、便于单测(可注入 Mock)、生命周期统一管理。
BeanFactory 和 ApplicationContext 的区别?
| BeanFactory | ApplicationContext | |
|---|---|---|
| 定位 | 最底层容器接口 | 功能更全的容器,继承自 BeanFactory |
| Bean 初始化 | 懒加载,getBean 时才创建 | 默认预实例化,容器启动即创建所有单例 Bean |
| 附加能力 | 基础的 Bean 管理 | 事件发布、国际化、资源加载、AOP 自动代理 |
有哪几种依赖注入方式?推荐哪种?
- 构造器注入(推荐):依赖不可变(final)、不为 null、便于单测;依赖过多时构造器臃肿,能"闻出"设计问题;
- Setter 注入:适合可选依赖;
- 字段注入 (
@Autowired直接标在字段上):写法最简单,但有缺点------不能声明 final、隐藏依赖关系、脱离容器无法实例化。
Spring 官方推荐构造器注入 ;如果只有一个构造器,可以省略 @Autowired。
@Autowired、@Resource、@Qualifier 的区别?
| @Autowired | @Resource | |
|---|---|---|
| 来源 | Spring 提供 | JDK 提供(JSR-250) |
| 匹配规则 | 先按类型,多个再按名称 | 先按名称,找不到再按类型 |
| 搭配 | @Qualifier 指定名称 | name 属性直接指定 |
同一个接口有多个实现类时:@Autowired + @Qualifier("beanName"),或用 @Resource(name = "beanName"),或字段/参数名恰好等于 Bean 名。
什么是 Bean?
由 Spring 容器实例化、组装、管理的对象。配置来源:XML、JavaConfig(@Configuration + @Bean)、注解扫描(@Component 及衍生注解)。
@Component 和 @Bean 的区别?
@Component:标在类上,靠类路径扫描自动注册,类是自己写的;@Bean:标在方法 上(通常在 @Configuration 类中),方法返回值注册为 Bean,常用于第三方类(拿不到源码无法加 @Component)。
Bean 生命周期与作用域
Bean 的生命周期?(高频)
完整链路可以记成"实例化 → 属性 → 初始化 → 使用 → 销毁":
- 实例化:通过构造器/工厂方法创建对象(反射);
- 属性填充:依赖注入(@Autowired 等);
- Aware 回调 :实现
BeanNameAware、BeanFactoryAware、ApplicationContextAware等拿到容器信息; - BeanPostProcessor 前置处理 :
postProcessBeforeInitialization(@PostConstruct 在这一步附近执行); - 初始化 :
@PostConstruct→InitializingBean.afterPropertiesSet()→ 自定义init-method; - BeanPostProcessor 后置处理 :
postProcessAfterInitialization,AOP 代理就是在这里生成的(AbstractAutoProxyCreator); - 使用中;
- 销毁 :容器关闭时
@PreDestroy→DisposableBean.destroy()→ 自定义destroy-method。
Bean 有哪些作用域?
| 作用域 | 说明 |
|---|---|
singleton |
默认,容器中只有一个实例 |
prototype |
每次 getBean 都新建实例 |
request |
每个 HTTP 请求一个实例(Web 环境) |
session |
每个 HTTP 会话一个实例(Web 环境) |
application |
整个 ServletContext 一个实例 |
websocket |
每个 WebSocket 会话一个实例 |
prototype Bean 注入到 singleton Bean 时,singleton 里的那一份永远是同一个(除非用 @Lookup 或 ObjectFactory 延迟获取)。
单例 Bean 是线程安全的吗?
Spring 不保证。单例 Bean 无状态(只有方法、没有可变的成员变量)时线程安全;有可变状态时会出现并发问题。
解决:成员变量改局部变量、用 ThreadLocal、加锁、或改 prototype 作用域。这也是为什么 Service/Controller 通常写成无状态的。
@PostConstruct、init-method、afterPropertiesSet 的执行顺序?
@PostConstruct → InitializingBean.afterPropertiesSet() → init-method。销毁同理:@PreDestroy → destroy() → destroy-method。
循环依赖
什么是循环依赖?Spring 怎么解决的?(高频)
A 依赖 B,B 又依赖 A。Spring 通过三级缓存 解决 单例 + 字段/Setter 注入 的循环依赖:
| 缓存 | 名称 | 存什么 |
|---|---|---|
| 一级缓存 | singletonObjects | 完整的成品 Bean |
| 二级缓存 | earlySingletonObjects | 提前曝光的半成品(已实例化、未填充属性) |
| 三级缓存 | singletonFactories | 能生成早期引用的 ObjectFactory |
流程:A 实例化后,先把工厂放入三级缓存再填充属性 → 发现需要 B → 创建 B → B 填充属性时需要 A → 从三级缓存拿到 A 的早期引用(升级到二级缓存)→ B 完成创建放入一级缓存 → A 拿到 B 完成初始化。
三级缓存的意义:保证正常情况下 Bean 用原始对象,只有发生循环依赖时才提前生成代理对象,避免 AOP 代理被创建两次。
哪些情况 Spring 解决不了循环依赖?
- 构造器注入的循环依赖:还没实例化完就要注入,无法提前曝光(会抛 BeanCurrentlyInCreationException);
- prototype 作用域:每次都新建,不做缓存;
- 无法用早期引用的场景(如构造完就完成的代理逻辑)。
解决办法:改用 Setter 注入、加 @Lazy 延迟初始化其中一方、或重新设计消除环。
AOP 面向切面
什么是 AOP?解决什么问题?
面向切面编程,把日志、事务、权限、限流等横切逻辑从业务代码中抽离成"切面",通过动态代理织入目标方法。好处:业务代码干净、横切逻辑集中可复用。
AOP 的核心术语?
- 切面 Aspect:横切逻辑的模块化(类);
- 连接点 JoinPoint:程序执行的某个点(Spring 里就是方法执行);
- 切点 Pointcut:匹配连接点的表达式(哪些方法要增强);
- 通知 Advice:增强的逻辑(前置/后置/环绕等);
- 织入 Weaving:把通知应用到目标对象的过程(运行期动态代理实现)。
Spring AOP 的实现原理?
运行期动态代理(编译期织入 AspectJ 才是字节码增强):
- 目标类实现了接口 → JDK 动态代理(Proxy + InvocationHandler);
- 没有接口 → CGLIB(生成目标类的子类,方法不能是 final/static/private)。
Spring Boot 2.x 起默认无论有无接口都用 CGLIB(spring.aop.proxy-target-class=true)。
五种通知类型及执行顺序?
java
@Around // 环绕:包住整个方法,可控制是否执行、修改入参和返回值
@Before // 前置:方法执行前
@After // 后置:方法执行后(finally,无论是否异常)
@AfterReturning // 返回后:正常返回时
@AfterThrowing // 异常抛出后
正常流程(Spring 5.2+):@Around 前半 → @Before → 业务方法 → @AfterReturning → @After → @Around 后半。
异常流程:@Around 前半 → @Before → 抛异常 → @AfterThrowing → @After(@Around 后半不执行,除非 try 住)。
AOP 在切面里能拿到方法参数吗?
能。@Around("execution(* com.demo.service..*(..))") 配合 JoinPoint / ProceedingJoinPoint:
java
@Around("logPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs(); // 入参
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // 执行目标方法
long cost = System.currentTimeMillis() - start;
return result; // 也可修改后返回
}
Spring 事务
Spring 事务的传播行为有哪些?(高频)
同一个事务管理器下,方法之间调用时事务如何传递,共 7 种:
| 传播行为 | 说明 |
|---|---|
REQUIRED(默认) |
有事务就加入,没有就新建 |
REQUIRES_NEW |
挂起当前事务,新建独立事务(常用于"无论主流程成败都要记日志") |
NESTED |
有事务则在其中开嵌套事务(保存点),可部分回滚;没有等同 REQUIRED |
SUPPORTS |
有就加入,没有就非事务执行 |
NOT_SUPPORTED |
挂起当前事务,以非事务执行 |
MANDATORY |
必须在事务中,否则抛异常 |
NEVER |
必须不在事务中,否则抛异常 |
面试重点记前三个:REQUIRED、REQUIRES_NEW、NESTED(嵌套回滚不影响外层已提交部分,外层回滚会带走内层)。
Spring 事务的隔离级别?
直接沿用数据库隔离级别,Spring 提供了 Isolation.DEFAULT(跟随数据库默认)以及 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。MySQL InnoDB 默认 REPEATABLE_READ,Oracle 默认 READ_COMMITTED。
@Transactional 失效场景?(超高频)
- 方法不是 public:代理只拦截 public 方法;
- 同类内部自调用 :
this.methodB()不走代理,事务注解无效------最常见坑。解法:注入自身代理、拆到另一个类、或用 AopContext.currentProxy(); - 异常被 catch 吞掉:事务感知不到异常,不回滚;
- 默认只回滚 RuntimeException 和 Error :抛受检异常不回滚,需要
@Transactional(rollbackFor = Exception.class); - 类没被 Spring 管理(没加 @Service 等);
- 数据库引擎不支持事务(如 MyISAM);
- 多线程调用:事务绑定在 ThreadLocal 的连接上,子线程不在同一事务;
- 传播行为配置错误:如 NOT_SUPPORTED、NEVER;
- 事务方法内新开事务 REQUIRES_NEW 后又抛异常时的回滚范围需理清楚。
Spring 事务的实现原理?
AOP:TransactionInterceptor 环绕目标方法 → 开启事务(获取数据库连接,绑定到 ThreadLocal)→ 执行业务 → 正常提交 / 异常按规则回滚。本质是对 Connection 提交/回滚动作的代理封装。
事务什么时候会导致脏数据?------Propagation.REQUIRED 与 REQUIRES_NEW 嵌套的坑
REQUIRES_NEW 是独立事务独立连接,外层事务未提交时,内层事务已提交的数据对外层不可见(取决于隔离级别);若外层最终回滚,内层已提交的数据不会回滚------设计"必须落库的记录"(审计日志)时用它,但要知道它占两个连接,可能引发连接池耗尽。
Spring MVC
Spring MVC 的请求处理流程?(高频)
- 请求到达 DispatcherServlet(前端控制器,统一入口);
- HandlerMapping 根据 URL 找到对应的 Handler(Controller 方法)及拦截器链;
- HandlerAdapter 适配并执行 Handler(参数绑定、类型转换在这完成);
- 执行业务返回结果,
@ResponseBody时经 HttpMessageConverter 序列化(如 Jackson 转 JSON)直接写响应;传统方式返回 ModelAndView; - ViewResolver 解析视图 → 渲染返回(前后端分离项目基本不走视图解析);
- 触发拦截器 afterCompletion、异常由 HandlerExceptionResolver 处理。
@Controller 和 @RestController 的区别?
@Controller:返回视图名(配合模板引擎);@RestController=@Controller+@ResponseBody:所有方法的返回值直接序列化为 JSON/XML 写入响应体,前后端分离用这个。
拦截器(Interceptor)和过滤器(Filter)的区别?
| Filter | Interceptor | |
|---|---|---|
| 规范 | Servlet 规范 | Spring MVC 提供 |
| 执行时机 | 在 DispatcherServlet 之前 | 在 DispatcherServlet 内部、Handler 前后 |
| 能否拿到 Handler 信息 | 不能 | 能(preHandler 可拿到 HandlerMethod) |
| 能否用 Spring Bean | 理论可以但时机偏早 | 天然支持注入 |
| 典型用途 | 编码、跨域、全站防护 | 登录校验、权限、日志、性能监控 |
执行顺序:Filter → DispatcherServlet → Interceptor.preHandle → Controller → postHandle → afterCompletion → Filter。
@RequestParam、@RequestBody、@PathVariable 的区别?
@RequestParam:取 URL 查询参数或表单字段;@PathVariable:取 RESTful 路径变量/user/{id};@RequestBody:把请求体 JSON 反序列化为对象(POST/PUT);- 响应侧对应
@ResponseBody/ResponseEntity。
全局异常怎么处理?
@RestControllerAdvice + @ExceptionHandler(XxxException.class) 统一捕获转换成标准响应结构,避免每个 Controller 重复 try-catch。
Spring 中的设计模式
Spring 框架用到了哪些设计模式?(高频)
| 模式 | 在 Spring 里的体现 |
|---|---|
| 工厂模式 | BeanFactory / ApplicationContext 创建 Bean |
| 单例模式 | Bean 默认作用域 singleton(容器级单例,不是 JVM 饿汉式那种) |
| 代理模式 | AOP 的 JDK 动态代理、CGLIB |
| 模板方法 | JdbcTemplate、RedisTemplate、RestTemplate 封装固定流程 |
| 观察者模式 | ApplicationEvent / ApplicationListener 事件机制 |
| 适配器模式 | HandlerAdapter 适配不同类型的 Controller |
| 策略模式 | Resource 加载策略、实例化策略 |
| 装饰器模式 | 各类 Wrapper 包装类 |
| 责任链模式 | MVC 拦截器链、AOP 通知链 |
事件机制与其他
Spring 的事件机制是什么?
基于观察者模式:发布者发布 ApplicationEvent,监听器通过 @EventListener 或实现 ApplicationListener 接收。
java
// 定义事件
public class OrderCreatedEvent extends ApplicationEvent { ... }
// 发布
applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));
// 监听
@EventListener
public void onOrderCreated(OrderCreatedEvent event) { ... }
@Async @EventListener // 加 @Async 可异步执行、与发布者事务解耦
用途:业务解耦(下单成功后发短信、加积分),常配合 @TransactionalEventListener 做"事务提交后再执行"。
@Lazy 有什么用?
延迟初始化:Bean 不在容器启动时创建,第一次被使用时才创建。用途:加快启动、打破循环依赖(在依赖点标 @Lazy 注入一个代理,真正调用时才获取 Bean)。
BeanPostProcessor 和 BeanFactoryPostProcessor 的区别?
- BeanFactoryPostProcessor :在 Bean 实例化之前修改 Bean 定义信息(BeanDefinition),如占位符解析 PropertySourcesPlaceholderConfigurer;
- BeanPostProcessor :在 Bean 初始化前后加工 Bean 实例,如 AOP 代理生成、@Autowired 处理(AutowiredAnnotationBeanPostProcessor)。