一、 Spring框架的面试热点综述
随着企业级应用开发的演进,Spring框架的面试重点已从简单的XML配置转向了自动装配、条件化配置以及源码级理解。根据对各大招聘平台及面试反馈数据的分析,以下三个维度是面试官最关注的“信息增益”点:
⚡ IOC容器深度理解
不再局限于“什么是依赖注入”,而是追问BeanFactory与ApplicationContext的区别,以及循环依赖的三级缓存解决方案。这是考察候选人是否阅读过源码的关键。
⚙️ AOP动态代理机制
重点考察JDK动态代理与CGLIB的区别,以及在Spring 5+中的默认选择策略,以及AOP在事务管理中的具体应用。
🔗 事务传播机制
PROPAGATION_REQUIRED与PROPAGATION_REQUIRES_NEW的区别,以及事务失效的常见场景(如自调用、异常捕获等),是高频考点。
二、 IOC容器的核心原理与源码解析
Spring框架的面试中,IOC(控制反转)是必问知识点。面试官通常希望听到关于IoC容器初始化过程的详细描述。
2.1 BeanFactory 与 ApplicationContext 的区别
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 实例化策略 | 懒加载(Lazy Load),获取Bean时才实例化 | 默认饿加载(Eager Load),启动时实例化所有单例Bean |
| 功能丰富度 | 仅仅提供基本的DI功能 | 集成AOP、消息资源处理、事件传播、多种应用层上下文 |
| 异常处理 | 配置错误在启动时可能不暴露 | 启动时会抛出所有配置和实例化异常 |
2.2 解决循环依赖的三级缓存机制
在Spring框架的面试中,循环依赖是一个经典难题。Spring通过三级缓存解决了单例Bean的循环依赖问题。
// 伪代码展示三级缓存
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期的Bean引用(尚未填充属性)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放ObjectFactory,用于生成代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
解析:当Bean A依赖Bean B,Bean B依赖Bean A时。Spring先实例化A,将A的ObjectFactory放入三级缓存。当A填充属性时发现依赖B,转而实例化B。B实例化后发现依赖A,去缓存中找。如果A没有代理,直接去二级缓存取;如果A需要代理,则通过三级缓存的Factory创建代理对象放入二级缓存。这样既保证了AOP代理的正确性,又解决了循环依赖。
三、 AOP技术:动态代理与实现原理
AOP(面向切面编程)是Spring框架的面试中考察代码解耦能力的重要环节。
基于接口的代理
JDK动态代理是基于接口的。它利用java.lang.reflect.Proxy类和InvocationHandler接口,在运行时动态生成实现类。
- 要求目标类必须实现接口。
- 性能在JDK 1.8+之后有显著提升,接近CGLIB。
- 代码侵入性低,但无法代理未实现接口的类。
基于继承的代理
CGLIB(Code Generation Library)通过字节码处理库ASM,在内存中生成目标类的子类。
- 不要求目标类实现接口。
- 如果目标类被final修饰,则无法使用CGLIB。
- 通过重写方法来实现增强,因此目标方法不能是final的。
Spring 5+ 的默认策略
在Spring Framework 5之后,Spring默认优先使用JDK动态代理。如果目标类没有实现接口,则回退到CGLIB。这一变化主要是出于对JDK动态代理性能优化的认可,以及减少对外部库的依赖。
// 配置强制使用CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
四、 Bean生命周期:从创建到销毁的完整旅程
理解Bean的生命周期是掌握Spring框架的面试中高级问题的基础。面试官常问:“你能描述一下一个Bean从Spring容器中获取的全过程吗?”
Spring容器通过构造器或工厂方法创建Bean的实例。此时Bean只是一个空壳,属性未填充。
使用依赖注入(DI)填充Bean的属性。如果存在Aware接口(如BeanNameAware, ApplicationContextAware),在此阶段回调。
① BeanNameAware/BeanFactoryAware回调:获取Bean名称或工厂引用。
② BeanPostProcessor.postProcessBeforeInitialization:前置处理,允许修改Bean实例。
③ InitializingBean.afterPropertiesSet:实现该接口的自定义初始化逻辑。
④ @PostConstruct注解方法:JSR-250标准的初始化方法。
⑤ 自定义init-method:XML或注解配置的初始化方法。
⑥ BeanPostProcessor.postProcessAfterInitialization:后置处理,AOP代理通常在此阶段生成。
Bean处于可用状态,供应用程序调用。
容器关闭时触发。
① DisposableBean.destroy:实现该接口的销毁逻辑。
② @PreDestroy注解方法:JSR-250标准的销毁方法。
③ 自定义destroy-method:配置的销毁方法。
五、 Spring事务管理:传播行为与隔离级别
事务管理是Spring框架的面试中区分初级与中高级开发者的关键。
5.1 事务传播行为(Propagation)
| 常量 | 描述 | 场景示例 |
|---|---|---|
| REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。这是默认值。 | 大多数业务场景,如用户注册、订单创建。 |
| REQUIRES_NEW | 创建一个新事务,如果当前存在事务,则把当前事务挂起。 | 日志记录、审计操作。即使外层事务回滚,日志也必须提交。 |
| SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。 | 查询操作,非核心业务逻辑。 |
5.2 事务失效的常见场景
在Spring框架的面试中,问到事务失效是陷阱题。以下是常见原因:
- 方法非public:Spring事务管理器默认只代理public方法。
- 自调用:类内部方法A调用方法B,B上的@Transactional无效,因为绕过了代理对象。
- 异常被捕获:如果在try-catch中吞掉了异常,事务管理器无法感知异常,不会回滚。
- 数据库引擎不支持:如MySQL使用MyISAM引擎,不支持事务。
六、 网友们还关心:Spring框架的面试高频问答
基于搜索引擎数据与社区讨论,以下是求职者最关注的Spring框架的面试周边问题及深度解答。
Q1: Spring Boot 自动装配(Auto-Configuration)是如何实现的?
Spring Boot的自动装配核心在于@SpringBootApplication注解,它组合了@EnableAutoConfiguration。
原理:
- @EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入选择器。
- 选择器读取META-INF/spring.factories文件(Spring Boot 2.7+改为org.springframework.boot.autoconfigure.AutoConfiguration.imports)。
- 加载所有配置类,并通过@Conditional注解(如@ConditionalOnClass, @ConditionalOnMissingBean)进行过滤。
- 只有满足条件的配置类才会被注册到Spring容器中。
Q2: Spring 中的 Bean 作用域(Scope)有哪些?
在Spring框架的面试中,除了常见的Singleton(单例)和Prototype(原型),还需了解:
- Request:每个HTTP请求创建一个Bean实例,仅在Web应用中使用。
- Session:每个HTTP Session创建一个Bean实例。
- Application:每个ServletContext创建一个Bean实例。
- WebSocket:每个WebSocket会话创建一个Bean实例。
Q3: 如何自定义一个 Spring Starter?
自定义Starter是进阶技能。步骤如下:
1. 创建Auto-configure模块
编写配置类,使用@Configuration和@Conditional注解。在META-INF/spring.factories中注册。
2. 创建Starter模块
这是一个空模块,仅通过依赖引入Auto-configure模块和业务依赖,方便用户一键引入。
3. 命名规范
官方建议命名为spring-boot-starter-xxx,自定义模块命名为xxx-spring-boot-starter。
七、 拓展:Spring框架的面试中的架构思维
对于高级职位,面试官不仅关注API的使用,更关注Spring框架的面试中体现的架构设计能力。
7.1 为什么Spring推荐使用构造器注入而非Setter注入?
① 不可变性:构造器注入可以让依赖字段声明为final,保证Bean实例化后依赖不可变,线程安全。
② 完整性:构造器注入确保Bean在创建时所有依赖都已就绪,避免了Setter注入可能出现的部分依赖缺失问题。
③ 测试友好:便于单元测试,无需依赖Spring容器即可创建Bean实例。
7.2 Spring 如何解决大数据量下的性能问题?
① 懒加载:将非核心Bean设置为懒加载,减少启动时间和内存占用。
② 异步处理:利用@Async注解,将耗时操作(如发邮件、生成报表)放入线程池异步执行。
③ 缓存机制:结合@Cacheable与Redis/Caffeine,减少数据库查询压力。
④ 分库分表:虽然Spring本身不直接分库分表,但可通过集成ShardingSphere等中间件实现,Spring负责事务协调。