本轮 SpringBoot2核心功能面试题库(带答案)
【面试题】1. SpringBoot启动类上的@SpringBootApplication注解由哪些注解组合而成?各自作用是什么?
【标准答案】@SpringBootApplication是一个组合注解,由三个核心注解构成:
- @SpringBootConfiguration:本质就是@Configuration,标识这是一个配置类,允许在类中定义@Bean方法
- @EnableAutoConfiguration:开启自动配置的核心,通过@Import导入AutoConfigurationImportSelector,利用类加载器读取META-INF/spring.factories(老版本)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新版本)中的自动配置类全限定名,结合条件注解按需装配
- @ComponentScan:开启组件扫描,默认扫描当前包及其子包下带有@Component/@Service/@Repository/@Controller/@Configuration等注解的类
实际开发中,如果需要自定义扫描路径,可以手动拆分这三个注解,指定@ComponentScan的basePackages。
【面试题】2. SpringBoot的条件注解@ConditionalOnProperty是如何工作的?请举例说明实际应用场景。
【标准答案】@ConditionalOnProperty通过检查配置文件中的属性值来决定是否装配Bean。
核心属性:
- prefix:属性前缀
- name:属性名
- havingValue:期望值
- matchIfMissing:当属性不存在时是否匹配(默认false)
工作原理:容器启动时,从Environment中读取指定属性,判断是否符合条件,符合则注册Bean,否则跳过。
实际应用场景:
@Bean
@ConditionalOnProperty(prefix = "cache", name = "type", havingValue = "redis")
public CacheManager redisCacheManager() {
return new RedisCacheManager();
}
@Bean
@ConditionalOnProperty(prefix = "cache", name = "type", havingValue = "ehcache", matchIfMissing = true)
public CacheManager ehCacheManager() {
return new EhCacheCacheManager();
}这样可以通过配置cache.type=redis切换缓存实现,不配置则默认使用Ehcache。
【面试题】3. SpringBoot的配置文件加载顺序和优先级规则是什么?
【标准答案】SpringBoot配置加载遵循"外层覆盖内层、后者覆盖前者"原则,优先级从高到低:
- 命令行参数(--server.port=8081)
- 系统属性(System.getProperties())
- 操作系统环境变量
- jar包外部的application-{profile}.properties/yml
- jar包内部的application-{profile}.properties/yml
- jar包外部的application.properties/yml
- jar包内部的application.properties/yml
- @Configuration类上的@PropertySource
- 默认属性(SpringApplication.setDefaultProperties)
实际开发中:
- 开发环境用application-dev.yml配置本地数据库
- 生产环境打包时,在jar同级目录放application-prod.yml,启动命令加--spring.profiles.active=prod
- 敏感配置(数据库密码)用环境变量注入,避免硬编码
【面试题】4. @ConfigurationProperties与@Value注解绑定配置有什么区别?各有什么优缺点?
【标准答案】
@ConfigurationProperties优势:
- 批量绑定:一次性将配置文件中的多个属性映射到Bean字段
- 松散绑定:支持firstName、first-name、first_name、FIRST_NAME等格式自动映射
- 复杂类型:支持Map、List、嵌套对象绑定
- 属性校验:配合@Validated + @NotNull/@Min等JSR-303注解做校验
- 配置提示:配合spring-boot-configuration-processor生成元数据,IDE有提示
@Value优势:
- 简单直接:单个属性注入,代码量少
- SpEL支持:支持表达式#{...},可以做复杂计算
- 动态性:可以在非Spring管理的类中使用(需配合@PropertySource)
选择建议:
- 配置项多、需要分组管理、需要校验 → 用@ConfigurationProperties
- 单个属性、简单场景、需要SpEL计算 → 用@Value
实际项目中,数据库配置、Redis配置等复杂配置推荐@ConfigurationProperties,端口号、开关类配置用@Value即可。
【面试题】5. BeanPostProcessor和BeanFactoryPostProcessor有什么区别?各自的执行时机是什么?
【标准答案】
BeanFactoryPostProcessor:
- 作用:在BeanDefinition加载完成后、Bean实例化之前,对BeanDefinition进行修改
- 执行时机:ApplicationContext.refresh()的invokeBeanFactoryPostProcessors阶段,早于Bean实例化
- 典型应用:PropertySourcesPlaceholderConfigurer处理${...}占位符,修改Bean的scope、lazy-init等属性
- 注意:如果过早实例化Bean,会破坏BeanPostProcessor的拦截机制
BeanPostProcessor:
- 作用:在Bean实例化后、初始化前后介入,对Bean进行增强或代理
- 执行时机:Bean实例化完成后,在属性注入之后,调用初始化方法前后
- 核心方法:
- postProcessBeforeInitialization:@PostConstruct、InitializingBean之前执行
- postProcessAfterInitialization:返回代理对象,如AOP、事务代理
- 典型应用:AutowiredAnnotationBeanPostProcessor处理@Autowired,AbstractAutoProxyCreator创建AOP代理
面试回答时强调:BeanFactoryPostProcessor操作的是BeanDefinition(类定义),BeanPostProcessor操作的是Bean实例(对象)。
【面试题】6. @Import注解有哪几种使用方式?分别适用于什么场景?
【标准答案】@Import有三种导入方式:
方式一:直接导入配置类
@Import({MyConfiguration.class})适用场景:需要引入其他配置类,又不想用@ComponentScan扫描整个包
方式二:实现ImportSelector接口
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return new String[]{"com.example.MyConfiguration"};
}
}适用场景:根据条件动态决定导入哪些类,如@EnableAutoConfiguration的底层实现
方式三:实现ImportBeanDefinitionRegistrar接口
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(MyBean.class).getBeanDefinition();
registry.registerBeanDefinition("myBean", bd);
}
}适用场景:需要手动注册BeanDefinition、动态修改Bean属性,如MyBatis的@MapperScan底层就是用这种方式注册Mapper
实际开发中,自定义Starter常用方式二和方式三实现自动装配。
【面试题】7. Spring AOP中JDK动态代理和CGLIB代理有什么区别?SpringBoot默认使用哪种?
【标准答案】
JDK动态代理:
- 基于接口:目标类必须实现接口
- 原理:通过反射生成实现接口的代理类
- 性能:JDK8以后性能优化,接近CGLIB
- 限制:只能代理接口方法
CGLIB代理:
- 基于继承:通过字节码生成目标类的子类
- 原理:使用ASM字节码框架生成代理类
- 性能:创建代理慢,执行快(方法调用时少了反射开销)
- 限制:无法代理final类和final方法
SpringBoot默认策略:
- SpringBoot 2.x默认使用CGLIB代理(spring.aop.proxy-target-class默认true)
- 原因:很多场景没有接口,CGLIB更通用
强制指定方式:
spring.aop.proxy-target-class=false # 强制使用JDK代理面试注意点:事务失效场景之一——类内部调用不走代理,解决方案是自注入或AopContext.currentProxy()。
【面试题】8. SpringBoot的事件机制有哪些核心事件?实际开发中如何使用?
【标准答案】
核心内置事件(按启动顺序):
- ApplicationStartingEvent:启动开始,Environment未创建
- ApplicationEnvironmentPreparedEvent:Environment准备好
- ApplicationContextInitializedEvent:ApplicationContext初始化完成
- ApplicationPreparedEvent:BeanDefinition加载完成
- ContextRefreshedEvent:容器刷新完成,所有Bean已就绪
- ContextStartedEvent:调用start()方法时触发
- ApplicationReadyEvent:应用就绪,可以接收请求
- ContextClosedEvent:容器关闭时触发
自定义事件使用:
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private Long orderId;
public OrderCreatedEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
}
// 2. 发布事件
@Autowired
private ApplicationEventPublisher publisher;
publisher.publishEvent(new OrderCreatedEvent(this, 123L));
// 3. 监听事件
@Component
public class OrderEventListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 发送通知、记录日志等
}
}异步事件:
@EventListener
@Async
public void onOrderCreated(OrderCreatedEvent event) {
// 异步处理,不阻塞主线程
}实际应用:订单创建后发送消息通知、用户注册后初始化数据、系统启动后加载缓存。
【面试题】9. SpringBoot如何切换嵌入式容器?Tomcat、Undertow、Jetty各自有什么特点?
【标准答案】
切换方式(Maven):
<!-- 默认Tomcat,排除后引入其他容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 切换为Undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>三大容器对比:
| 容器 | 特点 | 适用场景 |
|---|---|---|
| Tomcat | 生态最成熟、文档丰富、社区活跃 | 通用Web应用、传统企业系统 |
| Undertow | 轻量级、高并发、内存占用低 | 高性能网关、微服务、I/O密集型 |
| Jetty | 低内存、易嵌入、长连接支持好 | 聊天服务器、推送服务、嵌入式设备 |
性能调优示例(Undertow):
server:
undertow:
io-threads: 16
worker-threads: 256
buffer-size: 1024
direct-buffers: true面试建议:简单应用用默认Tomcat,高并发场景考虑Undertow。
【面试题】10. @Transactional注解失效的常见场景有哪些?如何避免?
【标准答案】
常见失效场景:
类内部调用:同一个类中方法A调用方法B,B的事务不生效
- 原因:不走代理,直接调用目标对象方法
- 解决:自注入自己,或用AopContext.currentProxy()
方法非public:protected/private方法加@Transactional无效
- 原因:Spring AOP默认只代理public方法
- 解决:改为public,或开启AspectJ编译时织入
异常被捕获:try-catch捕获异常后未抛出
- 原因:代理类检测不到异常,认为方法执行成功
- 解决:手动抛出异常,或设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
异常类型不匹配:默认只回滚RuntimeException和Error
- 原因:checked异常不回滚
- 解决:@Transactional(rollbackFor = Exception.class)
传播行为配置错误:REQUIRES_NEW嵌套调用导致死锁或数据不一致
- 解决:理解7种传播行为,合理选择REQUIRED/REQUIRES_NEW
多数据源场景:事务管理器未正确绑定数据源
- 解决:@Transactional(transactionManager = "secondaryTransactionManager")
最佳实践:
- 事务方法尽量放在Service层,不要嵌套调用
- 统一使用rollbackFor = Exception.class
- 避免事务方法耗时操作(远程调用、文件IO),防止长事务