Springboot相关
📚【面试题目1:Spring Boot的核心特性是什么?与Spring的区别和联系?】
回答要点关键字
(1) 核心特性:自动配置、起步依赖、嵌入式服务器、无XML配置、Actuator监控
(2) 与Spring关系:Spring的增强封装、基于Spring IoC/DI核心、简化配置与部署
(3) 核心优势:开发效率高、配置简化、快速集成第三方组件、开箱即用
(4) 底层支撑:@SpringBootApplication注解、SPI机制(Spring Factories)、自动配置类
打开详情
🍺基础回答:
Spring Boot 就是 Spring 的“懒人版”,核心是让开发更高效。它的核心特性比如自动配置,不用像Spring那样写一堆XML或Java配置,能自动识别环境帮你配置Bean;还有起步依赖,比如要用到Web开发,直接引入spring-boot-starter-web,里面自动包含了Spring MVC、Tomcat这些依赖,不用自己一个个导。和Spring的关系就是Spring Boot基于Spring核心(IoC/DI这些),只是帮我们简化了配置、集成了嵌入式服务器(不用单独部署Tomcat),还能通过Actuator监控应用,总之就是让我们少写配置、快速开发和部署。
🎉高级扩展版:
- 核心特性深度解析:- 自动配置:核心是
@EnableAutoConfiguration注解,通过Spring Factories机制加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的自动配置类(如DataSourceAutoConfiguration、WebMvcAutoConfiguration),根据类路径下的依赖、配置文件(application.yml/properties)动态判断是否需要配置对应的Bean,支持@Conditional系列注解灵活控制(如@ConditionalOnClass、@ConditionalOnMissingBean);- 起步依赖(Starter):本质是Maven依赖聚合,每个Starter(如spring-boot-starter-data-redis)都包含了该场景所需的核心依赖、传递依赖,遵循“约定优于配置”,避免依赖版本冲突(Spring Boot统一管理版本);- 嵌入式服务器:默认集成Tomcat(也支持Jetty、Undertow),通过嵌入式服务器可将应用打包为JAR包,直接通过java -jar运行,无需单独部署Web服务器;- 无XML配置:支持全注解配置(@Configuration、@Bean、@ComponentScan等),也可通过@ImportResource引入少量必要XML;- Actuator:提供应用监控端点(如/actuator/health、/actuator/metrics),可监控应用健康状态、性能指标、Bean信息等,支持自定义端点。2. 与Spring的区别和联系:- 联系:Spring Boot是Spring框架的扩展,完全基于Spring的IoC容器、DI、AOP等核心机制,所有Spring的特性(如事务管理、Spring MVC)在Spring Boot中都能直接使用;- 区别:Spring是“配置驱动”(需手动配置Bean、依赖管理、服务器部署),Spring Boot是“约定驱动+自动配置”(简化配置、统一依赖版本、嵌入式服务器、开箱即用);Spring Boot的核心是“简化Spring应用开发”,而非替代Spring。
📌 加分项:
提到Spring Boot的版本管理机制(parent POM:spring-boot-starter-parent,统一管理依赖版本,开发者无需指定版本号);对比不同嵌入式服务器的性能(Undertow并发性能优于Tomcat,适合高并发场景,可通过排除Tomcat依赖引入Undertow);自动配置的自定义扩展(通过@ConfigurationProperties绑定配置、@Bean覆盖默认自动配置Bean、spring.factories自定义自动配置类);结合实际场景,如微服务架构中Spring Boot作为服务基础框架,配合Spring Cloud实现服务注册发现、配置中心等。
⚠️注意事项:
自动配置并非“万能”,复杂场景(如多数据源、自定义MVC拦截器)仍需手动补充配置;起步依赖虽方便,但需避免引入不必要的Starter(如不需要Web功能却引入spring-boot-starter-web),导致应用体积增大;嵌入式服务器的端口、线程池等参数需根据业务需求优化(如配置server.tomcat.max-threads调整Tomcat最大线程数);Spring Boot版本升级需注意兼容性(如2.x到3.x移除了部分自动配置类、调整了依赖坐标),建议通过spring-boot-dependencies管理版本,而非直接指定依赖版本。
📚【面试题目2:Spring Boot的自动配置原理是什么?如何自定义自动配置?】
回答要点关键字
(1) 核心原理:@EnableAutoConfiguration、Spring Factories机制、自动配置类、@Conditional注解
(2) 执行流程:启动扫描→加载自动配置类→条件判断→Bean注册→配置绑定
(3) 自定义方式:编写自动配置类、META-INF文件配置、@ConfigurationProperties绑定
(4) 关键注解:@ConditionalOnClass、@ConditionalOnMissingBean、@EnableConfigurationProperties
打开详情
🍺基础回答:
Spring Boot自动配置的核心是@EnableAutoConfiguration注解,它会去加载类路径下META-INF目录里的自动配置文件,里面列了各种场景的配置类(比如Redis、数据库的配置类)。这些配置类会根据条件判断(比如类路径下有没有对应的依赖类、有没有手动配置过该Bean),如果满足条件就自动创建对应的Bean到Spring容器里。比如引入了spring-boot-starter-data-redis依赖,自动配置类就会判断存在Redis相关类,然后自动配置RedisTemplate、StringRedisTemplate这些Bean。自定义自动配置的话,就是自己写一个配置类,用@Conditional注解控制生效条件,再把配置类写到META-INF的文件里,让Spring Boot扫描到就行,还能通过@ConfigurationProperties绑定配置文件里的参数。
🎉高级扩展版:
- 自动配置底层原理:- 核心注解组合:
@SpringBootApplication=@SpringBootConfiguration(本质@Configuration) +@ComponentScan(扫描主类所在包及子包) +@EnableAutoConfiguration(核心自动配置开关);- Spring Factories机制:@EnableAutoConfiguration通过AutoConfigurationImportSelector类,加载类路径下所有META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(Spring Boot 2.7+)或META-INF/spring.factories文件(旧版本)中的自动配置类全限定名,将其导入Spring容器;- 条件判断机制:自动配置类依赖@Conditional系列注解实现灵活生效控制,常用注解: -@ConditionalOnClass:类路径下存在指定类时生效(如DataSourceAutoConfiguration依赖@ConditionalOnClass(DataSource.class)); -@ConditionalOnMissingBean:容器中不存在指定Bean时生效(允许用户手动配置Bean覆盖自动配置); -@ConditionalOnProperty:配置文件中存在指定属性且值匹配时生效(如@ConditionalOnProperty(prefix="spring.datasource", name="url")); -@ConditionalOnWebApplication:Web应用环境下生效;- 配置绑定:通过@ConfigurationProperties注解将配置文件(application.yml/properties)中的属性绑定到JavaBean(如DataSourceProperties绑定spring.datasource相关配置),自动配置类通过依赖这些属性Bean完成配置。2. 自定义自动配置步骤:- 步骤1:创建配置类,使用@Configuration注解,配合@Conditional注解定义生效条件,通过@Bean注册核心Bean;- 步骤2:创建配置属性类,使用@ConfigurationProperties(prefix="custom")绑定配置文件中的自定义属性;- 步骤3:在resources下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,写入自定义自动配置类的全限定名;- 步骤4:(可选)通过@EnableConfigurationProperties启用配置属性类,或在配置类中直接注入配置属性Bean;- 步骤5:打包发布,其他项目引入依赖后,即可享受自动配置功能(无需手动配置)。
📌 加分项:
对比Spring Boot 2.7+与旧版本自动配置文件的差异(2.7+用AutoConfiguration.imports文件替代spring.factories中的EnableAutoConfiguration配置,更清晰);自定义自动配置的优先级控制(通过@AutoConfigureBefore/@AutoConfigureAfter指定自动配置类的执行顺序);自动配置的调试技巧(启动参数添加--debug,打印自动配置报告,查看哪些配置类生效、哪些未生效及原因);结合实际案例,如自定义Redis集群自动配置类,支持通过配置文件指定集群节点,自动创建RedisClusterTemplate。
⚠️注意事项:
自定义自动配置类需避免与Spring Boot默认自动配置类冲突(可通过@ConditionalOnMissingBean让用户手动配置优先);@ComponentScan扫描范围需注意,避免扫描到不必要的类导致自动配置失效;配置属性类需提供getter/setter方法,否则无法绑定配置文件属性;@Conditional注解的条件判断需精准,避免出现“预期外生效”或“预期内不生效”(如@ConditionalOnClass需确保依赖类在类路径下);自定义自动配置需测试不同场景(如存在用户自定义Bean、不存在自定义Bean、不同配置属性值),确保稳定性。
📚【面试题目3:Spring Boot的配置文件有哪些类型?配置优先级是怎样的?如何实现配置动态刷新?】
回答要点关键字
(1) 配置文件类型:application.yml/properties、bootstrap.yml/properties、自定义配置文件
(2) 优先级:命令行参数 > 系统环境变量 > 应用配置文件 > bootstrap配置 > 默认配置
(3) 动态刷新:Spring Cloud Config/Nacos + @RefreshScope、Actuator端点触发
(4) 配置方式:配置绑定(@ConfigurationProperties/@Value)、Profile多环境配置
打开详情
🍺基础回答:
Spring Boot 常用的配置文件是application.yml和application.properties,还有bootstrap.yml(主要用于Spring Cloud场景,加载更早)。配置优先级挺多的,命令行参数(比如java -jar app.jar --server.port=8081)优先级最高,然后是系统环境变量,再到应用的配置文件(同目录下的application.yml比classpath下的优先级高),bootstrap配置文件比application加载早,但优先级比application低,最后是Spring Boot的默认配置。动态刷新配置的话,要是用了Spring Cloud Config或Nacos这些配置中心,在需要刷新的Bean上加@RefreshScope注解,然后通过Actuator的/actuator/refresh端点触发,就能不用重启应用更新配置了。
🎉高级扩展版:
- 配置文件类型详解:- 应用配置文件(application.yml/properties):核心配置文件,用于配置应用端口、数据源、第三方组件(Redis、MySQL)等,支持Profile多环境配置(如application-dev.yml、application-prod.yml,通过spring.profiles.active激活);- bootstrap.yml/properties:启动阶段加载的配置文件,加载顺序早于application(Spring Boot启动时先加载bootstrap,再加载application),主要用于配置: - 配置中心地址(如spring.cloud.config.uri); - 加密/解密配置(如spring.cloud.bootstrap.enabled=true); - 固定不变的基础配置(如应用名称、注册中心地址);- 自定义配置文件:通过
@PropertySource("classpath:custom.properties")注解引入,需配合@Configuration使用,适用于拆分配置(如将Redis配置单独放在redis.properties)。2. 配置优先级(从高到低):- 命令行参数(--key=value,如--spring.datasource.url=xxx);- 系统环境变量(如SERVER_PORT=8081,对应配置文件中的server.port);- 应用外部配置文件(如应用所在目录下的config/application.yml,比classpath下的优先级高);- 应用内部配置文件(classpath下的application.yml,以及激活的Profile配置文件,如application-dev.yml);- bootstrap.yml/properties(包括外部和内部的bootstrap文件);- Spring Boot自动配置的默认值(如server.port默认8080)。3. 动态刷新实现方案:- 方案一:Spring Cloud Config + Actuator: - 步骤:引入spring-cloud-starter-config、spring-boot-starter-actuator依赖; - 配置文件中指定配置中心地址(spring.cloud.config.uri); - 在需要动态刷新的Bean上添加@RefreshScope注解(如@RestController + @RefreshScope); - 暴露refresh端点(management.endpoints.web.exposure.include=refresh); - 触发刷新:发送POST请求到/actuator/refresh,或通过配置中心的WebHook自动触发;- 方案二:Nacos Config(更轻量,支持自动刷新): - 步骤:引入spring-cloud-starter-alibaba-nacos-config依赖; - 配置Nacos地址(spring.cloud.nacos.config.server-addr); - 无需手动触发,Nacos配置变更后会自动推送到应用,配合@RefreshScope或@NacosValue注解更新配置;- 核心原理:动态刷新通过销毁旧的Bean实例,重新创建新的Bean实例(基于最新配置)实现,@RefreshScope本质是一个作用域代理,当配置变更时,代理会重新获取Bean实例。
📌 加分项:
对比@ConfigurationProperties与@Value的差异(@ConfigurationProperties适合批量绑定配置,支持类型转换和校验,@Value适合单个属性注入,支持SpEL表达式);配置加密解密(Spring Boot支持JCE加密,通过encrypt.key配置密钥,对敏感配置(如数据库密码)加密存储,格式为{cipher}加密后的字符串);多环境配置的进阶用法(通过spring.profiles.group分组激活多个Profile,如spring.profiles.active=dev,log,同时激活dev和log环境的配置);动态刷新的注意事项(@RefreshScope标注的Bean会被重新创建,需确保Bean是无状态的,避免状态丢失);配置优先级的实际应用(生产环境通过命令行参数覆盖配置文件中的端口、数据源地址,无需修改配置文件)。
⚠️注意事项:
bootstrap配置文件仅在Spring Cloud环境下生效(需引入Spring Cloud相关依赖),纯Spring Boot应用中bootstrap文件不会被加载;命令行参数优先级过高,生产环境需注意安全(避免敏感配置通过命令行暴露);动态刷新仅对@RefreshScope标注的Bean生效,未标注的Bean不会更新配置;@RefreshScope标注的Bean若有状态(如成员变量存储临时数据),刷新后状态会丢失,需设计为无状态Bean;配置文件中属性名大小写不敏感,但建议遵循“kebab-case”(如spring.datasource.url),避免使用驼峰命名;多Profile配置时,激活的Profile配置文件会覆盖application.yml中的默认配置,而非替换。
📚【面试题目4:Spring Boot中的Starter是什么?如何自定义一个Starter?】
回答要点关键字
(1) Starter本质:Maven依赖聚合、自动配置入口、“约定优于配置”的实现
(2) 核心组成: Starter模块(依赖管理)、自动配置模块(配置类+条件判断)
(3) 自定义步骤:创建模块→依赖管理→编写自动配置类→配置Spring Factories→测试打包
(4) 关键规范:命名规范(xxx-spring-boot-starter)、依赖传递控制、配置绑定
打开详情
🍺基础回答:
Spring Boot的Starter就是一个“依赖包集合”,比如spring-boot-starter-web,里面已经包含了Spring MVC、Tomcat、Jackson这些Web开发需要的所有依赖,不用我们自己一个个去导,还能避免依赖版本冲突(Spring Boot统一管理)。它的核心是“约定优于配置”,引入对应的Starter后,Spring Boot会自动配置相关的Bean,开箱即用。自定义Starter的话,大概分几步:先创建两个模块(一个是Starter模块,负责管理依赖;一个是自动配置模块,负责写配置类),然后在自动配置模块里写配置类(用@Configuration、@Bean、@Conditional注解),再把配置类配置到Spring Factories文件里,最后打包发布,别人引入这个Starter就能自动使用配置好的功能了。
🎉高级扩展版:
- Starter的本质与核心组成:- 本质:Starter是一套“依赖管理+自动配置”的组合方案,核心目标是“简化第三方组件的集成”,开发者只需引入Starter依赖,无需手动配置依赖和Bean;- 核心组成: - Starter模块(xxx-spring-boot-starter):仅负责依赖管理,不包含业务逻辑,通过Maven的
引入自动配置模块、核心依赖、第三方依赖(如自定义Redis Starter需引入spring-boot-starter、spring-boot-starter-data-redis),遵循命名规范(官方Starter命名为spring-boot-starter-xxx,自定义Starter命名为xxx-spring-boot-starter,避免冲突); - 自动配置模块(xxx-spring-boot-autoconfigure):包含自动配置类、配置属性类、Spring Factories文件,是Starter的核心,通过自动配置类动态注册Bean,配置属性类绑定配置文件参数。2. 自定义Starter详细步骤:- 步骤1:创建Maven模块(两个模块分离,也可合并为一个): - 模块1:custom-starter-spring-boot-starter(Starter模块):pom.xml中引入自动配置模块和必要依赖; - 模块2:custom-starter-spring-boot-autoconfigure(自动配置模块):核心业务模块;- 步骤2:编写配置属性类:用 @ConfigurationProperties(prefix="custom.starter")注解,绑定配置文件中的自定义属性(如custom.starter.enabled、custom.starter.timeout);- 步骤3:编写自动配置类: - 用@Configuration注解标识配置类; - 用@Conditional系列注解控制生效条件(如@ConditionalOnClass、@ConditionalOnProperty); - 用@Bean注解注册核心Bean(如CustomService); - 用@EnableConfigurationProperties启用配置属性类;- 步骤4:配置Spring Factories:在自动配置模块的resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,写入自动配置类的全限定名;- 步骤5:打包发布:使用Maven的clean install命令打包两个模块,发布到本地仓库或私服;- 步骤6:测试使用:其他Spring Boot项目引入custom-starter-spring-boot-starter依赖,在配置文件中配置custom.starter相关属性,即可自动注入CustomService Bean,无需手动配置。3. Starter的依赖传递控制:- 避免引入不必要的依赖(使用true 标记可选依赖,让用户按需引入);- 统一依赖版本(继承spring-boot-starter-parent或导入spring-boot-dependencies,避免版本冲突);- 排除冲突依赖(通过排除Starter中可能与用户项目冲突的依赖)。
📌 加分项:
提到Starter的依赖仲裁机制(Spring Boot的dependencyManagement统一管理依赖版本,Starter中的依赖无需指定版本号,由Spring Boot统一控制);自定义Starter的调试技巧(在测试项目中添加--debug参数,查看自动配置类是否生效);Starter的可扩展性设计(通过@ConditionalOnMissingBean允许用户手动配置Bean覆盖Starter的默认配置);结合实际案例,如自定义一个短信发送Starter(sms-spring-boot-starter),集成阿里云短信服务,用户引入后只需配置accessKey、secretKey即可使用SmsService发送短信;对比官方Starter与自定义Starter的差异(官方Starter经过严格测试,依赖管理更完善,自定义Starter需注意兼容性和稳定性)。
⚠️注意事项:
Starter命名需遵循规范,避免与官方Starter冲突(官方是spring-boot-starter-xxx,自定义是xxx-spring-boot-starter);自动配置类的条件判断需精准,避免在不需要的场景下生效(如@ConditionalOnClass需确保依赖类存在,否则配置类不生效);依赖传递需谨慎,避免引入过重或冲突的依赖(如自定义Starter中引入Tomcat依赖,可能与用户项目的Jetty依赖冲突);配置属性类需提供默认值,避免因用户未配置导致NullPointerException;自定义Starter需兼容不同版本的Spring Boot(通过@ConditionalOnSpringBootVersion控制版本兼容性);打包时需确保Spring Factories文件路径正确(META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports),否则自动配置类无法被加载。
📚【面试题目5:Spring Boot的事务管理如何实现?@Transactional注解的工作原理与失效场景?】
回答要点关键字
(1) 事务实现:基于Spring事务管理(PlatformTransactionManager)、声明式事务(@Transactional)
(2) 核心原理:AOP动态代理(JDK动态代理/CGLIB)、事务拦截器(TransactionInterceptor)
(3) 失效场景:非public方法、内部调用、异常被捕获、事务管理器未配置、隔离级别不支持
(4) 关键属性:propagation(传播机制)、isolation(隔离级别)、rollbackFor(回滚条件)
打开详情
🍺基础回答:
Spring Boot的事务管理特别简单,主要用@Transactional注解实现声明式事务,不用写复杂的代码。原理就是Spring通过AOP给加了这个注解的方法生成代理对象,在方法执行前开启事务,执行成功后提交事务,抛异常了就回滚事务。用的时候直接在Service的方法上加@Transactional就行,还能配置传播机制(比如REQUIRED、REQUIRES_NEW)、隔离级别,指定哪些异常回滚(比如rollbackFor=Exception.class)。不过这个注解也有失效场景,比如用在非public方法上、方法内部调用(比如A方法调用本类的B方法,B加了注解)、异常被try-catch捕获没抛出去,这些情况事务都不会生效。
🎉高级扩展版:
- 事务管理实现原理:- 核心依赖:Spring Boot自动配置
DataSourceTransactionManager(当类路径下存在DataSource且无自定义TransactionManager时),无需手动配置;- 声明式事务核心:@Transactional注解通过AOP实现,Spring扫描到该注解后,由AnnotationTransactionAttributeSource解析注解属性(传播机制、隔离级别等),TransactionInterceptor作为事务拦截器,在目标方法执行前后进行事务控制: - 执行前:获取事务管理器(PlatformTransactionManager),根据传播机制判断是否需要创建新事务或加入现有事务,开启事务并设置隔离级别; - 执行后:若方法正常返回,提交事务;若抛出异常,根据rollbackFor属性判断是否回滚事务(默认仅回滚RuntimeException和Error);- 代理方式:默认情况下,目标类有接口时使用JDK动态代理,无接口时使用CGLIB代理(Spring Boot 2.x默认开启proxyTargetClass=true,无论是否有接口都用CGLIB代理)。2.@Transactional关键属性:- propagation(传播机制):控制事务传播行为,常用: - REQUIRED(默认):若当前有事务则加入,无则创建新事务; - REQUIRES_NEW:无论当前是否有事务,都创建新事务(新事务与原事务独立); - SUPPORTS:若当前有事务则加入,无则以非事务方式执行; - NOT_SUPPORTED:以非事务方式执行,若当前有事务则暂停;- isolation(隔离级别):默认跟随数据库隔离级别,可指定READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE;- rollbackFor:指定需要回滚的异常类型(默认仅回滚RuntimeException和Error),如rollbackFor=Exception.class(所有异常都回滚);- noRollbackFor:指定不需要回滚的异常类型;- timeout:事务超时时间(默认-1,无超时),超过时间未完成则回滚;- readOnly:是否为只读事务(默认false),只读事务仅允许查询操作,可优化性能。3. 失效场景深度解析:- 非public方法:@Transactional仅对public方法生效(AOP代理时会过滤非public方法);- 内部调用:同一类中方法A调用方法B(B加@Transactional),由于是直接调用目标对象,未经过代理对象,事务拦截器无法生效;- 异常被捕获:方法内抛出异常后被try-catch捕获,未重新抛出,事务管理器无法感知异常,不会回滚;- 事务管理器未配置:若项目中存在多个DataSource且未手动配置TransactionManager,Spring Boot无法自动配置,事务失效;- 隔离级别不支持:数据库不支持指定的隔离级别(如MySQL不支持SERIALIZABLE隔离级别在某些存储引擎下);- 注解标注位置错误:标注在Controller层(建议标注在Service层,Controller层可能被多次调用,导致事务传播异常);- proxyTargetClass配置错误:设置proxyTargetClass=false,目标类有接口但注解标注在类上,JDK动态代理仅代理接口方法,类中未实现接口的方法事务失效。
📌 加分项:
提到编程式事务(通过TransactionTemplate实现,适合复杂事务场景,如需要手动控制事务提交/回滚);@Transactional的 propagation 属性实际应用场景(如转账业务用REQUIRED,日志记录用REQUIRES_NEW,确保日志事务与主事务独立);事务失效的排查技巧(开启Spring事务日志debug级别,日志中搜索“Creating new transaction”“Rolling back transaction”判断事务是否生效);分布式事务解决方案(Spring Cloud Alibaba Seata、2PC/TCC/SAGA);结合实际案例,如订单创建业务中,@Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRED)确保订单创建、库存扣减、支付记录添加在同一事务中,任一环节失败都回滚。
⚠️注意事项:
@Transactional注解尽量标注在Service层的public方法上,避免标注在Controller层或非public方法;内部调用导致事务失效的解决方案(通过AopContext.currentProxy()获取代理对象,再调用目标方法,如((UserService)AopContext.currentProxy()).updateUser());rollbackFor属性建议显式指定(如rollbackFor=Exception.class),避免 checked 异常不回滚的问题;事务超时时间不宜设置过长(如超过30秒),避免长期占用数据库连接;只读事务仅用于查询操作,若包含写操作(insert/update/delete),设置readOnly=true会导致事务失效;多数据源场景需手动配置TransactionManager,并通过@Transactional(transactionManager="xxxTransactionManager")指定,否则事务失效。
📚【面试题目6:Spring Boot如何实现统一异常处理?全局异常处理器的原理是什么?】
回答要点关键字
(1) 实现方式:@RestControllerAdvice+@ExceptionHandler、自定义异常类、统一响应体
(2) 核心原理:AOP切面、异常拦截机制、Spring MVC异常解析器
(3) 关键组件:全局异常处理器、自定义业务异常、响应体封装类、异常日志记录
(4) 扩展场景:不同异常类型分类处理、参数校验异常处理、自定义异常码
打开详情
🍺基础回答:
Spring Boot 实现统一异常处理主要靠@RestControllerAdvice和@ExceptionHandler注解。先写一个全局异常处理器类,用@RestControllerAdvice标注,然后在类里写方法,用@ExceptionHandler指定要处理的异常类型(比如NullPointerException、自定义的BusinessException),方法里封装统一的响应体(包含状态码、提示信息、数据)返回给前端。原理就是这个处理器类相当于一个AOP切面,Spring MVC会把所有Controller层抛出的异常都交给它来处理,不用在每个Controller里写try-catch了,既统一了返回格式,又方便维护。比如用户输入参数错误、数据库查询不到数据,都会抛出对应的异常,全局处理器捕获后返回友好提示,还能记录异常日志。
🎉高级扩展版:
- 统一异常处理实现细节:- 核心注解: -
@RestControllerAdvice:组合了@ControllerAdvice和@ResponseBody,表示全局异常处理类,且返回结果为JSON格式,作用范围默认是所有Controller; -@ExceptionHandler(异常类型.class):指定当前方法处理的异常类型,可同时处理多个异常(如@ExceptionHandler({NullPointerException.class, IllegalArgumentException.class}));- 实现步骤: 1. 定义统一响应体类(ResultDTO),包含code(状态码)、message(提示信息)、data(响应数据)字段; 2. 定义自定义业务异常类(如BusinessException),继承RuntimeException,包含异常码和异常信息; 3. 创建全局异常处理器类(GlobalExceptionHandler),用@RestControllerAdvice标注; 4. 在处理器类中编写方法,用@ExceptionHandler分别处理自定义异常、系统异常(如NullPointerException、IOException)、参数校验异常(如MethodArgumentNotValidException); 5. 异常处理方法中记录异常日志(如通过@Slf4j注解打印error日志),封装ResultDTO返回。2. 核心原理:- Spring MVC的异常处理机制:当Controller层抛出异常时,Spring MVC会遍历注册的异常解析器(HandlerExceptionResolver),@RestControllerAdvice+@ExceptionHandler本质是通过ExceptionHandlerExceptionResolver实现的,该解析器会扫描所有标注@ExceptionHandler的方法,根据异常类型匹配对应的处理方法;- AOP切面思想:全局异常处理器相当于一个环绕切面,拦截Controller层的异常抛出,进行统一处理(日志记录、响应封装),无需侵入业务代码;- 异常处理顺序:先匹配精确异常类型(如BusinessException),再匹配父类异常类型(如RuntimeException),最后匹配Exception(所有异常的父类),建议处理方法按“从具体到通用”的顺序编写。3. 扩展场景:- 参数校验异常处理:Spring Boot支持JSR-380规范(如@NotNull、@NotBlank、@Pattern),参数校验失败会抛出MethodArgumentNotValidException(请求体参数)或BindException(路径/请求参数),可在全局处理器中单独处理,返回具体的参数错误信息;- 自定义异常码:在自定义异常类中添加code字段(如200成功、400参数错误、500系统异常),响应体中返回异常码,前端根据码值进行不同处理;- 全局异常日志:通过logback或log4j记录异常堆栈信息,方便问题排查,可结合ELK栈进行日志收集和分析;- 特殊异常处理:如404(NoHandlerFoundException)、403(AccessDeniedException)、500(Throwable),单独处理返回友好提示,避免暴露系统敏感信息。
📌 加分项:
提到@ControllerAdvice与@RestControllerAdvice的区别(@ControllerAdvice需配合@ResponseBody返回JSON,@RestControllerAdvice已包含@ResponseBody);异常处理的优先级控制(多个@RestControllerAdvice类可通过@Order注解指定执行顺序,Order值越小越先执行);参数校验的进阶用法(通过@Validated分组校验,不同场景使用不同校验规则);结合实际业务,如自定义BusinessException用于业务逻辑异常(如“余额不足”“用户不存在”),系统异常(如NullPointerException)单独处理,返回“系统繁忙,请稍后重试”;全局异常处理器的测试(编写单元测试,模拟抛出不同异常,验证响应体是否符合预期)。
⚠️注意事项:
全局异常处理器需确保能被Spring扫描到(包路径在主类扫描范围内,或通过@ComponentScan指定);异常处理方法需返回统一响应体,避免部分异常返回JSON、部分返回页面,导致前端解析混乱;自定义异常建议继承RuntimeException(无需强制捕获),避免继承Exception(checked异常,需手动捕获,否则编译报错);异常日志需记录完整的堆栈信息(log.error("异常信息", e),而非仅记录消息,否则无法排查问题);避免在异常处理方法中抛出新异常,否则会导致异常无法返回给前端;参数校验异常处理时,需遍历BindingResult中的字段错误信息,返回具体的错误提示(如“用户名不能为空”),而非笼统的“参数错误”。
📚【面试题目7:Spring AOP的核心原理是什么?常见失效场景有哪些?】
回答要点关键字
(1) 核心原理:动态代理(JDK动态代理/CGLIB)、切面编程(Aspect)、织入(Weaving)
(2) 核心组件:切面(Aspect)、通知(Advice)、切入点(Pointcut)、连接点(JoinPoint)
(3) 失效场景:非public方法、内部调用、目标类未被Spring管理、代理方式不匹配、通知顺序错误
(4) 实现机制:AOP联盟规范、Spring AOP 动态代理选择规则、织入时机(运行时织入)
打开详情
🍺基础回答:
Spring AOP 是面向切面编程,核心是在不修改业务代码的前提下,给方法加统一功能(比如日志、权限校验、事务)。原理是用动态代理:如果目标类有接口,就用 JDK 动态代理生成代理对象;如果没有接口,就用 CGLIB 代理(通过继承目标类生成子类)。关键组件有切面(放通知的类)、通知(要加的功能,比如前置通知、后置通知)、切入点(指定哪些方法要被增强)。但 AOP 也会失效,比如把通知加在非 public 方法上、方法内部调用(比如 A 方法调用本类的 B 方法,B 被增强)、目标类没交给 Spring 管理(没加 @Component 这些注解),这些情况代理对象没生效,AOP 就没用了。
🎉高级扩展版:
- 核心原理深度解析:- 动态代理机制: - JDK 动态代理:基于接口实现,代理类实现目标接口,通过 InvocationHandler 拦截目标方法调用,仅支持 public 方法; - CGLIB 代理:基于继承实现,代理类继承目标类,重写目标方法,通过 MethodInterceptor 拦截调用,支持非 public 方法(但 Spring AOP 默认仅拦截 public 方法); - Spring AOP 选择规则:Spring Boot 2.x 默认开启 proxyTargetClass=true,无论目标类是否有接口,都优先使用 CGLIB 代理;若手动设置为 false,有接口用 JDK 代理,无接口用 CGLIB 代理。- 织入时机:Spring AOP 是运行时织入,通过动态代理在程序运行时创建代理对象,而非编译期或类加载期,灵活性高但性能略低于编译期织入(如 AspectJ)。- 核心组件关系:切面(Aspect)包含通知(Advice)和切入点(Pointcut),切入点通过表达式(如 execution(* com.xxx.service..(..)))指定连接点(JoinPoint,即被增强的方法),通知定义增强逻辑的执行时机(前置 @Before、后置 @After、返回后 @AfterReturning、异常后 @AfterThrowing、环绕 @Around)。2. 常见失效场景及原因:- 非 public 方法:Spring AOP 默认仅拦截 public 方法(JDK 代理本身不支持非 public,CGLIB 支持但 Spring 做了限制),若通知目标为非 public 方法,代理不会拦截;- 内部调用:同一类中方法 A 直接调用方法 B(B 被 AOP 增强),调用时未经过代理对象,而是直接调用目标对象方法,通知无法触发;- 目标类未被 Spring 管理:目标类未加 @Component、@Service 等注解,未被 Spring 容器初始化,无法生成代理对象;- 代理方式不匹配:如目标类有接口但设置 proxyTargetClass=false,通知方法标注在类上而非接口上,JDK 代理仅代理接口方法,类中未实现接口的方法无法被增强;- 切入点表达式错误:切入点表达式写错(如包路径、方法名错误),未匹配到目标方法;- 通知顺序错误:多个切面增强同一方法时,通过 @Order 指定顺序,若顺序错误可能导致某切面逻辑未执行(如权限校验切面未先执行);- final 方法:CGLIB 代理无法重写 final 方法,JDK 代理也无法拦截 final 方法,若目标方法为 final,AOP 增强失效。
📌 加分项:
对比 Spring AOP 与 AspectJ 的差异(Spring AOP 是基于动态代理的轻量级实现,仅支持方法级增强;AspectJ 是完整的 AOP 框架,支持字段、构造器、方法级增强,编译期织入,性能更高);内部调用失效的解决方案(通过 AopContext.currentProxy() 获取代理对象,再调用目标方法,如 ((UserService)AopContext.currentProxy()).updateUser());环绕通知的使用技巧(ProceedingJoinPoint.proceed() 执行目标方法,可在前后添加自定义逻辑,甚至修改参数和返回值);结合实际场景,如用 AOP 实现接口请求日志记录(记录请求参数、响应结果、耗时)、接口限流(基于 Redis + AOP 实现令牌桶限流)。
⚠️注意事项:
避免过度使用 AOP 增强过多方法,否则会增加系统复杂度和性能开销;环绕通知必须调用 ProceedingJoinPoint.proceed(),否则目标方法不会执行;切入点表达式需精准匹配,避免误增强不需要的方法(如 Spring 内置的 Bean 方法);使用 CGLIB 代理时,目标类的构造方法会被执行两次(代理类初始化时会创建目标类实例),需避免在构造方法中执行复杂逻辑;内部调用通过 AopContext 解决时,需开启 exposeProxy=true(@EnableAspectJAutoProxy(exposeProxy=true));Spring AOP 不支持对静态方法、private 方法的增强,若需增强需使用 AspectJ。
📚【面试题目8:Spring中的BeanFactory与FactoryBean有什么区别?各自的使用场景是什么?】
回答要点关键字
(1) 核心区别:BeanFactory是Bean容器(管理Bean),FactoryBean是Bean工厂(创建Bean)
(2) BeanFactory:Spring IoC核心、定义Bean生命周期、提供Bean获取接口(getBean())
(3) FactoryBean:接口规范、自定义Bean创建逻辑、getObject()返回Bean实例、单例/原型控制
(4) 使用场景:BeanFactory(Spring内部容器实现)、FactoryBean(复杂Bean创建,如MyBatis SqlSessionFactory)
打开详情
🍺基础回答:
BeanFactory 和 FactoryBean 虽然名字像,但完全是两回事。BeanFactory 是 Spring 的核心 IoC 容器,相当于一个“Bean仓库”,负责管理所有 Bean 的创建、初始化、依赖注入和销毁,我们平时用的 ApplicationContext 就是它的子类,提供了 getBean() 方法获取 Bean。而 FactoryBean 是一个接口,是用来创建复杂 Bean 的“工厂Bean”——如果一个类实现了 FactoryBean 接口,Spring 不会直接把这个类当成 Bean,而是调用它的 getObject() 方法来获取真正的 Bean 实例。比如 MyBatis 整合 Spring 时,SqlSessionFactoryBean 就是 FactoryBean,Spring 通过它的 getObject() 方法创建 SqlSessionFactory 实例,而不是直接把 SqlSessionFactoryBean 作为 Bean。
🎉高级扩展版:
-
核心区别深度解析:| 维度 | BeanFactory | FactoryBean | |---------------------|----------------------------------------------|----------------------------------------------| | 角色 | IoC容器(Bean管理者) | Bean创建工厂(特殊Bean) | | 核心功能 | 管理Bean生命周期(实例化、依赖注入、销毁)、提供Bean获取接口 | 自定义Bean创建逻辑(复杂Bean构建)、返回Bean实例 | | 接口定义 | 定义容器规范(getBean()、containsBean()等) | 定义工厂Bean规范(getObject()、getObjectType()、isSingleton()) | | 使用方式 | Spring内部实现(如DefaultListableBeanFactory)、应用层通过ApplicationContext使用 | 开发者实现接口,自定义Bean创建逻辑,Spring容器调用 | | 实例化对象 | 直接实例化配置的Bean(如@Component、@Bean标注的类) | 实例化 getObject() 方法返回的对象(真正的业务Bean) |
-
详细解析:- BeanFactory: - 是 Spring IoC 容器的顶层接口,所有 Spring 容器(如 ApplicationContext、XmlBeanFactory)都实现了该接口; - 核心方法:getBean(String name)(根据名称获取Bean)、getBean(Class
requiredType)(根据类型获取Bean)、containsBean(String name)(判断Bean是否存在)等; - 生命周期管理:负责Bean的实例化(通过构造器或工厂方法)、依赖注入(DI)、初始化(InitializingBean.afterPropertiesSet()、@PostConstruct)、销毁(DisposableBean.destroy()、@PreDestroy); - 延迟加载:默认情况下,BeanFactory 是延迟加载Bean(获取Bean时才实例化),而 ApplicationContext 是预加载Bean(容器启动时实例化单例Bean)。- FactoryBean: - 接口方法: 1. getObject():返回创建的Bean实例(核心方法,Spring容器通过该方法获取真正的Bean); 2. getObjectType():返回创建的Bean的类型; 3. isSingleton():是否为单例Bean(默认true,返回单例实例;false则每次getBean()返回新实例); - 特殊特性:Spring容器中,实现FactoryBean的类本身会被当成“工厂Bean”,而getObject()返回的对象才是“业务Bean”,通过&符号可获取工厂Bean本身(如getBean("&sqlSessionFactory")获取SqlSessionFactoryBean实例); - 适用场景:复杂Bean的创建(如需要多步构建、依赖第三方组件、动态配置参数),例如: - MyBatis 的 SqlSessionFactoryBean:通过配置数据源、mapper路径等,创建 SqlSessionFactory; - Spring 的 TransactionProxyFactoryBean:创建事务代理Bean; - 自定义FactoryBean:如创建RedisTemplate实例(需配置连接池、序列化方式等复杂参数)。
📌 加分项:
提到 ApplicationContext 与 BeanFactory 的关系(ApplicationContext 继承 BeanFactory,扩展了更多功能:国际化、事件发布、资源加载等);FactoryBean 的单例控制(isSingleton() 返回 false 时,每次 getBean() 都会调用 getObject() 创建新实例,适合原型Bean场景);自定义 FactoryBean 的实现案例(如创建一个 RedisTemplateFactoryBean,通过 @ConfigurationProperties 绑定Redis配置,在 getObject() 中构建 RedisTemplate 并设置序列化方式);Spring 内部的 FactoryBean 实现(如 ProxyFactoryBean 用于AOP代理创建、JndiObjectFactoryBean 用于JNDI资源获取)。
⚠️注意事项:
FactoryBean 的 getObject() 方法不能返回 null,否则会导致 Bean 创建失败;实现 FactoryBean 时,需确保 getObjectType() 返回的类型与 getObject() 返回的实例类型一致,否则可能导致类型转换异常;通过 & 符号获取工厂Bean时,需注意工厂Bean本身是否被Spring管理(需加 @Component 或在配置类中通过 @Bean 注册);BeanFactory 是 Spring 内部容器,开发者无需自定义实现,只需使用 ApplicationContext 即可;FactoryBean 适合创建复杂Bean,简单Bean(如普通POJO)无需使用,直接通过 @Component 或 @Bean 注册更简洁。
📚【面试题目9:Spring事务传播机制有哪些?各自的适用场景是什么?】
回答要点关键字
(1) 核心传播机制:REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED
(2) 核心逻辑:事务是否继承父事务、是否创建新事务、异常回滚影响范围
(3) 适用场景:REQUIRED(默认,普通业务)、REQUIRES_NEW(独立事务,如日志)、NESTED(嵌套事务,如分步提交)
(4) 关键区别:REQUIRES_NEW与NESTED(前者完全独立,后者嵌套回滚)、MANDATORY与REQUIRED(前者强制父事务)
打开详情
🍺基础回答:
Spring 事务传播机制就是定义一个事务方法调用另一个事务方法时,事务该怎么传递(比如是否共用一个事务、是否创建新事务)。常用的有 REQUIRED、REQUIRES_NEW、NESTED 这几个。REQUIRED 是默认的,比如方法 A 有事务,调用方法 B(REQUIRED),B 就加入 A 的事务,一起提交或回滚;如果 A 没事务,B 就自己创建新事务。REQUIRES_NEW 是不管 A 有没有事务,B 都创建新事务,A 和 B 的事务独立,比如 A 事务失败回滚,不会影响 B 已经提交的事务(比如记录操作日志,不管主业务成功与否都要保存日志)。NESTED 是嵌套事务,B 事务嵌套在 A 里面,A 可以单独回滚 B 的事务,但 B 回滚不会影响 A 其他部分(比如下单业务,扣库存失败回滚扣库存操作,但下单流程其他步骤继续)。
🎉高级扩展版:
- 7种事务传播机制详解(基于 Spring 事务规范):- REQUIRED(默认): - 逻辑:若当前存在事务,则加入该事务;若当前无事务,则创建新事务。 - 回滚规则:子方法异常回滚会导致父方法事务回滚,父方法异常回滚也会导致子方法事务回滚(共用一个事务)。 - 适用场景:大多数普通业务场景(如订单创建+库存扣减,需原子操作)。- REQUIRES_NEW: - 逻辑:无论当前是否存在事务,都创建新事务;若当前有事务,则将当前事务暂停,新事务执行完成后恢复原事务。 - 回滚规则:子事务与父事务完全独立,子事务失败回滚不影响父事务,父事务失败回滚不影响子事务(已提交的子事务无法回滚)。 - 适用场景:独立于主事务的操作(如操作日志记录、消息发送,即使主业务失败也要保存)。- SUPPORTS: - 逻辑:若当前存在事务,则加入该事务;若当前无事务,则以非事务方式执行。 - 回滚规则:仅当运行在父事务中时,才会随父事务回滚;非事务方式执行时,无回滚机制。 - 适用场景:可选事务的查询操作(如列表查询,有事务则加入,无则正常执行)。- NOT_SUPPORTED: - 逻辑:以非事务方式执行;若当前存在事务,则暂停当前事务。 - 回滚规则:无事务支持,操作失败不会回滚。 - 适用场景:无需事务的操作(如缓存更新、非核心数据查询,避免事务开销)。- MANDATORY: - 逻辑:必须在存在事务的环境中执行;若当前无事务,则抛出 IllegalTransactionStateException 异常。 - 适用场景:强制要求父方法有事务的子方法(如核心业务的子步骤,必须在主事务中执行)。- NEVER: - 逻辑:必须在无事务的环境中执行;若当前存在事务,则抛出 IllegalTransactionStateException 异常。 - 适用场景:绝对不能在事务中执行的操作(如某些非原子性的批量操作,避免事务过长)。- NESTED: - 逻辑:若当前存在事务,则创建嵌套事务(子事务);若当前无事务,则创建新事务(与 REQUIRED 一致)。 - 回滚规则:子事务回滚仅回滚自身操作,不影响父事务;父事务回滚会导致所有子事务回滚;子事务可以独立提交(需数据库支持保存点 Savepoint)。 - 适用场景:分步提交的业务(如订单创建:先扣库存,再创建订单,库存扣减失败可单独回滚,不影响订单创建的其他准备操作)。2. 关键区别对比:- REQUIRES_NEW vs NESTED: - REQUIRES_NEW 是两个完全独立的事务,有各自的事务边界,子事务提交后父事务回滚无法影响; - NESTED 是嵌套事务,子事务依赖父事务,基于数据库保存点实现,子事务回滚不影响父事务。- REQUIRED vs MANDATORY: - REQUIRED 允许父方法无事务(自身创建新事务); - MANDATORY 强制父方法有事务,否则报错。
📌 加分项:
结合数据库支持说明(NESTED 依赖数据库支持保存点(Savepoint),如 MySQL InnoDB、Oracle,MyISAM 不支持);实际业务案例(如转账业务:转账主方法用 REQUIRED,日志记录用 REQUIRES_NEW,确保转账失败日志仍保留;下单业务:下单主方法用 REQUIRED,扣库存用 NESTED,库存扣减失败回滚,下单流程继续处理其他步骤);事务传播机制的实现原理(Spring 通过 TransactionSynchronizationManager 管理事务上下文,根据传播机制判断是否创建新事务、暂停现有事务);异常传播对事务的影响(如子方法抛出 unchecked 异常,REQUIRED 会导致父事务回滚,REQUIRES_NEW 仅子事务回滚)。
⚠️注意事项:
事务传播机制仅对 public 方法生效(与 @Transactional 注解生效条件一致);NESTED 依赖数据库保存点,若数据库不支持(如 MyISAM),则退化为 REQUIRED 行为;REQUIRES_NEW 会暂停当前事务,若父事务持有数据库锁,子事务执行期间父事务锁未释放,可能导致锁竞争(需控制事务时长);避免过度使用复杂传播机制(如 NESTED、MANDATORY),增加系统复杂度,优先使用 REQUIRED 和 REQUIRES_NEW;事务传播机制需结合实际业务场景选择,避免因回滚规则不符合预期导致数据不一致(如误将核心业务用 REQUIRES_NEW,导致主事务回滚后子业务未回滚)。
📚【面试题目10:Spring Boot的启动流程是什么?核心步骤有哪些?】
回答要点关键字
(1) 核心流程:初始化SpringApplication→准备环境→创建上下文→刷新上下文→自动配置→启动完成
(2) 关键步骤:启动类注解解析、环境变量加载、Bean定义扫描、自动配置类加载、嵌入式服务器启动
(3) 核心组件:SpringApplication、ApplicationContext、AutoConfigurationImportSelector、SpringFactoriesLoader
(4) 扩展点:ApplicationContextInitializer、ApplicationListener、CommandLineRunner、ApplicationRunner
打开详情
🍺基础回答:
Spring Boot 启动流程大概就是“初始化→准备环境→创建容器→加载Bean→启动服务器”这几步。首先执行主类的 main 方法,调用 SpringApplication.run() 开始启动;然后初始化 SpringApplication,解析主类上的 @SpringBootApplication 注解,判断是不是Web应用、加载配置;接着准备环境(加载 application.yml/properties、系统环境变量、命令行参数);之后创建 ApplicationContext 容器(Spring的IoC容器);然后刷新容器,扫描并创建Bean(包括@Component、@Bean标注的类,还有自动配置的Bean);最后启动嵌入式服务器(如Tomcat),发布启动完成事件,应用就能对外提供服务了。中间还会触发一些扩展点,比如 CommandLineRunner 可以在启动后执行自定义逻辑。
🎉高级扩展版:
- 启动流程详细拆解(核心7步):- 步骤1:执行 SpringApplication.run(主类.class, args),初始化 SpringApplication 实例: - 解析主类(通过 @SpringBootApplication 注解),判断应用类型(WebApplicationType:SERVLET、REACTIVE、NONE); - 加载 SpringFactories 中的 ApplicationContextInitializer(上下文初始化器)和 ApplicationListener(应用监听器); - 设置主类(mainApplicationClass)。- 步骤2:准备环境(prepareEnvironment): - 创建 Environment 对象(包含配置属性:系统环境变量、命令行参数、应用配置文件、bootstrap配置); - 触发 EnvironmentPostProcessor 扩展点(如加载额外配置文件、加密配置解密); - 绑定配置到 SpringApplication(如 spring.profiles.active 激活对应的Profile)。- 步骤3:创建并准备 ApplicationContext(createAndRefreshContext): - 根据应用类型创建对应的 ApplicationContext(SERVLET 应用创建 AnnotationConfigServletWebServerApplicationContext,REACTIVE 应用创建 AnnotationConfigReactiveWebServerApplicationContext); - 向上下文注册 Environment、ApplicationListener 等组件; - 应用 ApplicationContextInitializer 初始化上下文(如设置资源加载器、添加Bean定义处理器)。- 步骤4:刷新 ApplicationContext(核心步骤,调用 Spring 容器的 refresh() 方法): - 初始化 BeanFactory(创建 DefaultListableBeanFactory); - 加载 Bean 定义(扫描主类所在包及子包的 @Component、@Configuration、@Bean 等注解,加载自动配置类); - 触发 BeanFactoryPostProcessor(Bean工厂后置处理器,如 ConfigurationClassPostProcessor 解析 @Configuration 类); - 注册 BeanPostProcessor(Bean后置处理器,如AOP代理处理器、依赖注入处理器); - 初始化非懒加载的单例Bean(实例化、依赖注入、初始化); - 触发 ContextRefreshedEvent 事件。- 步骤5:自动配置生效(refresh 过程中): - 通过 AutoConfigurationImportSelector 加载 SpringFactories 中的自动配置类; - 根据 @Conditional 注解条件判断,注册符合条件的自动配置Bean(如 DataSourceAutoConfiguration、WebMvcAutoConfiguration); - 绑定配置属性到自动配置类(通过 @ConfigurationProperties)。- 步骤6:启动嵌入式服务器(onRefresh 方法中): - Web 应用中,刷新上下文时会触发 onRefresh() 方法,调用 ServletWebServerFactory 创建嵌入式服务器(Tomcat/Jetty/Undertow); - 配置服务器参数(端口、线程池、连接超时等); - 启动服务器,绑定端口,监听请求。- 步骤7:启动完成(callRunners): - 触发 ApplicationStartedEvent、ApplicationReadyEvent 事件; - 执行 CommandLineRunner 和 ApplicationRunner 接口的 run() 方法(CommandLineRunner 接收字符串数组参数,ApplicationRunner 接收 ApplicationArguments 对象,支持参数解析); - 应用启动完成,对外提供服务。2. 核心扩展点说明:- ApplicationContextInitializer:上下文初始化前执行,可修改上下文配置(如添加自定义 PropertySource);- ApplicationListener:监听应用启动事件(如 ApplicationStartingEvent、ApplicationReadyEvent),执行自定义逻辑;- CommandLineRunner/ApplicationRunner:应用启动后执行(如初始化缓存、加载字典数据),支持 @Order 指定执行顺序;- EnvironmentPostProcessor:环境准备后执行,可扩展配置(如加载Nacos配置、解密敏感配置)。
📌 加分项:
对比 Spring Boot 2.x 与 1.x 启动流程的差异(2.x 重构了 SpringApplication 初始化逻辑,优化了应用类型判断、扩展点加载机制);启动流程中的异常处理(若启动失败,触发 ApplicationFailedEvent 事件,打印异常堆栈信息);自定义扩展点实现案例(如实现 ApplicationContextInitializer 向环境中添加自定义配置,实现 CommandLineRunner 启动后加载热点数据到Redis);嵌入式服务器启动原理(Spring Boot 自动配置 TomcatServletWebServerFactory,通过 @ConfigurationProperties 绑定 server.tomcat 相关配置,创建 Tomcat 实例并启动);Bean 加载顺序(@Configuration 类优先加载,自动配置类按 @AutoConfigureBefore/After 顺序加载,自定义 Bean 覆盖自动配置 Bean)。
⚠️注意事项:
启动类需放在根包下(如 com.xxx.app),确保 @ComponentScan 能扫描到所有业务Bean;避免在 CommandLineRunner/ApplicationRunner 中执行耗时操作(如长时间数据库查询),否则会延长启动时间;自定义 ApplicationContextInitializer 需通过 SpringFactories 注册(META-INF/spring.factories),否则无法被 SpringApplication 加载;启动失败时,优先查看日志中的异常堆栈(如Bean创建失败、端口被占用、配置错误),重点关注 refresh 阶段的异常;嵌入式服务器端口需确保未被占用,可通过 server.port=0 让系统自动分配端口(适合测试环境)。
📚【面试题目11:Spring Boot生产环境故障排查流程是什么?OOM、频繁Full GC、慢接口、JVM调优如何处理?】
回答要点关键字
(1) 故障排查核心:监控采集→日志分析→问题定位→方案验证→复盘优化
(2) OOM处理:堆Dump抓取→MAT分析→大对象/内存泄漏定位→代码优化
(3) 频繁Full GC:GC日志分析→老年代/元空间占用分析→内存泄漏/参数不合理优化
(4) 慢接口处理:链路追踪→慢SQL分析→索引优化→外部依赖排查
(5) JVM调优:基于监控数据→参数迭代→性能验证→长期监控
打开详情
🍺基础回答:
生产环境故障排查核心是“先定位问题,再找根因,最后优化”。首先得有监控(比如Prometheus+Grafana、SkyWalking)和完整日志,不然没法下手。OOM(内存溢出)的话,先通过jmap抓取堆Dump文件,用MAT工具分析,看哪些对象占用内存多、有没有内存泄漏(比如静态集合没清理、长连接没释放),然后改代码(比如清理无用对象、优化缓存策略)。频繁Full GC一般是老年代满了或者元空间不够,先看GC日志,用jstat监控老年代占用率,要是内存泄漏就按OOM的方法排查,要是参数不合理就调大老年代内存。慢接口的话,用链路追踪工具(比如SkyWalking)找耗时步骤,大概率是慢SQL(看执行计划、加索引)或者外部依赖(比如Redis、第三方接口慢),针对性优化。JVM调优不能盲目,要基于监控数据(比如GC停顿时间、内存占用),先调核心参数(堆大小、新生代/老年代比例、垃圾收集器),改完验证性能,慢慢迭代。
🎉高级扩展版:
- 通用故障排查流程(5步):- 步骤1:监控采集(数据基础): - 系统监控:CPU、内存、磁盘IO、网络IO(工具:top、iostat、netstat); - 应用监控:JVM内存、GC情况、线程状态、接口响应时间(工具:Prometheus+Grafana、SkyWalking、Pinpoint); - 日志采集:应用日志(ERROR/WARN级别)、GC日志、访问日志(工具:ELK栈、Loki); - 链路追踪:接口调用链路、耗时分布(工具:SkyWalking、Zipkin)。- 步骤2:日志分析(初步定位): - 应用日志:查找ERROR日志、堆栈信息(如空指针、数据库异常); - GC日志:分析GC频率、停顿时间、内存回收情况; - 访问日志:筛选慢接口(响应时间>1s)、高频请求; - 链路日志:定位慢接口的耗时环节(如SQL执行500ms、Redis查询300ms)。- 步骤3:问题定位(核心): - 现场保留:避免重启应用(OOM、死锁等故障重启后现场丢失); - 工具辅助:jps(查看进程)、jmap(堆内存)、jstat(GC统计)、jstack(线程栈)、jinfo(JVM参数); - 根因分析:区分是代码问题(如内存泄漏、死循环)、配置问题(如JVM参数、数据库连接池)、环境问题(如资源不足、网络波动)。- 步骤4:方案验证(快速止血+长期优化): - 紧急止血:如重启应用(临时解决)、扩容资源、降级非核心接口; - 长期优化:修改代码、调整配置、优化依赖; - 验证效果:监控指标是否恢复正常(如Full GC频率降低、接口响应时间缩短)。- 步骤5:复盘优化(避免复发): - 记录故障原因、排查过程、解决方案; - 优化监控告警(如OOM时自动抓取堆Dump、慢接口超时告警); - 代码评审(避免同类问题,如规范静态集合使用、SQL评审)。2. 常见故障处理细节:- OOM(内存溢出): - 抓取堆Dump:jmap -dump:format=b,file=heapdump.hprof [PID](或通过-XX:+HeapDumpOnOutOfMemoryError自动生成); - 分析工具:MAT(Memory Analyzer Tool)、JProfiler; - 排查重点:大对象(如byte[]数组、大集合)、内存泄漏(静态Map未清理、ThreadLocal未remove、长生命周期Bean持有短生命周期Bean引用); - 优化方案:清理无用对象、减少大对象创建(如分页查询替代全量查询)、优化缓存过期策略、调大堆内存(-Xms/-Xmx)。- 频繁Full GC: - 监控工具:jstat -gcutil [PID] 1000(每秒输出GC统计)、Grafana监控老年代占用率; - 排查原因: 1. 内存泄漏:老年代内存持续增长,Full GC后回收很少; 2. 堆参数不合理:老年代空间过小,频繁晋升; 3. 大对象直接进入老年代:未设置-XX:PretenureSizeThreshold,大对象跳过新生代直接进入老年代; - 优化方案:解决内存泄漏、调大老年代内存(-Xmx/-Xms设置合理,新生代/老年代比例默认8:2,可通过-XX:NewRatio调整)、启用G1垃圾收集器(适合大堆内存,减少GC停顿)。- 慢接口/慢SQL: - 链路定位:通过SkyWalking查看接口耗时分布(如Controller→Service→DAO各环节耗时); - 慢SQL分析: 1. 查看SQL执行计划(explain SQL语句),是否走索引(key字段不为NULL)、是否全表扫描(type=ALL); 2. 优化方案:添加索引、优化SQL(避免SELECT *、JOIN过多表、子查询优化)、分库分表(数据量大时); - 外部依赖排查:Redis查询慢(检查缓存key设计、是否命中缓存)、第三方接口慢(添加超时时间、异步调用); - 应用层优化:接口异步化(@Async)、缓存热点数据、优化代码逻辑(避免循环调用数据库)。- JVM调优: - 核心参数: 1. 堆内存:-Xms=4G -Xmx=4G(初始堆=最大堆,避免频繁扩容); 2. 新生代/老年代:-XX:NewRatio=2(老年代:新生代=2:1)、-XX:SurvivorRatio=8(Eden:S0:S1=8:1:1); 3. 垃圾收集器:JDK8默认ParallelGC,高并发场景推荐G1(-XX:+UseG1GC -XX:MaxGCPauseMillis=200); 4. 元空间:-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M(避免元空间溢出); - 调优原则: 1. 基于监控数据,不盲目调参; 2. 小步迭代,每次只改1-2个参数,验证效果; 3. 优先优化代码(如解决内存泄漏、慢SQL),再调JVM参数; 4. 目标:GC停顿时间<200ms,Full GC频率<1次/小时,内存使用率稳定在70%左右。
📌 加分项:
提到自动化故障处理(如通过Arthas在线排查故障,无需重启应用;OOM时通过-XX:+HeapDumpOnOutOfMemoryError自动生成Dump文件);慢接口的压测验证(使用JMeter、Gatling压测,复现慢接口场景,验证优化效果);JVM调优的工具链(JConsole、VisualVM、Arthas、Grafana+Prometheus);生产环境监控告警配置(如GC停顿时间>500ms告警、接口响应时间>3s告警、内存使用率>90%告警);结合实际案例,如某应用频繁Full GC,通过jstat发现老年代持续增长,MAT分析堆Dump发现ThreadLocal未remove导致内存泄漏,修改代码后问题解决。
⚠️注意事项:
抓取堆Dump时需注意应用内存大小,避免Dump文件过大(如4G堆内存的Dump文件约4G),影响生产环境;GC日志需提前开启(-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:gc.log),否则故障发生后无法分析;慢SQL优化前需备份数据,避免索引优化导致SQL性能下降;JVM调优需在测试环境验证,避免直接在生产环境修改参数,导致意外故障;故障排查时优先保障业务可用性(如降级非核心接口、扩容),再排查根因,避免故障扩大;长期监控是关键,避免“头痛医头脚痛医脚”,通过监控发现潜在问题(如内存缓慢泄漏)。