1. 为什么需要Spring面试题整理
作为Java开发者,Spring框架几乎是面试必考内容。我经历过数十场技术面试,发现面试官对Spring的考察往往集中在几个核心领域:IoC容器、AOP原理、事务管理、Spring MVC和Spring Boot特性。这些知识点看似基础,但想要回答得深入却需要系统性的准备。
记得我第一次面试时,被问到"Spring如何处理循环依赖"这个问题,虽然知道三级缓存的概念,但解释得支离破碎。后来我花了整整两周时间,把Spring源码中相关的部分都啃了一遍,才真正理解了DefaultSingletonBeanRegistry这个核心类的工作机制。这种深度理解在后来的面试中给了我很大帮助。
2. Spring核心概念面试题解析
2.1 IoC容器工作原理
Spring框架的核心是IoC容器,面试中最常被问到的就是Bean的生命周期。完整的生命周期包括:
- 实例化Bean(调用构造方法)
- 属性赋值(populateBean)
- 初始化(invokeInitMethods)
- 使用
- 销毁
其中最容易混淆的是各种扩展点的执行顺序:
- BeanPostProcessor的postProcessBeforeInitialization
- @PostConstruct注解方法
- InitializingBean的afterPropertiesSet
- BeanPostProcessor的postProcessAfterInitialization
提示:面试时如果能画出完整的生命周期流程图,并解释每个扩展点的作用,会给面试官留下深刻印象。
2.2 AOP实现原理
Spring AOP的面试问题通常集中在代理模式的选择上:
- JDK动态代理:基于接口,通过Proxy.newProxyInstance创建代理对象
- CGLIB:基于继承,通过Enhancer创建子类代理
关键区别:
- JDK代理要求目标类实现接口
- CGLIB不能代理final方法和类
- Spring Boot 2.x开始默认使用CGLIB
实际应用中,我曾经遇到一个性能问题:在高并发场景下,CGLIB创建的代理对象初始化较慢。后来通过配置@Scope("prototype")解决了这个问题。
3. Spring事务管理深度解析
3.1 事务传播行为
Spring定义了7种传播行为,但实际开发中最常用的是:
- REQUIRED(默认):如果当前存在事务,则加入该事务;否则新建事务
- REQUIRES_NEW:新建事务,如果当前存在事务则挂起
- NESTED:如果当前存在事务,则在嵌套事务内执行
常见面试问题:"REQUIRES_NEW和NESTED有什么区别?"
答案要点:
- REQUIRES_NEW会启动全新的独立事务
- NESTED是嵌套事务,外层事务回滚会导致内层回滚
- NESTED依赖于保存点机制,数据库必须支持
3.2 事务失效场景
在实际项目中,事务失效是最容易踩的坑。常见原因包括:
- 方法非public
- 自调用问题(this.method())
- 异常类型不匹配(默认只回滚RuntimeException)
- 数据库引擎不支持(如MyISAM)
我曾经遇到一个典型案例:在@Transactional方法中捕获了异常但没有重新抛出,导致事务没有回滚。解决方法是在catch块中手动抛出RuntimeException。
4. Spring MVC面试要点
4.1 请求处理流程
Spring MVC的核心流程可以用以下关键类来描述:
- DispatcherServlet:前端控制器
- HandlerMapping:请求到处理器的映射
- HandlerAdapter:实际调用处理器
- ViewResolver:视图解析
面试时经常被问到:"@RequestMapping和@GetMapping有什么区别?"
答案要点:
- @GetMapping是@RequestMapping(method=GET)的缩写
- @RequestMapping更灵活,可以定义多种HTTP方法
- Spring 4.3开始引入专用注解(@GetMapping等)
4.2 参数绑定原理
Spring MVC的参数绑定机制非常强大,支持多种参数类型:
- 基本类型和String:自动转换
- 复杂对象:自动绑定表单字段
- @RequestParam:明确指定参数名
- @RequestBody:绑定请求体(JSON/XML)
一个实用技巧:当需要处理日期参数时,可以注册自定义的Formatter:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
5. Spring Boot高级特性
5.1 自动配置原理
Spring Boot的魔法来自于@SpringBootApplication注解,它实际上是三个注解的组合:
- @Configuration:标记为配置类
- @ComponentScan:启用组件扫描
- @EnableAutoConfiguration:启用自动配置
自动配置的核心机制:
- spring.factories文件中定义自动配置类
- @Conditional系列注解控制条件加载
- 通过spring-autoconfigure-metadata.properties优化加载过程
我曾经通过自定义自动配置类,实现了公司内部中间件的自动集成,大大简化了项目配置。
5.2 启动过程分析
Spring Boot应用的启动过程可以分为几个关键阶段:
- 创建SpringApplication实例
- 运行run方法
- 准备Environment
- 创建ApplicationContext
- 刷新上下文(关键步骤)
- 调用ApplicationRunner和CommandLineRunner
一个有用的调试技巧:添加--debug参数启动应用,可以查看自动配置的决策过程:
bash复制java -jar myapp.jar --debug
6. 性能优化与最佳实践
6.1 组件扫描优化
默认的@ComponentScan会扫描主类所在包及其子包,但这可能导致:
- 启动时间变长
- 内存消耗增加
- 意外的bean冲突
优化方案:
java复制@ComponentScan(basePackages = {"com.myapp.service", "com.myapp.repository"})
6.2 合理使用Bean作用域
Spring默认使用单例作用域,但在以下场景需要考虑其他作用域:
- 有状态的控制器:使用request作用域
- 线程不安全的组件:使用prototype作用域
- 会话相关数据:使用session作用域
我曾经遇到一个并发问题:在单例Bean中注入了request作用域的Bean,导致数据混乱。解决方法是在单例Bean中使用方法注入:
java复制@Autowired
private ObjectFactory<MyRequestScopedBean> beanFactory;
public void doWork() {
MyRequestScopedBean bean = beanFactory.getObject();
// 使用bean
}
7. 常见面试问题与回答技巧
7.1 源码级问题示例
"Spring是如何解决循环依赖的?"
完整回答应该包括:
- 三级缓存的概念(singletonObjects、earlySingletonObjects、singletonFactories)
- 提前暴露对象引用的机制
- 构造函数注入不支持循环依赖的原因
- AOP代理对象在循环依赖中的特殊处理
7.2 设计思想问题示例
"Spring框架的设计体现了哪些设计模式?"
典型答案:
- 工厂模式:BeanFactory
- 代理模式:AOP实现
- 模板方法:JdbcTemplate等
- 观察者模式:ApplicationEvent
- 适配器模式:HandlerAdapter
在准备这类问题时,最好能结合具体的源码实现来讲解,比如展示ProxyFactoryBean的类图。
8. 实战经验分享
8.1 自定义Starter开发
公司内部中间件通常需要封装为Spring Boot Starter。开发步骤:
- 创建autoconfigure模块
- 编写配置类(@Configuration)
- 定义条件注解(@Conditional)
- 添加spring.factories文件
- 创建starter模块(依赖autoconfigure)
关键点:
- 使用@ConfigurationProperties绑定配置
- 提供合理的默认值
- 完善的文档和示例
8.2 性能调优案例
在一次性能优化中,我发现Spring Boot应用启动特别慢。通过以下步骤解决了问题:
- 使用Spring Boot Actuator的startup端点分析
- 发现自动配置耗时占比高
- 通过@ImportAutoConfiguration排除不必要的自动配置
- 使用spring.autoconfigure.exclude属性
最终启动时间从15秒降低到6秒。这个案例教会我:不要盲目接受框架的默认行为,要根据实际需求进行定制。