面试题:Spring Boot 原理 (20)
1. Spring Boot 的自动配置是如何工作的?结合 @EnableAutoConfiguration、spring.factories 和条件注解说明其加载机制。
答案:
Spring Boot 自动配置的核心流程如下:
1、入口:@SpringBootApplication 包含 @EnableAutoConfiguration。
2、加载候选配置类:
- Spring Boot 启动时扫描所有 JAR 包中的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 2.7+)或旧版的 META-INF/spring.factories。
- 读取其中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的全限定类名列表。
3、条件过滤:
- 每个自动配置类(如 DataSourceAutoConfiguration)都带有 @ConditionalOn… 注解(如 @ConditionalOnClass, @ConditionalOnMissingBean)。
- Spring 容器在注册 Bean 前会评估这些条件,只有满足条件的配置类才会生效。
4、Bean 注册:
- 自动配置类通过 @Configuration + @Bean 方法向容器注册组件(如 DataSource, JdbcTemplate)。
5、优先级控制:
- 用户自定义的 Bean 优先于自动配置(因 @ConditionalOnMissingBean)。
- 可通过 @AutoConfigureBefore/After 控制自动配置类之间的顺序。
考察点:SPI 机制、条件装配、启动流程、扩展点设计。
2. Spring Boot 应用的完整启动流程是怎样的?从 main() 到 Web 容器启动经历了哪些关键步骤?
答案:
Web应用关键步骤如下:
1、执行 SpringApplication.run():
- 创建 SpringApplication 实例,推断应用类型(SERVLET/REACTIVE/NONE)。
2、初始化监听器和初始器:
- 加载 spring.factories 中的 ApplicationContextInitializer 和 ApplicationListener。
3、创建 ApplicationContext:
- 根据应用类型创建 AnnotationConfigServletWebServerApplicationContext。
4、准备上下文(prepareContext):
- 应用 ApplicationContextInitializer。
- 注册主配置类(即带 @SpringBootApplication 的类)。
5、刷新上下文(refresh):
- 调用 AbstractApplicationContext.refresh(),这是 Spring 的核心生命周期方法。
- 执行 BeanFactoryPostProcessor(包括 ConfigurationClassPostProcessor 解析 @Configuration)。
- 扫描并注册 BeanDefinition。
- 实例化非懒加载的单例 Bean。
6、内嵌 Web 容器启动:
- ServletWebServerFactoryAutoConfiguration 生效,创建 TomcatServletWebServerFactory。
- 在 refresh 阶段末尾,调用 onRefresh() → createWebServer() → 启动 Tomcat。
7、发布 ApplicationStartedEvent / ApplicationReadyEvent。
考察点:启动生命周期、上下文刷新、内嵌容器集成、事件机制。
3. 如何自定义 Starter?请说明其结构、自动配置类设计原则及如何避免与用户配置冲突。
答案:
Starter 结构:
- my-spring-boot-starter(依赖管理)
- my-spring-boot-autoconfigure(自动配置逻辑)
设计原则:
1、分离依赖与配置:starter 只声明依赖,autoconfigure 模块包含实际逻辑。
2、使用条件注解:
@ConditionalOnClass(MyService.class)@ConditionalOnMissingBean(MyService.class)// 允许用户覆盖@ConfigurationpublicclassMyServiceAutoConfiguration{@BeanpublicMyServicemyService(){returnnewMyService();}}3、提供配置属性绑定:
@ConfigurationProperties("my.service")publicclassMyServiceProperties{/* ... */}并在自动配置类中注入。
4、注册到 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.MyServiceAutoConfiguration避免冲突:
- 始终使用 @ConditionalOnMissingBean。
- 属性前缀命名规范(如 my.starter.xxx)。
- 提供默认值但允许完全关闭(如 enabled 开关)。
考察点:模块化设计、开闭原则、配置优先级。
4. Spring Boot 中的外部化配置加载顺序是什么?如何实现动态刷新配置?
答案:
加载优先级(高→低):
1、命令行参数(–server.port=8081)
2、SPRING_APPLICATION_JSON
3、ServletConfig / ServletContext 初始化参数
4、JNDI 属性
5、Java System Properties (System.getProperties())
6、OS 环境变量
7、application.properties(当前目录/config → 当前目录 → classpath:/config/ → classpath:/)
8、@PropertySource 注解
9、默认属性(SpringApplication.setDefaultProperties)
动态刷新配置:
1、方案1(推荐):使用 Spring Cloud Config + @RefreshScope
- 添加 spring-boot-starter-actuator 和 spring-cloud-starter-config
- 在需要刷新的 Bean 上加 @RefreshScope
- 调用 /actuator/refresh 触发刷新
2、方案2:监听 EnvironmentChangeEvent
@EventListenerpublicvoidonEnvironmentChange(EnvironmentChangeEvent event){if(event.getKeys().contains("my.config.key")){// 手动更新内部状态}}注意:普通 @ConfigurationProperties Bean 不会自动刷新,需配合 @RefreshScope。
考察点:配置管理、生产运维、云原生适配。
5. Spring Boot 如何实现内嵌 Web 容器的可插拔?如何切换为 Undertow 或 Jetty?
答案:
可插拔机制:
- Spring Boot 通过 ServletWebServerFactory 接口抽象 Web 容器。
- 自动配置类 TomcatServletWebServerFactoryConfiguration、JettyServletWebServerFactoryConfiguration 等根据 classpath 条件生效。
- 例如:若 org.eclipse.jetty.server.Server 在 classpath 且 org.apache.catalina.startup.Tomcat 不在,则启用 Jetty。
切换方式:
<!-- 排除 Tomcat--><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><!-- 引入 Undertow--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency>考察点:SPI 设计、依赖管理。
6. Spring Boot Actuator 的端点(Endpoints)安全如何保障?如何自定义健康检查?
答案:
安全控制:
- 默认只暴露 /health 和 /info(Web 环境)。
- 通过 management.endpoints.web.exposure.include=* 开放所有端点(生产环境必须配合安全)。
- 必须集成 Spring Security:
management: endpoints: web: exposure: include: health,metrics,env endpoint: health: show-details: always @ConfigurationpublicclassActuatorSecurityextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{ http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests().anyRequest().hasRole("ACTUATOR").and().httpBasic();}}自定义健康检查:
@ComponentpublicclassCustomHealthIndicatorimplementsHealthIndicator{@OverridepublicHealthhealth(){boolean isOk =checkExternalService();if(isOk){returnHealth.up().withDetail("service","available").build();}else{returnHealth.down().withDetail("reason","timeout").build();}}}考察点:生产安全、可观测性、扩展能力。
7. Spring Boot 中如何正确处理异步任务?@Async 的线程池如何配置?为什么不能在同一个类中调用?
答案:
正确使用 @Async:
- 在启动类或配置类上加 @EnableAsync。
- 在 Service 方法上加 @Async。
- 不能在同一个类中调用:因为 @Async 基于代理(JDK/CGLIB),内部调用绕过代理,不会异步执行。
自定义线程池(避免使用默认SimpleAsyncTaskExecutor):
@Configuration@EnableAsyncpublicclassAsyncConfig{@Bean("taskExecutor")publicExecutortaskExecutor(){ThreadPoolTaskExecutor executor =newThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-task-"); executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy()); executor.initialize();return executor;}}异常处理:
- 实现 AsyncUncaughtExceptionHandler 捕获未处理异常。
- 或返回 CompletableFuture 显式处理异常。
考察点:AOP 代理机制、线程池调优、异常治理。
8. Spring Boot 应用内存泄漏的常见原因有哪些?如何排查和优化?
答案:
常见原因:
1、静态集合持有 Bean 引用:如 static Map 缓存未清理。
2、未关闭资源:数据库连接、文件流、HTTP 客户端未 close。
3、ThreadLocal 未清理:尤其在线程池中复用线程。
4、Actuator 端点缓存:如 /heapdump 生成大文件。
5、CGLIB 代理类过多:动态生成类占用 Metaspace。
排查工具:
- jstat -gc :观察 GC 频率和堆内存。
- jmap -histo:live :查看对象分布。
- jmap -dump:format=b,file=heap.hprof + MAT 分析。
- Spring Boot Actuator 的 /metrics/jvm.memory.used。
优化建议:
- 使用 WeakHashMap 或显式 TTL 缓存(如 Caffeine)。
- 资源使用 try-with-resources。
- ThreadLocal 使用后调用 remove()。
- 限制线程池大小和队列容量。
考察点:JVM 调优、生产问题定位、资源管理。
9. Spring Boot 如何支持多数据源?请说明动态数据源切换的实现原理。
答案:
静态多数据源:
- 定义多个 @Configuration,分别创建 DataSource、SqlSessionFactory、TransactionManager。
- 使用 @Primary 指定默认数据源。
动态数据源(基于AOP):
1、自定义注解:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceDS{Stringvalue();}2、数据源路由:
publicclassDynamicDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){returnDataSourceContextHolder.get();// ThreadLocal 存储}}3、AOP 切换:
@Aspect@ComponentpublicclassDataSourceAspect{@Before("@annotation(ds)")publicvoidswitchDS(DS ds){DataSourceContextHolder.set(ds.value());}@After("@annotation(ds)")publicvoidrestoreDS(){DataSourceContextHolder.clear();}}4、配置数据源 Map:
@Bean@PrimarypublicDataSourcedynamicDataSource(){DynamicDataSource ds =newDynamicDataSource(); ds.setTargetDataSources(Map.of("master", masterDS,"slave", slaveDS)); ds.setDefaultTargetDataSource(masterDS);return ds;}考察点:AOP、数据源抽象、线程上下文传递。
10. Spring Boot 3.x 迁移的主要变化有哪些?GraalVM 原生镜像支持带来了什么?
答案:
主要变化:
1、最低 JDK 版本要求 JDK 17:
- 利用新语言特性(如 Records、Pattern Matching)。
- 与 Jakarta EE 9+ 对齐(包名从 javax.* → jakarta.*)。
2、GraalVM Native Image 支持:
- 通过 spring-native 项目(已合并到 Spring Boot 3)。
- 编译为 native 可执行文件,启动快(毫秒级)、内存低(~50MB)。
- 限制:反射、动态代理需显式配置(@NativeHint)。
3、移除过时 API:
- 如 WebMvcConfigurerAdapter(Java 8 接口默认方法已足够)。
- Micrometer 2.0 集成:
- 统一指标观测,替代旧版 Metrics。
考察点:技术演进、云原生。
11. Spring Boot 中 @Configuration 类为何默认是 CGLIB 代理?它和 @Bean 方法的“方法拦截”机制有何关系?
答案:
- Spring Boot 默认对 @Configuration 类启用 CGLIB 代理(即使没有 @EnableAspectJAutoProxy),这是由 - ConfigurationClassPostProcessor 在解析阶段决定的。
- 目的:实现 @Bean 方法的单例语义保证(即多次调用同一 @Bean 方法返回同一个实例)。
示例:
@ConfigurationpublicclassAppConfig{@BeanpublicServiceAserviceA(){returnnewServiceA();}@BeanpublicServiceBserviceB(){returnnewServiceB(serviceA());// 调用本类的 serviceA()}}- 如果没有代理,serviceB() 中调用 serviceA() 会创建新实例,破坏单例。
- 有了 CGLIB 代理,serviceA() 调用会被拦截,转而从 Spring 容器中获取已注册的单例 Bean。
考察点:Spring 容器生命周期、代理机制、配置类语义保障。
12. Spring Boot 应用启动慢的常见原因有哪些?如何系统性地诊断和优化启动时间?
答案:
常见原因:
1、自动配置类过多:大量 @ConditionalOn… 条件判断耗时。
2、组件扫描范围过大:@ComponentScan 扫描了不必要的包。
3、数据库连接初始化慢:HikariCP 获取连接超时或 DNS 解析慢。
4、第三方 SDK 初始化:如 Redis、Kafka 客户端建立连接。
5、类加载慢:JAR 包过多、Fat Jar 解压开销大。
诊断工具:
1、启用 Startup Info Logger:
logging.level.org.springframework.boot.StartupInfoLogger=DEBUG2、使用 Spring Boot 2.7+ 的 Startup Metrics:
management: metrics: enable: jvm:true tags: application: ${spring.application.name}查看 /actuator/metrics/startup.*。
3、使用 Async Profiler 或 JFR (Java Flight Recorder) 分析 CPU/锁/IO。
优化手段:
- 排除不必要的自动配置:@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
- 缩小 @ComponentScan 范围。
- 延迟初始化(Spring Boot 2.2+):
spring.main.lazy-initialization=true- 使用 GraalVM Native Image(Spring Boot 3+)实现毫秒级启动。
考察点:性能工程、可观测性、启动优化策略。
13. Spring Boot 如何实现优雅停机?其内部机制是什么?需要注意哪些陷阱?
答案:
启用方式(Spring Boot 2.3+):
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase:30s 内部机制:
1、收到 SIGTERM 信号后,Spring Boot:
- 停止接收新请求(Tomcat/Jetty 拒绝新连接)。
- 等待正在处理的请求完成(最长 timeout-per-shutdown-phase)。
- 依次关闭:Web 容器 → 执行 DisposableBean.destroy() → 关闭 ApplicationContext。
2、利用 Spring 的 Lifecycle 和 SmartLifecycle 接口协调关闭顺序。
注意事项(陷阱):
- 长任务阻塞:若业务线程执行长时间任务(如 while(true)),不会被中断,需自行监听 ContextClosedEvent。
- 异步任务未完成:@Async 任务可能被强制终止,建议使用 TaskExecutor 的 awaitTermination。
- K8s 就绪探针:应配合 readinessProbe 在停机前将 Pod 标记为 NotReady,避免流量打入。
考察点:云原生运维、服务治理、生命周期管理。
14. Spring Boot 中 ApplicationContextInitializer 和 ApplicationRunner / CommandLineRunner 的执行时机和用途有何区别?
答案:

典型场景:
- ApplicationContextInitializer:Vault 配置注入、K8s ConfigMap 动态绑定。
- CommandLineRunner:启动时执行 DB migration、加载字典表到内存。
考察点:扩展点时机、初始化逻辑分层、安全敏感操作位置。
15. Spring Boot 如何防止“循环依赖”?三级缓存机制在 Spring Boot 中是否仍然有效?
答案:
Spring Boot 并未改变 Spring Framework 的循环依赖处理机制,依然依赖 三级缓存:
- singletonObjects:成品单例池
- earlySingletonObjects:早期暴露的 Bean(尚未完成属性注入)
- singletonFactories:ObjectFactory,用于生成早期引用
Spring Boot 中的注意事项:
- 构造器注入(@Autowired on constructor)无法解决循环依赖,会直接报错(推荐做法,避免设计缺陷)。
- 字段/Setter 注入可被三级缓存解决,但属于“带伤运行”。
- 最佳实践:通过重构(如引入中介者、事件驱动)消除循环依赖,而非依赖框架兜底。
考察点:IoC 容器原理、设计反模式识别、代码质量意识。
16. Spring Boot Actuator 的 /health 端点是如何聚合多个健康指示器的?如何实现“部分失败仍标记为 UP”?
答案:
- 默认情况下,只要有一个 HealthIndicator 返回 DOWN,整体状态就是 DOWN。
- 自定义聚合策略:通过 ManagementHealthAggregator 实现。
实现“部分失败仍 UP”:
@Component publicclass CustomHealthAggregatorimplementsHealthAggregator{@OverridepublicHealthaggregate(Map<String,Health> healths){Status status =Status.UP;// 例如:只有核心服务 DOWN 才整体 DOWNif(isCoreServiceDown(healths)){ status =Status.DOWN;} returnnew Health.Builder(status).withDetails(healths).build();}privatebooleanisCoreServiceDown(Map<String,Health> healths){return healths.get("database")!=null&& healths.get("database").getStatus()==Status.DOWN;}}然后在配置中指定:
management: endpoint: health: show-details: always health: defaults: aggregator: customHealthAggregator 考察点:可观测性定制、业务健康模型、运维策略落地。
17. Spring Boot 中如何正确处理分布式事务?Seata 与本地事务 + 补偿机制的选型依据是什么?
答案:
方案对比:

Spring Boot 集成 Seata 示例:
1、添加依赖:seata-spring-boot-starter
2、配置 application.yml 指向 Seata Server
3、在业务方法加 @GlobalTransactional
选型建议:
- 金融核心系统 → Seata AT / TCC
- 电商下单 → Saga(订单→库存→积分)
- 日志/通知类 → 消息队列最终一致
考察点:分布式系统设计、CAP 权衡、落地经验。
18. Spring Boot 应用在 Kubernetes 中部署时,如何设计就绪探针和存活探针?
答案:
Liveness Probe(存活探针):
- 作用:判断应用是否“活着”,失败则重启 Pod。
- 端点:/actuator/health/liveness
- 配置建议:
livenessProbe: httpGet: path:/actuator/health/liveness port:8080 initialDelaySeconds:60 periodSeconds:30 failureThreshold:3不要包含外部依赖(如 DB 连接),否则网络抖动导致误杀。
Readiness Probe(就绪探针):
- 作用:判断应用是否“准备好接收流量”,失败则从 Service Endpoints 移除。
- 端点:/actuator/health/readiness
- 配置建议:
readinessProbe: httpGet: path:/actuator/health/readiness port:8080 initialDelaySeconds:10 periodSeconds:10 failureThreshold:1可包含 DB、Redis 等关键依赖检查。
Spring Boot 2.3+ 内置支持:
- 自动注册 livenessState 和 readinessState 健康指示器。
- 可通过 /actuator/health 查看状态。
考察点:云原生运维、弹性伸缩、服务网格集成。
19. Spring Boot 中如何防止 JSON 序列化导致的“无限递归”或“敏感信息泄露”?
答案:
问题场景:
- 实体类双向关联(如 User ↔ Order)导致 Jackson 序列化栈溢出。
- 密码、身份证等字段被意外返回。
解决方案:
- 防止无限递归:
@JsonManagedReference/@JsonBackReference@JsonIgnore(简单粗暴) 推荐:使用 DTO 转换,避免直接返回 Entity。
防止敏感信息泄露:
@JsonIgnore 标记敏感字段
使用 @JsonView 控制不同接口返回字段:
publicclassViews{publicstaticclassPublic{}publicstaticclassInternalextendsPublic{}}publicclassUser{@JsonView(Views.Public.class)privateString name;@JsonView(Views.Internal.class)privateString password;}Controller 中指定:
@JsonView(Views.Public.class)@GetMapping("/user")publicUsergetUser(){...}全局配置:
@ConfigurationpublicclassJacksonConfig{@BeanpublicJackson2ObjectMapperBuilderjackson2ObjectMapperBuilder(){returnnewJackson2ObjectMapperBuilder().failOnEmptyBeans(false).featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);}}考察点:安全编码、API 设计、序列化控制。
20. Spring Boot 3 中 Jakarta EE 9+ 迁移带来了哪些兼容性问题?如何平滑升级?
答案:
主要变化:
- 所有 javax.* 包名改为 jakarta.*:
- javax.servlet → jakarta.servlet
- javax.persistence → jakarta.persistence
- javax.validation → jakarta.validation
兼容性问题:
- 第三方库未升级:如 MyBatis、旧版 Hibernate 仍依赖 javax.*。
- 自定义 Filter/Listener:需修改 import 语句。
- Swagger/OpenAPI:需升级到支持 Jakarta 的版本(如 Springdoc OpenAPI 1.6+)。
平滑升级策略:
步骤1:升级到 Spring Boot 2.7,修复所有警告。
步骤2:升级到 Spring Boot 3.x + JDK 17+。
步骤3:批量替换 import。
步骤4:验证所有依赖是否兼容 Jakarta(查看 Maven/Gradle 依赖树)。
考察点:技术债务管理、大版本迁移、生态兼容性评估。