Skip to content

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

【面试题】1. SpringBoot中@Bean方法之间的依赖是如何解析的?如果存在循环依赖会怎样? ​

【标准答案】SpringBoot底层走的是Spring容器,@Bean方法之间的依赖解析分两种情况:如果@Configure类是full模式(proxyBeanMethods=true,默认),CGLIB会代理配置类,保证@Bean方法调用走容器单例,不会重复创建对象;如果是lite模式(proxyBeanMethods=false),@Bean方法调用就是普通Java方法调用,每次都会new新对象。循环依赖方面:单例@Bean通过三级缓存可以解决setter注入的循环依赖,但构造器注入的循环依赖无法解决,启动会报BeanCurrentlyInCreationException。另外,@Bean方法如果显式调另一个@Bean方法形成循环,full模式下也能靠代理解决,lite模式直接就死循环或重复创建了。面试时强调:默认proxyBeanMethods=true就是为了保证语义正确,lite模式适合纯配置不需要内部引用的场景,能提升启动速度。

【面试题】2. @ConditionalOnProperty的matchIfMissing属性有什么用?举例说明在Starter开发中的实际意义。 ​

【标准答案】matchIfMissing=true的意思是:当配置文件中根本没有配这个属性时,条件也算成立,Bean照样注册。实际开发中非常常见,比如Redis自动配置类上写@ConditionalOnProperty(prefix="spring.redis", name="enabled", havingValue="true", matchIfMissing=true),意思是用户配了spring.redis.enabled=true就生效,没配也默认生效(默认开启),只有显式配了false才不生效。这样新手引入Starter后零配置就能用,老手想关掉也能关。反过来matchIfMissing=false(默认值)就是没配就不生效,需要用户显式配置才开启。Starter设计的一般原则:核心功能matchIfMissing=true默认开启,可选功能matchIfMissing=false默认关闭,用户按需开启。

【面试题】3. SpringBoot的配置文件加载优先级是怎样的?如果application.yml和application.properties同时存在会怎样? ​

【标准答案】这是经典的优先级问题。首先,properties优先级高于yml,如果两个文件同时存在于同一位置,properties会覆盖yml中的同名属性,但不会完全替代——yml中独有的属性仍然生效,只是同名属性取properties的值。加载位置的优先级从高到低:1.jar包外config子目录的配置 2.jar包外的配置 3.jar包内config子目录的配置 4.jar包内classpath根目录的配置。高优先级覆盖低优先级的同名属性。另外,命令行参数优先级最高,然后是JAVA_OPTS环境变量、JNDI属性、系统环境变量,再到配置文件。面试关键点:不是说只取一个文件,而是所有配置源都加载,按优先级覆盖同名属性,不同名的属性会合并。

【面试题】4. BeanPostProcessor和BeanFactoryPostProcessor的区别是什么?各自在什么时候执行?举一个你用过的场景。 ​

【标准答案】核心区别在于操作对象不同:BeanFactoryPostProcessor操作的是BeanDefinition(bean的元数据),在Bean实例化之前执行;BeanPostProcessor操作的是Bean实例,在Bean初始化前后执行。执行时机:BeanFactoryPostProcessor在容器刷新的invokeBeanFactoryPostProcessors阶段执行,所有Bean定义已加载但还没实例化;BeanPostProcessor在每个Bean的初始化阶段执行,beforeInitialization在@PostConstruct之前,afterInitialization在@PostConstruct之后。典型场景:BeanFactoryPostProcessor——PropertyPlaceholderConfigurer解析${}占位符替换属性值;BeanPostProcessor——AutowiredAnnotationBeanPostProcessor处理@Autowired注入、CommonAnnotationBeanPostProcessor处理@PostConstruct/@PreDestroy、AOP的AbstractAutoProxyCreator生成代理对象。面试加分点:说清楚BeanFactoryPostProcessor可以修改BeanDefinition的scope、propertyValues等元数据,是"改图纸";BeanPostProcessor是"改产品"。

【面试题】5. SpringBoot如何实现优雅停机?在生产环境中怎么配置? ​

【标准答案】SpringBoot2.3+内置了优雅停机支持。配置方式:server.shutdown=graceful开启优雅停机,spring.lifecycle.timeout-per-shutdown-phase=30s设置超时时间。原理:收到SIGTERM信号后,Web容器不再接受新请求,但会等正在处理的请求完成,超过超时时间才强制关闭。对于非Web应用,SmartLifecycle接口的stop方法也支持优雅停机。生产环境完整配置:1.application.yml中配置优雅停机和超时 2.Docker/K8s中设置terminationGracePeriodSeconds大于spring的超时时间 3.注册Shutdown Hook处理线程池关闭、缓存刷盘等 4.如果是嵌入式Tomcat,还要注意Connector的acceptCount和maxThreads的配合。关键点:优雅停机不是万能的,长时间任务(如大文件上传、批量处理)还是需要自己实现可中断逻辑,定期检查Thread.interrupted()或自定义标志位。

【面试题】6. @Async标注的方法不生效的常见原因有哪些?如何排查? ​

【标准答案】@Async不生效主要有这几个坑:第一,没有在启动类或配置类上加@EnableAsync,这个最基础但最容易被忘;第二,同类内部调用(this.method()),因为没走代理,AOP切面不生效,这是所有AOP注解的通病;第三,方法不是public,Spring AOP默认只代理public方法;第四,方法定义在非Spring管理的对象上,没有交给容器就谈不上代理;第五,异步线程池满了,默认使用SimpleAsyncTaskQueue,队列满了会抛TaskRejectedException,需要自定义线程池。排查步骤:1.确认@EnableAsync 2.确认调用走代理(注入调用而非this调用)3.确认方法是public 4.检查日志有没有AsyncAnnotationBeanPostProcessor注册 5.自定义线程池:实现AsyncConfigurer接口或配置自定义TaskExecutor Bean,设置合理的corePoolSize、maxPoolSize、queueCapacity。生产建议一定要自定义线程池,默认的无界队列会拖垮系统。

【面试题】7. SpringBoot中@Transactional的propagation=REQUIRES_NEW和NESTED有什么区别?各适用什么场景? ​

【标准答案】REQUIRES_NEW:每次都挂起当前事务,开一个全新的独立事务,内层事务提交/回滚完全不影响外层。NESTED:在当前事务中创建一个保存点(savepoint),内层回滚只是回滚到保存点,外层可以决定提交还是回滚;如果外层回滚,内层也会一起回滚。关键区别:REQUIRES_NEW的内层事务独立,外层回滚不影响内层已提交的结果;NESTED是外层事务的子事务,外层回滚会连带内层一起回滚。适用场景:REQUIRES_NEW适合日志记录、审计等必须独立成功的操作——主业务回滚了,日志不能丢;NESTED适合子操作允许独立回滚但不想开新事务开销的场景——比如订单处理中某个可选步骤失败,回滚这个步骤但不影响主订单。注意:NESTED依赖数据库的savepoint机制,MySQL支持但某些数据库不支持,不支持时会退化为REQUIRES_NEW。

【面试题】8. SpringBoot Actuator的/health端点是如何工作的?如何自定义HealthIndicator? ​

【标准答案】/health端点会聚合所有HealthIndicator的检查结果,每个Indicator返回UP/DOWN/OUT_OF_SERVICE等状态,整体状态取最严重的(一个DOWN则整体DOWN)。内置Indicator包括DiskSpaceHealthIndicator、DataSourceHealthIndicator、RedisHealthIndicator等,通过auto-configuration自动注册。自定义方式:实现HealthIndicator接口,重写health()方法返回Health对象。示例:public class MyServiceHealthIndicator implements HealthIndicator { public Health health() { if(myService.isAlive()) return Health.up().withDetail("responseTime", "50ms").build(); else return Health.down().withDetail("error", "connection timeout").build(); }} 注意类要加@Component或通过@Bean注册。进阶:1.可以用AbstractHealthIndicator做模板化处理 2.通过management.endpoint.health.show-details=always/when-authorized控制详情展示 3.用HealthContributor接口(2.2+)可以做聚合 4.分组展示:management.endpoint.health.group.readiness.include=db,redis 区分liveness和readiness探针。

【面试题】9. SpringBoot自定义Starter的开发步骤是什么?命名规范有哪些? ​

【标准答案】开发步骤五步走:1.创建两个模块——xxx-spring-boot-autoconfigure(自动配置核心)和xxx-spring-boot-starter(依赖聚合,只引autoconfigure和第三方依赖)。2.在autoconfigure模块写配置属性类:@ConfigurationProperties(prefix="xxx") + 属性字段。3.写自动配置类:@AutoConfiguration + @ConditionalOnClass + @EnableConfigurationProperties + @Bean注册核心组件。4.注册自动配置:在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)或META-INF/spring.factories(2.7以下)中写配置类全限定名。5.在starter模块的pom.xml中引入autoconfigure模块和必要的第三方依赖。命名规范:官方Starter格式spring-boot-starter-xxx(如spring-boot-starter-data-redis),第三方Starter格式xxx-spring-boot-starter(如mybatis-spring-boot-starter),千万别用官方前缀。另外建议加@AutoConfigureBefore/After控制配置类加载顺序,加@ConditionalOnMissingBean让用户可覆盖默认Bean。

【面试题】10. SpringBoot中如何切换嵌入式容器?Tomcat、Undertow、Jetty各自的优缺点和选型建议? ​

【标准答案】切换方式就是排掉默认Tomcat依赖,引入新容器依赖。以切换Undertow为例:在spring-boot-starter-web中exclusion掉spring-boot-starter-tomcat,然后引入spring-boot-starter-undertow。三者的对比:Tomcat——生态最成熟,社区最大,默认选择,稳定可靠,但内存占用相对较高,高并发场景下线程模型不如Undertow;Undertow——基于NIO,轻量级,内存占用低,高并发性能好,支持Servlet4.0,适合微服务和容器化部署,缺点是社区和文档不如Tomcat丰富;Jetty——同样轻量,长连接性能好,常用于嵌入式场景和WebSocket应用,但企业级生产用的少。选型建议:一般业务用Tomcat就够了,不用折腾;如果是高并发、内存敏感的微服务,考虑Undertow;需要WebSocket或嵌入式场景可以看Jetty。调优方面:Tomcat调server.tomcat.max-threads、accept-count;Undertow调server.undertow.threads.io/worker、buffer-size;注意Undertow默认不支持JSP。