SpringBoot2核心功能面试题库
生成时间:2026-05-21 22:14
【面试题】1. SpringBoot的@SpringBootApplication注解由哪些注解组合而成?各自的作用是什么?
【标准答案】@SpringBootApplication是一个组合注解,由三个核心注解构成:
- @SpringBootConfiguration:本质就是@Configuration,标识当前类是配置类,可以声明@Bean方法。SpringBoot用自己注解是为了语义更清晰。
- @EnableAutoConfiguration:自动配置的核心开关,通过@Import导入AutoConfigurationImportSelector,利用SpringFactoriesLoader从spring.factories和AutoConfiguration.imports文件加载所有自动配置类的全限定名,再根据条件注解过滤出生效的配置。
- @ComponentScan:包扫描,默认扫描当前类所在包及其子包下所有@Component/@Service/@Repository/@Controller等注解标注的Bean。这就是为什么启动类要放在根包下。
面试加分点:可以说出exclude属性可以排除特定自动配置类,@ComponentScan的扫描范围可通过scanBasePackages自定义。
【面试题】2. @ConditionalOnMissingBean的判断时机是什么?和@Bean方法的执行顺序有关系吗?
【标准答案】@ConditionalOnMissingBean在BeanDefinition注册阶段判断,也就是BeanFactoryPostProcessor执行期间,远早于Bean实例化。
关键理解:
- 条件注解的判断发生在配置类解析时,Spring会检查容器中是否已存在指定类型的BeanDefinition,而不是Bean实例。
- 如果用户自己定义了一个@Bean方法返回DataSource,那么自动配置类里@ConditionalOnMissingBean标注的DataSource就不会注册BeanDefinition,根本不会进入实例化阶段。
- 顺序问题:SpringBoot保证用户自定义的@Bean优先于自动配置类被解析,所以@ConditionalOnMissingBean判断时,用户的BeanDefinition已经注册了,能正确生效。这是通过AutoConfigurationImportSelector在@Configuration阶段最后处理自动配置类来保证的。
面试陷阱:有人会误以为@ConditionalOnMissingBean是在运行时判断实例是否存在,实际上它在Bean定义阶段就完成了,性能开销几乎为零。
【面试题】3. application.yml和application.properties同时存在会怎样?SpringBoot的配置加载优先级是怎样的?
【标准答案】同时存在时,properties优先级高于yml,但SpringBoot会把两者都加载,相同key以properties为准,不同key会合并。
完整优先级从高到低(核心记忆口诀:命令行 > 外部配置 > 内部配置):
- 命令行参数(--server.port=8081)
- JNDI属性
- Java系统属性(System.getProperties())
- 操作系统环境变量
- jar包外application-{profile}.yml
- jar包内application-{profile}.yml
- jar包外application.yml
- jar包内application.yml
- @PropertySource引入的配置
实战要点:部署时经常用"jar包外放一个application.yml"来覆盖jar内默认配置,不需要改代码重新打包。这是外部化配置最常用的场景。
【面试题】4. @ConfigurationProperties和@Value有什么区别?什么场景用哪个?
【标准答案】
| 维度 | @ConfigurationProperties | @Value |
|---|---|---|
| 绑定方式 | 批量绑定整个前缀下的属性 | 逐个属性注入 |
| 松散绑定 | 支持(my-name → myName) | 不支持,必须精确匹配 |
| SpEL表达式 | 不支持 | 支持(#{...}) |
| 属性校验 | 支持@Validated JSR303校验 | 不支持 |
| 复杂对象 | 天然支持嵌套对象、List、Map | 需要SpEL,复杂类型很麻烦 |
| 刷新 | 配合@RefreshScope可动态刷新 | 运行时不可变 |
选择建议:配置项多、有层级结构、需要校验 → @ConfigurationProperties;只取一两个简单值、需要SpEL运算 → @Value。
面试加分:说@ConfigurationProperties需要配合@EnableConfigurationProperties或@Component才能生效,单独标注不会自动注册Bean。
【面试题】5. BeanPostProcessor和BeanFactoryPostProcessor的区别?分别在Bean生命周期的哪个阶段执行?
【标准答案】这是两个完全不同阶段的扩展点:
BeanFactoryPostProcessor:
- 执行时机:BeanDefinition注册完成后,Bean实例化之前
- 操作对象:BeanDefinition(可以修改Bean的属性、作用域等元数据)
- 典型应用:PropertySourcesPlaceholderConfigurer处理${}占位符、@Configuration类的CGLIB代理增强
- 执行顺序:所有BeanFactoryPostProcessor先统一执行完
BeanPostProcessor:
- 执行时机:Bean实例化之后,初始化前后
- 操作对象:Bean实例本身(可以替换Bean、包装代理等)
- 两个方法:postProcessBeforeInitialization(@PostConstruct、InitializingBean之前)和postProcessAfterInitialization(AOP代理在这里生成)
- 典型应用:AutowiredAnnotationBeanPostProcessor处理@Autowired、CommonAnnotationBeanPostProcessor处理@PostConstruct、AbstractAutoProxyCreator生成AOP代理
记忆技巧:Factory → 改定义,Post → 改实例。一个在图纸阶段改,一个在产品阶段改。
【面试题】6. SpringBoot中@Import的三种用法分别是什么?自定义Starter时哪种最常用?
【标准答案】@Import三种导入方式:
直接导入配置类:@Import(DataSourceConfig.class),效果等同于在当前配置类里内联写DataSourceConfig的内容,但不需要组件扫描。适合明确知道要导入哪个类的场景。
导入ImportSelector实现类:@Import(MyImportSelector.class),Selector的selectImports方法返回String[]全限定类名,Spring根据返回值动态注册BeanDefinition。这是@EnableAutoConfiguration的底层机制,适合根据条件动态选择导入哪些配置类。
导入ImportBeanDefinitionRegistrar实现类:@Import(MyRegistrar.class),直接拿到BeanDefinitionRegistry手动注册BeanDefinition,控制粒度最细。MyBatis的@MapperScan就是用这种方式批量注册MapperFactoryBean。
自定义Starter最常用:方式2(ImportSelector),因为Starter的自动配置类需要在spring.factories中声明,AutoConfigurationImportSelector会读取并加载,而每个自动配置类内部可能再用@Conditional决定是否生效。如果Starter需要注册特殊BeanDefinition(比如MyBatis的Mapper),则配合方式3。
【面试题】7. AOP中五种通知类型的执行顺序是怎样的?Around通知如果忘记调用ProceedingJoinPoint.proceed()会怎样?
【标准答案】正常执行顺序:
@Around前半段 → @Before → 目标方法 → @AfterReturning → @After → @Around后半段异常执行顺序:
@Around前半段 → @Before → 目标方法抛异常 → @AfterThrowing → @AfterAround忘记调用proceed()的后果:目标方法不会执行,后续所有通知也不执行,Around的后半段代码也不执行,方法直接返回Around前半段的结果。这相当于切面"吞掉"了目标方法调用,是常见的AOP Bug。
面试进阶点:
- Spring5.2.7+版本中,同一切面内@After在@AfterReturning/@AfterThrowing之后执行;更早版本顺序不同。
- 不同切面间的执行顺序由@Order决定,数值越小优先级越高(外层Around先进入后退出)。
- 如果需要同一切面内精确控制顺序,用@Order注解在不同切面类上。
【面试题】8. SpringBoot事件机制中,ContextRefreshedEvent在什么场景下会被多次触发?如何避免重复初始化?
【标准答案】ContextRefreshedEvent在ApplicationContext刷新完成时触发。多次触发的典型场景:SpringMVC中存在父子容器,Root WebApplicationContext和Servlet WebApplicationContext各自刷新时都会触发,所以一个监听器会被调用两次。
避免重复初始化的方案:
- 判断ApplicationContext的Parent:
@EventListener
public void onRefresh(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
// 只在Root容器刷新时执行
}
}- 用原子标记位:
private final AtomicBoolean initialized = new AtomicBoolean(false);
@EventListener
public void onRefresh(ContextRefreshedEvent event) {
if (initialized.compareAndSet(false, true)) {
// 只执行一次
}
}- 改用SmartInitializingListener:Spring4.3+提供,只在所有单例Bean初始化完成后回调一次,不依赖事件机制。
面试加分:SpringBoot内置事件顺序为ApplicationStartingEvent → ApplicationEnvironmentPreparedEvent → ApplicationContextInitializedEvent → ApplicationPreparedEvent → ContextRefreshedEvent → ApplicationStartedEvent → ApplicationReadyEvent。
【面试题】9. SpringBoot默认使用HikariCP连接池,如果要调优,核心参数有哪些?如何选择连接池大小?
【标准答案】HikariCP核心调优参数:
- maximumPoolSize(最大连接数):最关键参数。公式推荐:
连接数 = CPU核心数 × 2 + 有效磁盘数。对于普通Web应用,8-20通常够用,不是越大越好——连接数过多会导致数据库上下文切换开销剧增。 - minimumIdle(最小空闲连接):生产环境建议与maximumPoolSize一致,避免连接池收缩-扩张的抖动开销。
- idleTimeout(空闲超时):空闲连接存活时间,默认600000ms(10分钟),仅在minimumIdle < maximumPoolSize时生效。
- maxLifetime(连接最大存活时间):默认1800000ms(30分钟),建议比数据库的wait_timeout小几秒,避免被数据库主动断开。
- connectionTimeout(获取连接超时):默认30000ms,建议生产环境设5-10秒,快速失败比长时间等待好。
- leakDetectionThreshold(泄漏检测):建议开启,设60000ms,超过时间未关闭连接会打日志告警。
连接数选择的核心思路:数据库连接是稀缺资源,大多数时间连接在等IO(等数据库返回结果),所以CPU不是瓶颈,磁盘IO和网络延迟才是。对于SSD+低延迟网络,连接数可以稍多;对于高延迟远程数据库,连接数宜少。
【面试题】10. @Transactional的传播行为REQUIRED和REQUIRES_NEW有什么区别?在什么场景下REQUIRES_NEW会导致事务不回滚?
【标准答案】
REQUIRED(默认):当前有事务就加入,没有就新建一个。内层方法和外层方法用的是同一个事务,内层异常会标记整个事务为rollback-only,外层catch了也会回滚。
REQUIRES_NEW:不管当前有没有事务,都新建一个独立事务,外层事务挂起。内层事务提交/回滚与外层互不影响。
REQUIRES_NEW导致"不回滚"的经典场景: 外层方法调用内层REQUIRES_NEW方法,内层方法抛异常,外层catch了。此时内层事务独立回滚,但外层事务不受影响继续提交——看起来外层"没回滚"。这是预期行为,但很多人误以为外层也会回滚。
更危险的场景:内层REQUIRES_NEW方法正常提交了,外层之后抛异常回滚——内层已提交的数据不会回滚!如果业务要求内外层数据一致性,用REQUIRES_NEW就是错误的。
选择建议:需要独立事务的场景才用REQUIRES_NEW,比如操作日志记录——主业务回滚了,日志也要保留。其他绝大多数场景用REQUIRED。
面试加分:说Nested传播行为(SAVEPOINT)是REQUIRES_NEW的替代方案,内层可以部分回滚但共享外层事务,避免独立事务的一致性问题。