SpringBoot2核心功能面试题库
生成时间:2026-05-21 22:29
【面试题】1. @SpringBootApplication注解的内部组合关系是什么?为什么说它是SpringBoot自动配置的入口?
【标准答案】 @SpringBootApplication是一个组合注解,内部由三个核心注解构成:
- @SpringBootConfiguration:本质就是@Configuration,标记当前类是配置类,允许在类中定义@Bean方法。SpringBoot用它替代原生@Configuration,语义更明确。
- @EnableAutoConfiguration:这是自动配置的核心引擎。它通过@Import导入AutoConfigurationImportSelector,利用SpringFactoriesLoader从
META-INF/spring.factories(SpringBoot2)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)中读取所有自动配置类的全限定名,然后经过条件注解过滤后注册到容器。 - @ComponentScan:扫描当前包及子包下的@Component/@Service/@Repository等组件。excludeFilters默认排除了@Configuration和AutoConfiguration相关类,避免重复加载。
面试关键点:之所以说它是自动配置入口,是因为@EnableAutoConfiguration才是真正的驱动者,它把"约定大于配置"的理念落地——你引入了什么starter的依赖,它就尝试帮你自动配什么,配不配得上取决于条件注解是否满足。
【面试题】2. SpringBoot的自动配置类加载后,是如何通过条件注解实现"按需装配"的?请结合源码说明过滤流程。
【标准答案】 自动配置类的过滤是一个多级筛选过程:
第一级:spring.factories读取。AutoConfigurationImportSelector的selectImports方法调用getAutoConfigurationEntry,从spring.factories中读取所有候选配置类(通常100+个)。
第二级:AutoConfigurationImportFilter过滤。SpringBoot会调用所有实现了AutoConfigurationImportFilter的过滤器,最核心的是OnClassCondition。它检查候选类的@ConditionalOnClass注解中指定的类是否在classpath中存在,不存在的直接排除,避免ClassNotFound风险。这一步用到了TypeFilter的deduceClass结论,效率很高。
第三级:条件注解逐一校验。经过Filter过滤后的配置类被注册为BeanDefinition后,在实例化前还要经过每个@Bean方法上的条件注解判断:
- @ConditionalOnClass:classpath中存在指定类
- @ConditionalOnMissingBean:容器中不存在指定类型的Bean
- @ConditionalOnProperty:配置文件中指定属性满足条件
- @ConditionalOnBean:容器中已存在指定Bean
源码入口:ConditionEvaluator.shouldSkip方法,遍历BeanDefinition上的Condition实现,逐个调用matches方法。
面试关键点:条件注解不是在spring.factories读取阶段生效的,而是在Bean定义注册和实例化阶段。@ConditionalOnMissingBean保证了"用户自定义优先",用户手动定义的Bean会覆盖自动配置的默认Bean。
【面试题】3. 自定义一个SpringBoot Starter需要哪些步骤?如何确保它被其他项目正确引入并自动生效?
【标准答案】 自定义Starter的标准开发步骤:
1. 创建两个模块(推荐做法):
xxx-spring-boot-autoconfigure:放自动配置逻辑xxx-spring-boot-starter:只做依赖聚合,依赖autoconfigure模块+第三方库
2. autoconfigure模块核心内容:
- 配置属性类:用@ConfigurationProperties(prefix = "xxx")定义可配置项,配合@Validated做属性校验
- 核心服务类:实现Starter的业务功能,通过构造器注入配置属性
- 自动配置类:@Configuration + @ConditionalOnClass(核心类.class) + @EnableConfigurationProperties(属性类.class),在类中@Bean定义需要自动装配的Bean,配合@ConditionalOnMissingBean允许用户覆盖
- 注册文件:在resources/META-INF/spring.factories中添加
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xxx.XxxAutoConfiguration(2.7+可用imports文件替代)
3. starter模块:只含pom.xml,依赖autoconfigure模块和必需的第三方库
4. 确保自动生效的关键:
- spring.factories路径和内容必须正确,SpringFactoriesLoader按全限定名加载
- @ConditionalOnClass确保只有引入了相关依赖才会激活,不会在无关项目报错
- 提供合理的默认值,让Starter零配置可用
- 配置项用@ConfigurationProperties绑定,用户通过application.yml即可覆盖
面试加分点:提一下spring-configuration-metadata.json可以生成配置元数据,IDE中输入时有自动提示;另外META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports是2.7+的新写法,向后兼容。
【面试题】4. SpringBoot的外部化配置加载顺序是怎样的?哪些配置源优先级最高?实际项目中如何利用优先级覆盖?
【标准答案】 SpringBoot外部化配置的优先级从高到低(核心记忆:命令行 > 系统属性 > 配置文件):
- Devtools全局设置($HOME/.config/spring-boot)
- 测试上的@TestPropertySource
- @SpringBootTest#properties
- 命令行参数(--server.port=8081)
- SPRING_APPLICATION_JSON属性(环境变量或命令行)
- ServletConfig/ServletContext参数
- JNDI属性
- Java系统属性(System.getProperties())
- 操作系统环境变量(SERVER_PORT等)
- RandomValuePropertySource(random.*属性)
- jar包外的application-{profile}.yml
- jar包内的application-{profile}.yml
- jar包外的application.yml
- jar包内的application.yml
- @ConfigurationProperties的默认值
实际利用场景:
- 生产环境用环境变量覆盖数据库密码、密钥等敏感信息,代码中不硬编码
- 用命令行参数快速切换profile或覆盖端口:
java -jar app.jar --spring.profiles.active=prod --server.port=8081 - jar包外放一份application.yml覆盖jar内默认值,实现"不改包改配置"
- Docker/K8s中用ConfigMap挂载外部配置文件,利用jar包外优先级自动覆盖
面试关键点:同一个属性在高优先级源出现后,低优先级的值会被忽略,不是合并。yml和properties同时存在时,properties优先级更高(但实际项目中不要混用)。
【面试题】5. @ConfigurationProperties和@Value的区别是什么?在什么场景下应该选哪个?
【标准答案】 核心区别对比:
| 维度 | @ConfigurationProperties | @Value |
|---|---|---|
| 绑定方式 | 批量绑定整个前缀下的属性 | 逐个属性注入 |
| 松散绑定 | 支持(my-name → myName) | 不支持,必须精确匹配 |
| SpEL表达式 | 不支持 | 支持(#{...}) |
| 属性校验 | 支持@Validated + JSR-303 | 不支持 |
| 复杂对象 | 天然支持嵌套、List、Map | 需要SpEL或额外处理 |
| 刷新 | 配合@RefreshScope可刷新 | 一般不刷新 |
选择原则:
- 用@ConfigurationProperties的场景:配置项多且属于同一组(如数据源配置、第三方SDK配置);需要做属性校验;需要松散绑定;自定义Starter暴露配置
- 用@Value的场景:只需要注入一两个简单值;需要SpEL做运算或条件取值;从非自身前缀的配置中零散取值
面试加分点:@ConfigurationProperties需要配合@EnableConfigurationProperties或在@Configuration类上激活才生效,不是加了注解就自动注册的。另外@ConfigurationProperties支持构造器绑定(@ConstructorBinding),Immutable风格更安全,2.2+开始推荐。
【面试题】6. BeanFactory和ApplicationContext的区别是什么?SpringBoot启动时用的是哪一个?启动流程中它们何时被创建?
【标准答案】 核心区别:
BeanFactory是IoC容器的基础接口,提供最基础的Bean管理能力(getBean、类型推断等),懒加载设计,Bean在首次获取时才实例化。
ApplicationContext是BeanFactory的子接口(更准确说是超集),在BeanFactory基础上扩展了:
- 国际化支持(MessageSource)
- 事件发布(ApplicationEventPublisher)
- 资源加载(ResourceLoader)
- AOP集成
- 环境抽象(Environment)
- Bean生命周期管理(自动注册BeanPostProcessor等)
SpringBoot启动用的是ApplicationContext。具体是AnnotationConfigServletWebServerApplicationContext(Web应用)或AnnotationConfigApplicationContext(非Web应用)。
启动流程中的创建时机:
- run方法调用createApplicationContext,通过反射创建ApplicationContext实例
- 此时内部同时创建了DefaultListableBeanFactory作为实际的BeanFactory实现
- prepareContext阶段注册主配置类为BeanDefinition
- refreshContext阶段调用AbstractApplicationContext.refresh(),这是核心流程:
- invokeBeanFactoryPostProcessors:执行BeanDefinition的注册和修改
- registerBeanPostProcessors:注册BeanPostProcessor
- finishBeanFactoryInitialization:实例化所有非懒加载的单例Bean
面试关键点:ApplicationContext内部持有一个DefaultListableBeanFactory,绝大多数Bean操作最终委托给它。面试中如果被追问,说"ApplicationContext是门面,BeanFactory是引擎"这个比喻非常加分。
【面试题】7. BeanPostProcessor和BeanFactoryPostProcessor的区别和使用场景是什么?MyBatis的MapperScanner就是基于哪个实现的?
【标准答案】 BeanFactoryPostProcessor:
- 作用时机:BeanDefinition加载完成后、Bean实例化之前
- 操作对象:BeanDefinition(可以修改Bean的定义,如属性值、作用域等)
- 执行阶段:AbstractApplicationContext.refresh()的invokeBeanFactoryPostProcessors
- 典型场景:PropertySourcesPlaceholderConfigurer解析${}占位符、自定义BeanDefinitionRegistryPostProcessor动态注册Bean
BeanPostProcessor:
- 作用时机:Bean实例化之后,初始化前后
- 操作对象:Bean实例本身(可以包装、替换Bean)
- 两个方法:postProcessBeforeInitialization(初始化前,@PostConstruct等之前)、postProcessAfterInitialization(初始化后,AOP代理在此生成)
- 典型场景:AutowiredAnnotationBeanPostProcessor处理@Autowired、CommonAnnotationBeanPostProcessor处理@Resource、AbstractAutoProxyCreator生成AOP代理
MyBatis的MapperScanner:用的是BeanDefinitionRegistryPostProcessor(BeanFactoryPostProcessor的子接口)。MapperScannerConfigurer实现了这个接口,在postProcessBeanDefinitionRegistry中扫描指定包下的Mapper接口,为每个接口注册一个MapperFactoryBean的BeanDefinition。这样Mapper接口虽然是接口不能实例化,但通过FactoryBean机制实现了代理注入。
面试关键点:BeanFactoryPostProcessor改"图纸"(BeanDefinition),BeanPostProcessor改"产品"(Bean实例)。记住这个比喻就够了。另外BeanFactoryPostProcessor的执行早于BeanPostProcessor,这是固定顺序。
【面试题】8. SpringBoot中@Transactional失效的常见场景有哪些?如何排查和避免?
【标准答案】 @Transactional失效的6大经典场景:
1. 同类方法内部调用:同一个类中方法A调用方法B,B上的@Transactional失效。因为事务基于AOP代理,内部调用不走代理,直接是this调用。解决:注入自身(@Lazy注入避免循环依赖)、或拆到另一个Service中。
2. 方法非public:SpringAOP默认只代理public方法,protected/private/package-visible方法上的@Transactional不生效。源码中AbstractFallbackTransactionAttributeSource有显式判断。
3. 异常被catch吞掉:方法内部try-catch捕获了异常但没有重新抛出,事务感知不到异常就不会回滚。解决:catch后手动setRollbackOnly()或重新throw。
4. 异常类型不匹配:默认只对RuntimeException和Error回滚,checked Exception(如IOException)不回滚。解决:@Transactional(rollbackFor = Exception.class)显式指定。
5. 数据库引擎不支持:MySQL的MyISAM引擎不支持事务,必须用InnoDB。
6. 传播行为配置不当:@Transactional(propagation = Propagation.NOT_SUPPORTED)或NEVER,明确不使用事务。
排查方法:
- 开启事务日志:logging.level.org.springframework.transaction=DEBUG
- 检查是否走了代理:打断点看对象是原始类还是代理类(类名带$或CGLIB)
- 检查TransactionSynchronizationManager.isActualTransactionActive()是否为true
面试加分点:提一下自调用失效的本质原因是代理模式的天生限制,SpringBoot2.x默认用CGLIB代理,但自调用问题JDK代理和CGLIB代理都存在。
【面试题】9. SpringBoot的嵌入式容器是如何启动的?如何切换为Undertow?Tomcat和Undertow的性能差异在哪里?
【标准答案】 嵌入式容器启动流程:
- spring-boot-starter-web默认引入spring-boot-starter-tomcat
- ServletWebServerApplicationContext在refresh阶段调用onRefresh
- 创建WebServerFactory(默认TomcatServletWebServerFactory)
- 调用getWebServer()方法,内嵌启动Tomcat
- Tomcat的生命周期由SpringBoot管理,不需要外部容器
切换为Undertow:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>性能差异:
- Tomcat:成熟稳定,社区生态最好,默认配置就能满足大多数场景。但线程模型是传统BIO/NIO,每个请求占用一个线程,高并发下线程数膨胀
- Undertow:基于XNIO的NIO架构,轻量级,内存占用更小。高并发非阻塞场景性能优于Tomcat,尤其适合微服务/IO密集型。但生态和稳定性不如Tomcat
- Jetty:长连接/WebSocket场景表现好,一般不作为首选
调优关键参数:
- server.tomcat.max-threads=200(最大工作线程)
- server.tomcat.max-connections=8192(最大连接数)
- server.undertow.io-threads=4(IO线程,通常=CPU核心数)
- server.undertow.worker-threads=200(工作线程)
面试关键点:切换容器只需换starter依赖,SpringBoot通过ServletWebServerFactory抽象层屏蔽了差异,这是自动配置的经典体现。
【面试题】10. SpringBoot Actuator的核心端点有哪些?在生产环境如何安全地暴露和使用?如何自定义HealthIndicator?
【标准答案】 核心内置端点:
- /health:应用健康状态,最重要,用于存活探针
- /info:应用信息(版本号、构建时间等)
- /metrics:应用指标(JVM内存、HTTP请求统计、线程池等)
- /env:环境变量和配置属性
- /beans:容器中所有Bean
- /mappings:所有@RequestMapping映射
- /loggers:日志级别管理,支持运行时动态调整
- /threaddump:线程快照
- /heapdump:堆内存快照
生产环境安全暴露策略:
- 默认只暴露/health和/info(management.endpoints.web.exposure.include=health,info)
- 不要暴露/env、/beans、/mappings等敏感端点到公网
- 管理端口与应用端口分离:management.server.port=8081,配合防火墙只允许内网访问
- 开启端点安全认证:引入spring-boot-starter-security,配置endpoints访问角色
- /health只显示总体状态,详细信息需要配置management.endpoint.health.show-details=when-authorized
自定义HealthIndicator:
@Component
public class CustomHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
// 检查自定义资源(如外部API、缓存连接等)
if (checkExternalService()) {
builder.up().withDetail("externalService", "available")
.withDetail("responseTime", "50ms");
} else {
builder.down().withDetail("externalService", "unavailable");
}
}
}Prometheus+Grafana集成:
- 引入micrometer-registry-prometheus依赖
- 暴露/prometheus端点
- Prometheus抓取该端点数据
- Grafana导入JVM(Micrometer) Dashboard即可可视化监控
面试关键点:生产环境安全是重中之重,必须强调"管理端口分离+最小暴露+认证鉴权"三道防线。另外/health是K8s liveness/readiness探针的基础,必须保证它快速返回(不要做重操作)。