面试题:Spring Boot 原理 (20)

面试题: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=DEBUG

2、使用 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 依赖树)。

考察点:技术债务管理、大版本迁移、生态兼容性评估。

Read more

Go map 底层原理

Go map 底层原理

Go map 底层原理 * 1. 一语戳破哈希表 * 2. 经典版:Go map 到底长什么样 * 2.1 `hmap` 解决什么问题 * 2.2 `bmap` 解决什么问题 * 2.3 `tophash[8]` 到底在干什么 * 2.4 `overflow bucket` 是怎么来的 * 3. 扩容不是“多加几个桶”那么简单 * 3.1 为什么旧桶必须搬 * 3.2 为什么 Go 要做渐进式扩容 * 3.3 增量扩容和等量扩容 * 4. 并发安全:原生 map 为什么不能裸奔 * 5. 现版本的Go

By Ne0inhk
【MySQL数据库基础】(二)MySQL 数据库基础从入门到上手,一篇带你吃透核心知识点!

【MySQL数据库基础】(二)MySQL 数据库基础从入门到上手,一篇带你吃透核心知识点!

目录 前言 一、为什么需要数据库?文件存储的痛点全解析 二、主流数据库大盘点,MySQL 的适用场景是什么? 2.1 主流数据库特性对比 2.2 MySQL 的核心优势 三、MySQL 基础操作,从安装到数据 CRUD 手把手教 3.1 MySQL 的多平台安装方式 3.2 连接 MySQL 服务器,核心指令解析 指令参数详解 简化连接方式 连接成功的反馈 3.3 MySQL 服务器管理(Windows 平台) 3.4 服务器、数据库、表的层级关系 3.5 MySQL 核心

By Ne0inhk
开发兜不住?让数据库来兜底:金仓 SQL 防火墙的工程化实践

开发兜不住?让数据库来兜底:金仓 SQL 防火墙的工程化实践

开发兜不住?让数据库来兜底:金仓 SQL 防火墙的工程化实践 在真实的生产环境中,数据库安全从来不是“写完代码就结束”的问题,而是一个贯穿系统生命周期的持续对抗过程。哪怕你已经严格执行参数化查询、ORM 框架封装、输入校验等规范,仍然无法保证系统绝对无注入风险——遗留系统、动态 SQL、第三方组件、甚至临时脚本,都会成为潜在突破口。 这也是为什么越来越多企业开始将防线下沉到数据库层:既然应用层不可控,那就让数据库成为最后一道“强制执行的安全边界”。 本文结合 KingbaseES 的 SQL 防火墙机制,从原理、模式设计到性能表现,讲清楚它是如何在工程上解决 SQL 注入问题的。 一、SQL 注入的本质:语义劫持,而不是“字符串拼接问题” 很多人对 SQL 注入的理解还停留在“拼接字符串不安全”,但从数据库视角来看,本质其实是: 攻击者篡改了 SQL 的语义结构(

By Ne0inhk