本轮 SpringBoot2核心功能面试题库(带答案)
【面试题】1. @SpringBootApplication 注解背后到底做了哪三件事?如果不用它,怎么手动等价实现?
【标准答案】它是个三合一组合注解,拆开就是 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan。@SpringBootConfiguration 本质就是 @Configuration,标记当前类是配置类;@EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 去读 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载所有自动配置类;@ComponentScan 默认扫描当前类所在包及子包。手动等价实现:在主类上分别写 @Configuration、@EnableAutoConfiguration、@ComponentScan 三个注解,效果一模一样。面试官你说不用它也能跑,其实就是为了省事——99% 的项目都这么写,没人手动拆。
【面试题】2. SpringBoot 加载 application.yml 和 application.properties 的优先级顺序是怎样的?如果两者同时存在,谁生效?
【标准答案】SpringBoot 按固定顺序去搜配置文件:项目目录下的 config 子目录最优先(./config/application.yml),其次是项目根目录(./application.yml),然后是 classpath 下的 config 包(classpath:/config/application.yml),最后才是 classpath 根目录(classpath:/application.yml)。当 application.yml 和 application.properties 同时存在时,.properties 优先级高于 .yml——这是 SpringBoot 源码里 ConfigFileApplicationListener 硬编码的加载顺序。更上层还有命令行参数 > 系统属性 > 环境变量 > 外部化配置这些覆盖层级。实战中我一般统一用 yml,团队规范 > 语法糖。
【面试题】3. BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别?分别在 Bean 生命周期的哪个阶段执行?
【标准答案】BeanFactoryPostProcessor 执行得更早——它在所有 BeanDefinition 注册完成之后、Bean 实例化之前触发,可以修改 BeanDefinition 的元数据(比如改 scope、改属性值)。典型例子是 PropertySourcesPlaceholderConfigurer,它把 ${} 占位符替换成真实值,就发生在这个阶段。BeanPostProcessor 则是在每个 Bean 实例化完成、属性填充之后触发,它不碰定义,而是对 Bean 实例做增强——比如生成代理对象、注入依赖。前者操作的是"蓝图",后者操作的是"成品"。面试官常问的 AOP 代理创建,其实就是 AbstractAutoProxyCreator 这个 BeanPostProcessor 干的。
【面试题】4. 用 @ConfigurationProperties 和 @Value 绑定配置有什么区别?为什么说前者的校验能力更强?
【标准答案】@Value 是逐个字段绑定的,支持 SpEL 表达式,适合零星几个配置项的场景;@ConfigurationProperties 是批量绑定——把 prefix 相同的一组配置映射到一个 Java Bean 的字段上,支持松散绑定(last-name = lastName = LAST_NAME 都能识别),而且可以配合 @Validated + JSR303 注解(@NotNull、@Min、@Email)直接做校验。最核心的区别:@ConfigurationProperties 支持复杂类型——List、Map、嵌套对象都能映射,@Value 写 List 得一坨 SpEL 表达式,极其痛苦。实战口诀:超过 3 个同前缀的配置就用 @ConfigurationProperties,省心又安全。
【面试题】5. SpringBoot AOP 默认用 JDK 动态代理还是 CGLIB?什么情况下必须用 CGLIB?
【标准答案】SpringBoot 2.x 通过 spring.aop.proxy-target-class=true 默认启用了 CGLIB 代理。历史版本里 Spring 默认对实现了接口的 Bean 用 JDK 动态代理,没实现接口的才走 CGLIB;但 SpringBoot 2.0 之后统一改成了 CGLIB——原因是 JDK 代理只能代理接口方法,如果同一个 Bean 里有非接口方法需要被增强就代理不了,容易出线上事故。当然你可以改回 JDK:spring.aop.proxy-target-class=false。实际开发中基本没人改,CGLIB 性能已经跟 JDK 代理差不多了。
【面试题】6. SpringBoot 事件机制中,@EventListener 和实现 ApplicationListener 接口有什么不同?异步事件怎么做?
【标准答案】@EventListener 是注解驱动的,更简洁、一个方法就能搞定,而且支持 SpEL 条件过滤(@EventListener(condition="#event.type'order'")),还能通过 @Order 控制多个监听器的执行顺序。实现 ApplicationListener 接口是传统写法,需要强耦合接口抽象,现在基本被 @EventListener 取代。要发异步事件分两步:(1) 在启动类上加 @EnableAsync;(2) 在 @EventListener 方法上再加 @Async 注解。注意异步事件拿不到调用者的事务上下文,如果你要在事件里写库,要么新开事务,要么老老实实发同步事件。
【面试题】7. SpringBoot 怎么把 Tomcat 换成 Undertow?换成之后有什么实际收益?
【标准答案】两步:先在 pom.xml 里排除 spring-boot-starter-web 中的 tomcat 依赖——用 exclusions 把 spring-boot-starter-tomcat 排除掉;再加 spring-boot-starter-undertow 依赖。Undertow 的优势在于轻量级、高并发下的内存占用更低——它是 NIO 全异步设计,不需要像 Tomcat 那样给每个请求分配线程,所以在 WebFlux 场景或者高并发短连接场景下表现更好。但 Undertow 对 Servlet 3.1+ 规范完全兼容,所以你的拦截器、过滤器、SpringMVC 代码一行不用改。我的建议:如果不是压测发现 Tomcat 是瓶颈,别换——生态成熟度还是 Tomcat 更稳。
【面试题】8. @Transactional 的 propagation 有哪几种?REQUIRES_NEW 和 NESTED 的核心区别是什么?
【标准答案】一共 7 种传播行为:REQUIRED(默认,有就用当前的、没有就新建)、REQUIRES_NEW(不管有没有都新建一个独立事务,挂起当前的)、NESTED(嵌套事务,通过 savepoint 回滚到某个点)、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER。REQUIRES_NEW 和 NESTED 的区别很关键:REQUIRES_NEW 是完全独立的新事务,外层事务回滚不影响它;NESTED 是子事务,外层回滚会带着它一起回滚,但子事务回滚可以只回滚到 savepoint 不影响外层。面试官最喜欢问:订单主流程和记录日志——记录日志用 REQUIRES_NEW,这样订单回滚不影响日志落库。注意 NESTED 只有 JPA/JDBC 支持,Hibernate 纯 JPA 模式下不保证能跑。
【面试题】9. @Scheduled 定时任务默认是单线程还是多线程?如果不配置线程池会有什么坑?
【标准答案】默认是单线程!所有 @Scheduled 任务都由同一个线程串行执行,这是 Spring 的 ThreadPoolTaskScheduler 默认 poolSize=1 导致的。举个例子:你配了一个每 5 秒的任务和一个每 3 秒的任务,如果第一个任务执行了 10 秒,第二个任务就得等它跑完才能执行——任务之间会互相阻塞。解决方案是实现 SchedulingConfigurer 接口,重写 configureTasks 方法,手动 setPoolSize 或者注入一个自定义线程池。我更推荐用 @EnableAsync + @Async 组合来做定时任务,这样借助 Async 的线程池配置更灵活,还能控制拒绝策略。
【面试题】10. Actuator 的 /health 端点怎么自定义健康检查?什么情况下应该暴露全部端点?
【标准答案】实现 HealthIndicator 接口,重写 health() 方法即可。方法里用 Health.up().withDetail("status", "ok").build() 或 Health.down().withDetail("error", "连接超时").build() 来上报状态。SpringBoot 会自动把检测到的新 HealthIndicator 聚合到根 /health 结果里。关于端点暴露:开发环境配 management.endpoints.web.exposure.include=* 方便调试;生产环境只能暴露 health 和 info,敏感端点(env、beans、threaddump、heapdump)必须关掉或加 Spring Security 鉴权。千万别图省事全开——env 端点能看到数据库密码的明文,线上泄一次够你年终奖泡汤的。