Spring 面试高频考点
定位:本附录汇总 Spring Framework 核心面试题约 19 道,覆盖 IoC、AOP、事务、SpEL、事件、校验等核心知识点。每道题给出"一句话核心" + "展开解析",既适合面试前速记,也适合查漏补缺。
一、IoC 与 Bean 基础(5 题)
Q1:IoC 和 DI 有什么区别?
一句话:IoC 是设计原则,DI 是实现手段。
展开:
- IoC(Inversion of Control,控制反转):将对象创建和依赖管理的控制权从程序内部反转给外部容器。
- DI(Dependency Injection,依赖注入):IoC 的具体实现方式之一,容器在运行时通过构造器、Setter 或字段将依赖注入目标对象。
- 其他 IoC 实现方式还包括依赖查找(Dependency Lookup,如
context.getBean()),但 DI 是现代 Spring 的主流。
Q2:BeanFactory 和 ApplicationContext 有什么区别?
一句话:BeanFactory 是"基础版",ApplicationContext 是"旗舰版"。
展开:
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 初始化策略 | 延迟初始化(lazy-init) | 默认立即初始化所有 singleton |
| AOP 集成 | 需手动整合 | 内置支持 |
| 事件机制 | 无 | 支持 ApplicationEvent |
| 国际化 | 无 | 支持 MessageSource |
| Web 环境 | 无 | 支持 WebApplicationContext |
| 资源加载 | 基础 | 支持通配符、多种协议 |
实际开发几乎总是使用 ApplicationContext。
Q3:Spring Bean 的完整生命周期是怎样的?
一句话:实例化 → 填充 → Aware → BPP 前 → 初始化 → BPP 后 → 使用 → 销毁。
展开:
Q4:Spring 如何解决循环依赖?
一句话:singleton + 非构造器注入 → 三级缓存;构造器注入 → 无法自动解决。
展开:
- 三级缓存:
singletonObjects(成品)、earlySingletonObjects(半成品)、singletonFactories(工厂)。 - 当 A 依赖 B、B 依赖 A 时,A 实例化后通过
ObjectFactory提前暴露半成品引用给 B,B 完成后再回来填充 A 的依赖。 - 构造器注入的循环依赖:实例化阶段就需要依赖,无法提前暴露,只能通过
@Lazy延迟注入或重构代码解决。
Q5:Spring 的 Bean 作用域有哪些?singleton 是否线程安全?
一句话:6 种作用域;singleton 本身不保证线程安全,安全与否取决于内部状态是否可变。
展开:
| 作用域 | 说明 |
|---|---|
| singleton | 默认,每个容器一个实例 |
| prototype | 每次请求新建 |
| request | 每个 HTTP 请求一个 |
| session | 每个 HTTP Session 一个 |
| application | 每个 ServletContext 一个 |
| websocket | 每个 WebSocket 会话一个 |
无状态 singleton(如 Service、DAO)是线程安全的;有状态 Bean 需同步或改用 prototype。
二、AOP 与代理(4 题)
Q6:JDK 动态代理和 CGLIB 代理的区别?Spring 如何选择?
一句话:JDK 代理接口,CGLIB 继承子类;Spring Boot 2.x+ 默认优先 CGLIB。
展开:
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 基础 | 实现 InvocationHandler | 生成子类,方法拦截 |
| 要求 | 目标类必须实现接口 | 不要求接口 |
| 限制 | 无法代理类内部 this 调用 | 无法代理 final 类/方法 |
| Spring 默认 | 目标有接口时优先 | Spring Boot 2.x+ 默认 proxyTargetClass=true |
Q7:AOP 的五种 Advice 分别在什么时候执行?
一句话:Before 前,After 后(无论异常),AfterReturning 成功返回后,AfterThrowing 异常后,Around 包围全部。
展开:
Q8:同类内部方法调用为什么 AOP 不生效?如何解决?
一句话:this.method() 调用的是目标对象,不是代理对象。
展开:
- 原因:Spring AOP 基于代理,外部调用走代理对象,内部
this调用绕过代理。 - 解决:① 注入自身代理(
@Autowired自己);②AopContext.currentProxy()获取当前代理;③ 重构避免内部调用。
Q9:@Transactional 失效的常见场景有哪些?
一句话:非 public、内部调用、异常被吞、异步方法、数据库引擎不支持、传播行为不当。
展开:
- 方法不是
public(Spring AOP 无法代理) - 同类内部调用(绕过代理)
- 异常被
catch未抛出(事务无法感知) - 标注在
@Async方法上(异步线程无事务上下文) - 数据库引擎不支持事务(如 MyISAM)
- 传播行为配置错误(如
NEVER或NOT_SUPPORTED)
三、事务与数据访问(3 题)
Q10:Spring 事务的七种传播行为分别是什么?
一句话:REQUIRED 默认加入/新建,REQUIRES_NEW 挂起新建,SUPPORTS 有则加入无则非事务,NOT_SUPPORTED 挂起非事务,MANDATORY 必须有,NEVER 必须无,NESTED 嵌套 savepoint。
展开:
| 传播行为 | 含义 | 典型场景 |
|---|---|---|
| REQUIRED | 当前无事务则新建,有则加入 | 大多数业务方法 |
| REQUIRES_NEW | 挂起当前,新建独立事务 | 日志、审计,必须独立提交 |
| SUPPORTS | 有则加入,无则非事务 | 查询方法 |
| NOT_SUPPORTED | 挂起当前,非事务执行 | 发送通知 |
| MANDATORY | 必须有事务,否则抛异常 | 强制事务上下文 |
| NEVER | 必须无事务,否则抛异常 | 纯查询 |
| NESTED | 在当前事务中创建 savepoint | 部分回滚 |
Q11:Spring 事务隔离级别与数据库隔离级别的关系?
一句话:Spring 的 Isolation 枚举映射到 JDBC 常量,最终由数据库实现。
展开:
DEFAULT:使用数据库默认(MySQL InnoDB = REPEATABLE_READ,Oracle = READ_COMMITTED)READ_UNCOMMITTED/READ_COMMITTED/REPEATABLE_READ/SERIALIZABLE与数据库一一对应- Spring 不实现自己的隔离机制,只是透传配置。
Q12:@Autowired 和 @Resource 有什么区别?
一句话:@Autowired 按类型(Spring 专有),@Resource 按名称(JSR-250 标准)。
展开:
@Autowired:先按类型匹配,有多个时配合@Qualifier按名称精确指定。@Resource:默认按名称(字段名)匹配,找不到再按类型。@Inject:JSR-330 标准,行为与@Autowired基本一致。
四、其他核心机制(4 题)
Q19:什么是 SpEL?常见使用场景?
一句话:Spring Expression Language,运行时查询和操作对象图的表达式语言。
展开:
- 使用场景:
@Value("#{...}")注入、Spring Security 权限表达式、缓存 Key 生成、动态配置计算。 - 示例:
@Value("#{systemProperties['os.name']}")、@Cacheable(key = "#userId + '_' + #region")、@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")。 - 注意:不支持用户输入直接执行(安全风险),高频表达式建议开启编译模式。
Q20:Spring 事件机制中,@EventListener 和 ApplicationListener 有什么区别?
一句话:@EventListener 更灵活(多事件、SpEL 条件、异步、事务绑定),ApplicationListener 是标准接口实现。
展开:
@EventListener:一个类可监听多种事件,支持condition = "#event.amount > 1000",支持@Async和@TransactionalEventListener。ApplicationListener<E>:一个类只能监听一种事件,与 Spring 接口耦合,功能受限。
Q21:Converter 和 Formatter 有什么区别?
一句话:Converter 是通用类型转换,Formatter 是特化的 String↔T 转换,考虑 Locale。
展开:
Converter<S, T>:任意类型 S 到 T 的转换,如StringToIntegerConverter。Formatter<T>:专门处理String ↔ T,常用于 Web 层的日期、数字格式化(考虑本地化)。- Spring Web 层的数据绑定优先使用 Formatter。
Q22:@NotNull、@NotEmpty、@NotBlank 有什么区别?
一句话:@NotNull 防 null,@NotEmpty 防 null+空,@NotBlank 防 null+空白字符。
展开:
| 注解 | null | "" | " " | [] |
|---|---|---|---|---|
| @NotNull | ❌ | ✅ | ✅ | ✅ |
| @NotEmpty | ❌ | ❌ | ✅ | ❌ |
| @NotBlank | ❌ | ❌ | ❌ | N/A |
@NotBlank只能用于 String。
五、综合与进阶(3 题)
Q23:Spring 如何解决同类内部方法调用 AOP 不生效的问题?
一句话:注入自身代理、AopContext.currentProxy()、或重构避免内部调用。
展开:
- 方案 1:
@Autowired注入自己(循环依赖但 Spring 能处理 setter/字段注入)。 - 方案 2:
((MyService) AopContext.currentProxy()).method()(需@EnableAspectJAutoProxy(exposeProxy = true))。 - 方案 3:将方法拆到另一个 Bean 中,通过依赖注入调用。
Q24:@ConfigurationProperties 和 @Value 有什么区别?
一句话:@ConfigurationProperties 批量绑定结构化配置,@Value 单个注入支持 SpEL。
展开:
| 特性 | @ConfigurationProperties | @Value |
|---|---|---|
| 绑定方式 | 批量、结构化 | 单个 |
| 类型转换 | 自动,支持复杂对象 | 简单类型 |
| SpEL 支持 | ❌ | ✅ |
| 松散绑定 | ✅(kebab/camel/underscore) | ❌ |
| 校验 | ✅(@Validated) | ❌ |
| 默认值 | ✅ | ✅(${key:default}) |
Q25:Spring Boot 的自动配置原理是什么?
一句话:基于 @EnableAutoConfiguration,读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,结合条件注解按需注册 Bean。
展开:
spring.factories/AutoConfiguration.imports中注册自动配置类列表。- 条件注解:
@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean等。 - 满足所有条件时,自动配置类中的
@Bean被注册到容器。 - 用户自定义 Bean 优先(
@ConditionalOnMissingBean保证不覆盖)。
备考建议:面试时先给"一句话核心",再视面试官反应决定是否展开。切忌背诵长篇大论,重点展示对机制的理解深度和实际排查经验。