Skip to content

本轮 SpringBoot2核心功能面试题库(带答案)

【面试题】1. SpringBoot自动配置的生效流程是怎样的?从启动类到具体Bean装配的完整链路是什么? ​

【标准答案】 SpringBoot自动配置的核心流程分四步走:

第一步,启动类上的@SpringBootApplication是一个组合注解,内部包含@EnableAutoConfiguration,这个注解通过@Import(AutoConfigurationImportSelector.class)导入了自动配置选择器。

第二步,AutoConfigurationImportSelector会去读取META-INF/spring.factories(旧版)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新版),把所有以EnableAutoConfiguration为key的配置类全限定名加载进来。

第三步,每个自动配置类(比如DataSourceAutoConfiguration)上面都有大量条件注解,比如@ConditionalOnClass(DataSource.class)、@ConditionalOnMissingBean、@ConditionalOnProperty等,Spring会逐一评估这些条件,只有全部满足才会让这个配置类生效。

第四步,生效的配置类内部的@Bean方法会被注册到容器,完成具体组件的装配。整个过程是"候选集加载 → 条件过滤 → Bean注册"的漏斗式流程。


【面试题】2. @ConditionalOnMissingBean什么时候会失效?如何保证自定义Bean一定覆盖自动配置? ​

【标准答案】 @ConditionalOnMissingBean失效主要有三种场景:

第一种是配置类加载顺序问题。如果你的自定义配置类在自动配置类之后加载,自动配置类先评估条件时,你的Bean还没注册,条件不满足,自动配置的Bean就先注册了。

第二种是Bean定义的名称不匹配。@ConditionalOnMissingBean默认检查的是返回值类型,但如果你自定义Bean时用了不同的Bean名称,而自动配置类指定了具体名称,可能导致判断失误。

第三种是包扫描范围问题。如果自定义Bean所在包不在@ComponentScan扫描范围内,根本没被注册到容器,自然无法触发条件。

保证覆盖的三种可靠方式:

一是使用@Configuration配置类配合@ConditionalOnMissingBean,确保在自动配置类之前加载。

二是配置spring.autoconfigure.exclude排除特定自动配置类。

三是使用@Primary注解,当多个同类型Bean存在时,优先注入带@Primary的Bean。


【面试题】3. SpringBoot的配置文件加载顺序和优先级覆盖规则是怎样的? ​

【标准答案】 配置加载顺序从高到低依次是:

  1. 命令行参数,比如--server.port=8081
  2. SPRING_APPLICATION_JSON环境变量或系统属性中的JSON配置
  3. ServletConfig初始化参数
  4. ServletContext初始化参数
  5. JNDI属性
  6. Java系统属性(System.getProperties())
  7. 操作系统环境变量
  8. random.*开头的随机属性
  9. jar包外的application-{profile}.properties/yml
  10. jar包内的application-{profile}.properties/yml
  11. jar包外的application.properties/yml
  12. jar包内的application.properties/yml
  13. @Configuration类上的@PropertySource注解
  14. 默认属性(SpringApplication.setDefaultProperties)

核心规则是"外层覆盖内层,命令行最高优先级"。同一个属性在多个配置源中出现时,优先级高的会覆盖优先级低的。实际开发中,我们常用命令行参数做临时覆盖,用Profile做多环境隔离。


【面试题】4. @ConfigurationProperties和@Value的区别是什么?各自适用场景? ​

【标准答案】 两者核心区别有四点:

第一,绑定方式不同。@ConfigurationProperties支持松散绑定,属性名可以是user-name、userName、user_name、USER_NAME任意一种,都能绑定到userName字段。@Value必须精确匹配属性名。

第二,数据校验能力不同。@ConfigurationProperties配合@Validated注解支持JSR303校验,可以直接在字段上加@NotNull、@Min等校验注解。@Value不支持。

第三,复杂对象绑定能力不同。@ConfigurationProperties天然支持嵌套对象、List、Map等复杂结构绑定。@Value需要SpEL表达式或手动解析,复杂场景非常麻烦。

第四,使用方式不同。@ConfigurationProperties通常标注在配置类或Bean上,需要配合@EnableConfigurationProperties或@Component使用。@Value直接标注在字段上。

适用场景:

  • 配置项较多、结构复杂、需要校验的场景用@ConfigurationProperties
  • 单个或少量配置项、简单值注入用@Value

【面试题】5. BeanFactory和ApplicationContext的区别是什么?SpringBoot启动时用的是什么? ​

【标准答案】 两者核心区别体现在五个维度:

第一,功能范围不同。BeanFactory是IoC容器的最底层接口,只提供基本的Bean获取、依赖查找功能。ApplicationContext继承自BeanFactory,在此基础上扩展了国际化支持、资源加载、事件发布、AOP集成、Environment抽象等高级特性。

第二,初始化策略不同。BeanFactory采用延迟初始化,第一次getBean()时才创建。ApplicationContext默认预加载,启动时就实例化所有单例Bean。

第三,Bean生命周期管理不同。ApplicationContext自动注册BeanFactoryPostProcessor、BeanPostProcessor等扩展点,BeanFactory需要手动注册。

第四,配置元数据来源不同。ApplicationContext支持XML、注解、JavaConfig等多种配置方式,BeanFactory需要手动编程式注册。

SpringBoot启动时,底层创建的是AnnotationConfigServletWebServerApplicationContext(Web应用)或AnnotationConfigApplicationContext(非Web应用),本质是ApplicationContext的实现类。SpringBoot通过SpringApplication.run()方法创建并刷新这个上下文,完成整个容器的启动流程。


【面试题】6. BeanPostProcessor和BeanFactoryPostProcessor的区别和执行时机? ​

【标准答案】 两者核心区别:

第一,作用对象不同。BeanFactoryPostProcessor操作的是BeanDefinition,也就是Bean的元数据定义,在Bean实例化之前执行。BeanPostProcessor操作的是Bean实例,在Bean实例化之后、初始化前后执行。

第二,执行时机不同。BeanFactoryPostProcessor在容器刷新阶段,所有Bean定义加载完成后,Bean实例化之前执行。BeanPostProcessor在每个Bean实例化后,初始化方法(@PostConstruct、InitializingBean等)前后执行。

第三,典型应用场景不同。BeanFactoryPostProcessor常用于修改Bean定义的属性,比如PropertySourcesPlaceholderConfigurer解析${}占位符,ConfigurationClassPostProcessor处理@Configuration类。BeanPostProcessor常用于Bean包装和代理,比如AutowiredAnnotationBeanPostProcessor处理@Autowired,AbstractAutoProxyCreator创建AOP代理。

执行顺序:

  1. 容器启动,加载所有BeanDefinition
  2. 执行所有BeanFactoryPostProcessor
  3. 实例化Bean
  4. 属性注入
  5. 执行BeanPostProcessor的postProcessBeforeInitialization
  6. 执行初始化方法(@PostConstruct → InitializingBean → init-method)
  7. 执行BeanPostProcessor的postProcessAfterInitialization

【面试题】7. @Import的三种使用方式分别是什么?自定义Starter时用哪种? ​

【标准答案】 @Import三种导入方式:

第一种,直接导入普通类。@Import(DataSource.class),被导入的类会直接作为Bean注册到容器,相当于XML配置中的<bean>标签。

第二种,导入ImportSelector实现类。返回需要导入的类全限定名数组,可以根据条件动态选择要导入的配置类。比如@Import(AutoConfigurationImportSelector.class),这是SpringBoot自动配置的核心机制。

第三种,导入ImportBeanDefinitionRegistrar实现类。直接操作BeanDefinitionRegistry,可以手动注册BeanDefinition,控制权最高。比如MyBatis的@MapperScannerRegistrar,批量注册Mapper接口。

自定义Starter的推荐做法:

通常使用第三种方式,配合@EnableXxx注解模式。比如自定义@EnableMyFeature注解,内部用@Import(MyFeatureRegistrar.class),注册器根据用户配置决定注册哪些Bean。这样用户只需在启动类上加@EnableMyFeature即可启用功能,体验更优雅。


【面试题】8. Spring AOP的代理方式选择规则是什么?如何强制使用CGLIB代理? ​

【标准答案】 Spring AOP代理方式选择规则:

如果目标类实现了接口,Spring默认使用JDK动态代理。JDK代理基于接口实现,生成的代理类实现了目标接口,只能代理接口方法。

如果目标类没有实现任何接口,Spring自动切换到CGLIB代理。CGLIB基于继承,生成目标类的子类,可以代理类中的所有非final方法。

从SpringBoot 2.0开始,默认策略仍然是上述规则,但可以通过配置强制使用CGLIB。

强制使用CGLIB的两种方式:

第一种,配置文件设置:spring.aop.proxy-target-class=true

第二种,注解方式:@EnableAspectJAutoProxy(proxyTargetClass = true)

实际生产中的建议:

  • 项目中如果大量使用代理,统一用CGLIB可以避免代理方式不一致带来的问题
  • 需要代理类方法而非接口方法时,必须用CGLIB
  • 性能敏感场景,JDK代理略优于CGLIB,但差距可以忽略

注意:SpringBoot 2.x后,即使配置proxy-target-class=false,当没有接口时仍会自动使用CGLIB。


【面试题】9. Spring事件机制的执行顺序是怎样的?如何实现异步事件? ​

【标准答案】 Spring事件执行顺序:

同步事件默认在发布线程中执行,监听器的执行顺序由@Order注解或Ordered接口控制,数值越小优先级越高。

事件发布流程:

  1. 调用ApplicationEventPublisher.publishEvent(event)
  2. SimpleApplicationEventMulticaster遍历所有匹配的监听器
  3. 依次调用每个监听器的onApplicationEvent方法
  4. 默认在当前线程同步执行

实现异步事件的三种方式:

第一种,监听器方法上加@Async注解:

java
@EventListener
@Async
public void handleEvent(MyEvent event) {
    // 异步处理
}

需要主配置类加@EnableAsync启用异步支持。

第二种,自定义事件广播器,在multicastEvent方法中用线程池执行:

java
@Component
public class AsyncEventMulticaster extends SimpleApplicationEventMulticaster {
    public AsyncEventMulticaster() {
        setTaskExecutor(new SimpleAsyncTaskExecutor());
    }
}

第三种,自定义ApplicationEventMulticaster Bean,替换默认广播器。

注意:异步事件要考虑事务边界问题,事件监听器如果抛异常,不会回滚发布者的事务。


【面试题】10. @Transactional的传播行为有哪些?实际开发中最常用的组合是什么? ​

【标准答案】 Spring定义了七种传播行为:

  1. REQUIRED(默认):有事务就加入,没有就新建
  2. SUPPORTS:有事务就加入,没有就以非事务执行
  3. MANDATORY:必须在事务中执行,否则抛异常
  4. REQUIRES_NEW:总是新建事务,挂起当前事务
  5. NOT_SUPPORTED:以非事务执行,挂起当前事务
  6. NEVER:以非事务执行,有事务则抛异常
  7. NESTED:有事务就创建嵌套事务,没有就新建

实际开发中最常用的组合场景:

场景一:常规业务方法 用默认REQUIRED,大多数业务方法都属于这种,保证在事务中执行,自动加入外层事务。

场景二:独立日志记录@Transactional(propagation = Propagation.REQUIRES_NEW),保证日志独立提交,即使外层事务回滚,日志也不丢失。

场景三:批量处理中部分失败不影响整体 外层方法用REQUIRED,内部循环调用方法用REQUIRES_NEW,每条记录独立事务。

场景四:嵌套事务回滚点 用NESTED,创建savepoint,内部失败可以只回滚到savepoint,外层可以决定是否继续。

常见坑:

  • 同一个类中方法自调用,事务不生效(代理绕过)
  • 异常被catch吞掉,事务不回滚
  • 默认只回滚RuntimeException,checked异常需要显式配置rollbackFor