Skip to content

SpringBoot2核心功能面试题库 ​

生成时间:2026-05-21 22:29


【面试题】1. @SpringBootApplication注解的内部组合关系是什么?为什么说它是SpringBoot自动配置的入口? ​

【标准答案】 @SpringBootApplication是一个组合注解,内部由三个核心注解构成:

  1. @SpringBootConfiguration:本质就是@Configuration,标记当前类是配置类,允许在类中定义@Bean方法。SpringBoot用它替代原生@Configuration,语义更明确。
  2. @EnableAutoConfiguration:这是自动配置的核心引擎。它通过@Import导入AutoConfigurationImportSelector,利用SpringFactoriesLoader从META-INF/spring.factories(SpringBoot2)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)中读取所有自动配置类的全限定名,然后经过条件注解过滤后注册到容器。
  3. @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外部化配置的优先级从高到低(核心记忆:命令行 > 系统属性 > 配置文件):

  1. Devtools全局设置($HOME/.config/spring-boot)
  2. 测试上的@TestPropertySource
  3. @SpringBootTest#properties
  4. 命令行参数(--server.port=8081)
  5. SPRING_APPLICATION_JSON属性(环境变量或命令行)
  6. ServletConfig/ServletContext参数
  7. JNDI属性
  8. Java系统属性(System.getProperties())
  9. 操作系统环境变量(SERVER_PORT等)
  10. RandomValuePropertySource(random.*属性)
  11. jar包外的application-{profile}.yml
  12. jar包内的application-{profile}.yml
  13. jar包外的application.yml
  14. jar包内的application.yml
  15. @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应用)。

启动流程中的创建时机:

  1. run方法调用createApplicationContext,通过反射创建ApplicationContext实例
  2. 此时内部同时创建了DefaultListableBeanFactory作为实际的BeanFactory实现
  3. prepareContext阶段注册主配置类为BeanDefinition
  4. 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的性能差异在哪里? ​

【标准答案】 嵌入式容器启动流程:

  1. spring-boot-starter-web默认引入spring-boot-starter-tomcat
  2. ServletWebServerApplicationContext在refresh阶段调用onRefresh
  3. 创建WebServerFactory(默认TomcatServletWebServerFactory)
  4. 调用getWebServer()方法,内嵌启动Tomcat
  5. Tomcat的生命周期由SpringBoot管理,不需要外部容器

切换为Undertow:

xml
<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:堆内存快照

生产环境安全暴露策略:

  1. 默认只暴露/health和/info(management.endpoints.web.exposure.include=health,info)
  2. 不要暴露/env、/beans、/mappings等敏感端点到公网
  3. 管理端口与应用端口分离:management.server.port=8081,配合防火墙只允许内网访问
  4. 开启端点安全认证:引入spring-boot-starter-security,配置endpoints访问角色
  5. /health只显示总体状态,详细信息需要配置management.endpoint.health.show-details=when-authorized

自定义HealthIndicator:

java
@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集成:

  1. 引入micrometer-registry-prometheus依赖
  2. 暴露/prometheus端点
  3. Prometheus抓取该端点数据
  4. Grafana导入JVM(Micrometer) Dashboard即可可视化监控

面试关键点:生产环境安全是重中之重,必须强调"管理端口分离+最小暴露+认证鉴权"三道防线。另外/health是K8s liveness/readiness探针的基础,必须保证它快速返回(不要做重操作)。