Skip to content

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

【面试题】1. SpringBoot中@ConditionalOnMissingBean和@ConditionalOnBean的区别是什么?在自动配置中各自的使用场景是什么? ​

【标准答案】两者都是条件装配注解,但逻辑相反。@ConditionalOnBean表示容器中存在指定Bean时才生效,@ConditionalOnMissingBean表示容器中不存在指定Bean时才生效。在自动配置中,@ConditionalOnMissingBean用得远多于@ConditionalOnBean——这是SpringBoot"允许用户覆盖"设计哲学的核心体现。比如DataSourceAutoConfiguration里配HikariDataSource,用的就是@ConditionalOnMissingBean(DataSource.class),意思是:如果用户自己定义了DataSource,自动配置就不插手;如果没有,我才帮你配一个默认的。反过来@ConditionalOnBean一般用在某些功能依赖另一个Bean存在的场景,比如@ConditionalOnBean(DataSource.class)才配事务管理器。面试中要点是:@ConditionalOnMissingBean是实现"用户优先、框架兜底"的关键机制,也是自定义Starter时最常用的条件注解。

【面试题】2. SpringBoot的application.yml和application.properties可以同时存在吗?加载优先级和覆盖规则是怎样的? ​

【标准答案】可以同时存在。SpringBoot对两种格式都支持,但同名属性会按优先级覆盖。优先级从高到低的关键顺序是:命令行参数 > SPRING_APPLICATION_JSON > ServletConfig/ServletContext > JNDI > Java系统属性(System.getProperties()) > 操作系统环境变量 > application.yml/properties(外部config目录 > 项目根目录 > classpath:/config/ > classpath:/)。在classpath下,properties优先级高于yml,即如果同一个属性在两个文件中都有定义,properties的值会覆盖yml。但实际开发中不建议两种格式混用,容易混乱。一个常被追问的点是:外部config目录下的配置优先于classpath,这便于运维时在外部覆盖配置而不需要重新打包。

【面试题】3. BeanPostProcessor和BeanFactoryPostProcessor的区别是什么?各自的执行时机和典型应用场景? ​

【标准答案】这是两个不同层次的扩展点。BeanFactoryPostProcessor是BeanDefinition级别的,在Bean实例化之前执行,它可以修改Bean的定义(比如修改属性值、修改scope等)。典型应用是PropertySourcesPlaceholderConfigurer,它负责把${...}占位符替换成实际值,这个动作必须在Bean实例化前完成。BeanPostProcessor是Bean实例级别的,在Bean实例化之后、初始化前后执行,提供两个回调:postProcessBeforeInitialization和postProcessAfterInitialization。典型应用是AutowiredAnnotationBeanPostProcessor处理@Autowired注入,以及AOP代理创建(在postProcessAfterInitialization中用代理对象替换原始对象)。一句话总结:BeanFactoryPostProcessor改定义(还没new),BeanPostProcessor改实例(已经new了)。执行顺序是:BeanDefinition注册 → BeanFactoryPostProcessor → Bean实例化 → BeanPostProcessor.before → 初始化回调 → BeanPostProcessor.after。

【面试题】4. SpringBoot中@Async注解失效的常见原因有哪些?如何正确配置异步线程池? ​

【标准答案】@Async失效最常见的原因有三个:第一,同类内部调用——@Async基于AOP代理,内部方法调this.xxx()不走代理,注解不生效,必须通过注入的对象调用;第二,启动类或配置类没加@EnableAsync;第三,方法所在类不是Spring管理的Bean。正确配置线程池的方式是自定义ThreadPoolTaskExecutor并注册为Bean,然后在@Async中指定:@Async("customExecutor")。自定义线程池需要关注核心线程数、最大线程数、队列容量、拒绝策略。生产环境建议必配自定义线程池,因为默认的SimpleAsyncTaskExecutor不重用线程,每个任务新建线程,高并发下会撑爆。还要注意异常处理:@Async方法默认吞异常,需要配置AsyncUncaughtExceptionHandler或者让返回值用Future/LIstenableFuture来捕获异常。

【面试题】5. SpringBoot嵌入式容器如何切换?以Tomcat切换到Undertow为例,需要改什么?Undertow有什么优势? ​

【标准答案】切换容器只需要两步:第一步在spring-boot-starter-web中exclusion排除Tomcat依赖,第二步引入对应的starter(如spring-boot-starter-undertow)。Maven配置就是在web的exclusions里排除spring-boot-starter-tomcat,然后单独加spring-boot-starter-undertow依赖。Undertow的优势主要有三个:一是轻量,启动快、内存占用小,比Tomcat少30%左右;二是基于NIO的非阻塞IO模型,高并发场景下吞吐量更好;三是支持Servlet 4.0和WebSocket。但也要注意Undertow不支持JSP(现在基本没人用了),而且默认不创建MBean,JMX监控需要额外配置。面试中常被问什么时候该切——一般微服务、容器化部署、高并发短连接场景下Undertow更合适,传统企业应用Tomcat更稳更成熟。

【面试题】6. SpringBoot的@Cacheable注解的key参数如何设计?在Redis集成场景下,缓存key的命名规范和过期策略怎么定? ​

【标准答案】@Cacheable的key默认使用SimpleKeyGenerator,按方法参数生成key。如果方法无参就用SimpleKey.EMPTY,一个参数就直接用该参数,多参数用SimpleKey组合。但生产中强烈建议自定义key,用SpEL表达式:@Cacheable(value="user", key="#id")或key="#user.id"。在Redis集成场景下,key命名规范建议用冒号分层:业务模块:实体:标识,比如user:info:1001。这样在Redis CLI中用keys user:info:*可以快速排查,也方便按前缀批量删除。过期策略要分层:热点数据短期(5-30分钟),基础数据中期(1-24小时),不变化的数据可以设长但必须设TTL,绝不能不设过期。另外注意@Cacheable的condition和unless:condition决定是否缓存(支持SpEL),unless决定是否排除某些返回值(比如null值不缓存:unless="#result == null")。

【面试题】7. SpringBoot事务@Transactional的传播行为有哪几种?PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW在业务中怎么选? ​

【标准答案】7种传播行为:REQUIRED(默认)、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。最常用的是REQUIRED和REQUIRES_NEW。REQUIRED的含义是:当前有事务就加入,没有就新建。这是默认行为,适合大部分场景——方法之间是同一个业务单元,要么一起成功要么一起回滚。REQUIRES_NEW的含义是:无论如何都新建事务,外层事务挂起。适合的场景是:内部操作无论成败都不影响外层。典型例子是日志记录——主业务失败要回滚,但操作日志必须入库,这时候日志方法用REQUIRES_NEW。但要注意REQUIRES_NEW会有性能开销,因为涉及事务嵌套和连接切换,而且内层事务独立提交后,外层回滚不影响内层已提交的数据。NESTED是另一种选择,它用保存点实现,内层回滚不影响外层,但外层回滚会连带内层——适合部分失败可容忍但不独立提交的场景。

【面试题】8. SpringBoot Actuator的端点安全暴露策略是什么?如何在生产环境安全地暴露监控端点? ​

【标准答案】Actuator默认只暴露/health和/info两个端点,其他所有端点都是关闭的,这是安全底线。生产环境的安全暴露策略分三步:第一步,通过management.endpoints.web.exposure.include配置需要暴露的端点,建议只开必要的(health、info、metrics、prometheus),绝不用"*"全开;第二步,通过management.endpoints.web.base-path自定义路径(不要用默认的/actuator),增加攻击者猜测难度;第三步,网络层隔离——Actuator端口和应用端口分离(management.server.port=独立端口),然后该端口只对内网或监控组件可见。如果用Prometheus+Grafana监控,需要暴露prometheus端点,配合Spring Boot Admin做可视化。还要注意/health端点的细节显示控制:management.endpoint.health.show-details=when-authorized,只有授权用户才能看到磁盘空间、数据源状态等详情,否则只显示status: UP/DOWN。

【面试题】9. SpringBoot自定义Starter的开发步骤是什么?spring.factories和AutoConfiguration.imports两种方式有什么区别? ​

【标准答案】自定义Starter开发分四步:第一步,创建两个模块——autoconfigure模块(放自动配置类)和starter模块(只做依赖聚合,引入autoconfigure);第二步,在autoconfigure模块写@Configuration配置类,配合@ConditionalOnClass、@ConditionalOnMissingBean等条件注解;第三步,注册自动配置——SpringBoot 2.7以前在META-INF/spring.factories里写org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.xxx.XxxAutoConfiguration;SpringBoot 2.7+推荐在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中每行写一个全限定类名。区别在于:spring.factories是旧的SPI机制,key-value格式,所有SPI共用一个文件;AutoConfiguration.imports是SpringBoot专门为自动配置设计的,纯文本每行一个类名,更清晰更轻量,且SpringBoot 3.0已移除spring.factories方式。第四步,写@ConfigurationProperties绑定配置属性,提供默认值,并在自动配置类上通过@EnableConfigurationProperties启用。

【面试题】10. SpringBoot应用如何实现优雅停机?server.shutdown=graceful的原理和注意事项是什么? ​

【标准答案】SpringBoot 2.3+支持优雅停机,配置server.shutdown=graceful即可开启。原理是:应用收到SIGTERM信号后,Web容器不再接受新请求,但会等待已接收的请求处理完毕,超过spring.lifecycle.timeout-per-shutdown-phase(默认30秒)才强制关闭。注意事项有四个:第一,只对嵌入式Web容器(Tomcat/Undertow/Jetty)的HTTP请求生效,非Web应用和自定义线程不在此管理范围;第二,如果用了@Scheduled或@Async,需要自行实现SmartLifecycle来等待这些任务完成;第三,Docker/K8s环境下要配合terminationGracePeriodSeconds使用,确保K8s给的时间大于Spring的timeout;第四,如果请求涉及外部调用(RPC、数据库),要确认外部调用也有合理的超时,否则一个卡住的外部调用可能拖垮整个停机过程。另外补充一点:SpringBoot的ContextClosedEvent在优雅停机完成前就会触发,不要在这个事件里做阻塞操作。