SpringBoot 面试题 100 道(对标阿里 P6 级别)
按难度和领域分层。P6级别要求候选人不仅会用框架,更要深入理解源码、具备架构思维和性能调优能力,以下题目覆盖从原理源码到生产实践的全链路。
一、基础与核心概念(1-10题)
-
说说Spring Boot和Spring的本质区别是什么?
Spring Boot本质上是Spring框架的扩展,并非替代品。Spring提供通用的IoC、AOP等基础能力,而Spring Boot是"脚手架",通过"约定优于配置"和自动配置机制,消除大量样板代码与XML配置,让开发者能快速创建可运行的独立应用。
-
Spring Boot的"约定优于配置"如何理解?具体体现在哪些方面?
其核心思想是框架提供合理的默认值,开发者只需规定应用中不符合约定的部分。体现方面:
· 自动配置:根据类路径下的依赖自动配置相应的Bean。
· 默认配置:如默认的静态资源路径、默认的ErrorController等。
· 起步依赖:通过引入Starter即可获得一组预配置好的依赖。
· 内嵌容器:默认内嵌Tomcat,无需额外部署WAR包。
- Spring Boot的核心设计理念有哪些?
主要包含:
· 约定优于配置:核心范式。
· 自动配置:基于条件注解智能推断并加载组件。
· 开箱即用:提供固化的生产级别应用视图。
· 起步依赖:简化依赖管理。
· 内嵌容器:使应用可独立运行。
· 生产就绪:内置Actuator等监控功能。
-
Spring Boot的起步依赖(Starter)机制是如何工作的?
Starter是一组方便的依赖描述符,将具备某功能的相关依赖打包在一起,简化导入过程。它通过Maven/Gradle的传递依赖机制,引入一个Starter即可获得该场景所需的所有依赖。官方Starter遵循spring-boot-starter-*命名格式,并在父POM中统一管理版本,有效避免版本冲突。
-
Spring Boot为什么要使用内嵌式容器?相比外置容器有什么优缺点?
优点:实现"开箱即用"的轻量级部署,简化CI/CD流程,方便微服务独立部署和弹性伸缩。
缺点:基础内存占用较高(120MB以上);生产环境缺少外部容器的高级管理监控功能;每个应用独立包含容器,资源利用率低于共享容器。
-
Spring Boot和Spring MVC是什么关系?
Spring MVC是基于Spring的一个Web层MVC框架。Spring Boot包含并扩展了Spring MVC,通过自动配置简化了Spring MVC应用的搭建。可以将Spring理解为"引擎",Spring MVC是基于引擎的"车身",Spring Boot则是能快速组装整车的"生产线"。
-
Spring Boot支持哪些内嵌容器?如何切换?
支持Tomcat(默认)、Jetty、Undertow,以及Reactor Netty(WebFlux)。切换方式:在pom.xml中排除spring-boot-starter-tomcat依赖,并引入对应Starter,如spring-boot-starter-jetty。
-
Spring Boot 2.x和3.x的核心区别是什么?
· Java版本基线:2.x支持Java 8至17,3.x强制要求Java 17及以上。
· Jakarta EE迁移:2.x使用javax.命名空间,3.x全面迁移至jakarta. 。
· GraalVM原生镜像:3.x对GraalVM原生镜像有更好支持。
-
Spring Boot为什么可以直接通过java -jar运行?
Spring Boot Maven/Gradle插件在打包时会将应用、依赖jar以及内嵌Web容器一起打成一个可执行的Fat JAR(Uber JAR),并在MANIFEST.MF中指定了Main-Class为JarLauncher,Start-Class为应用的main方法所在类。执行java -jar时,JVM启动JarLauncher,其负责加载Fat JAR中的依赖并调用Start-Class的main方法。
-
Spring Boot的Banner可以自定义吗?如何实现?
可以,有以下方式:
· 文本Banner:在src/main/resources下创建banner.txt文件,写入自定义ASCII艺术字。
· 图片Banner:放置banner.gif/banner.jpg等图片,并通过spring.banner.image.*配置属性。
· 编程方式:实现org.springframework.boot.Banner接口,通过SpringApplication.setBanner()设置。
· 关闭Banner:通过spring.main.banner-mode=off配置关闭。
二、@SpringBootApplication与核心注解(11-25题)
以下是第二部分(11-25题)的详细答案:
- @SpringBootApplication由哪三个注解组成?各自的作用是什么?
@SpringBootApplication是一个组合注解,由以下三个核心注解组成:
· @SpringBootConfiguration:标注当前类为配置类,是@Configuration的Spring Boot专用替代品,允许注册额外的Bean或导入其他配置类。
· @EnableAutoConfiguration:开启Spring Boot的自动配置机制。
· @ComponentScan:启用组件扫描,默认扫描当前类所在包及其子包下的所有组件。
此外,@SpringBootApplication还通过@AliasFor提供了exclude、excludeName、scanBasePackages等属性,用于定制@EnableAutoConfiguration和@ComponentScan的行为。
- @EnableAutoConfiguration的底层实现原理是什么?
@EnableAutoConfiguration是Spring Boot自动配置的核心入口,其源码如下:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { ... }
核心实现原理分为两步:
- @AutoConfigurationPackage:将标注该注解的类所在包注册为自动配置的基础包,用于后续扫描。
- @Import(AutoConfigurationImportSelector.class):AutoConfigurationImportSelector是核心加载器,它通过SpringFactoriesLoader从META-INF/spring.factories或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x新方式)中读取所有自动配置类的全限定名。
自动配置类在被加载前,会经过@Conditional系列注解的过滤(如@ConditionalOnClass检查类路径是否存在某个类),只有满足所有条件才会被注册到Spring容器中。自动配置总是在用户自定义的Bean注册之后应用,以确保用户可以覆盖默认配置。
- @ComponentScan的默认扫描规则是什么?如何自定义?
默认规则:扫描标注了@ComponentScan的类所在包及其所有子包,自动发现并注册带有@Component、@Service、@Repository、@Controller等注解的Bean。@SpringBootApplication本身自带了@ComponentScan,所以启动类所在包即为默认扫描根路径。
自定义方式:
· 指定扫描包:@ComponentScan(basePackages = {"com.example.service", "com.example.dao"})
· 指定扫描类:@ComponentScan(basePackageClasses = {UserService.class, OrderService.class})
· 排除某些类:@ComponentScan(excludeFilters = @Filter(type = FilterType.REGEX, pattern = "com.example.demo.Test."))
@SpringBootApplication也提供了scanBasePackages和scanBasePackageClasses属性,是对@ComponentScan的快捷定制。
- @Configuration和@SpringBootConfiguration有什么区别?
· @Configuration:Spring Framework的标准配置类注解,用于标识一个类包含@Bean方法。
· @SpringBootConfiguration:是Spring Boot对@Configuration的封装,本质相同------@SpringBootConfiguration本身被@Configuration标注。区别在于:@SpringBootConfiguration表示这是一个Spring Boot专用的配置类,在集成测试中辅助配置检测。此外,@SpringBootConfiguration默认proxyBeanMethods = true,保证@Bean方法之间的调用走代理、返回单例对象。
推荐在Spring Boot应用的主启动类上使用@SpringBootConfiguration(通过@SpringBootApplication继承),以明确标识这是Spring Boot特有的配置入口。
- @Conditional系列注解有哪些?分别什么场景使用?
@Conditional是Spring 4.0引入的条件装配注解。当所有指定Condition的matches()方法返回true时,被标注的组件才会被注册。
Spring Boot基于@Conditional派生了一系列注解,主要分为六大类:
分类 注解 使用场景
Class Conditions @ConditionalOnClass、@ConditionalOnMissingClass 根据类路径是否存在某个类来决定是否加载配置
Bean Conditions @ConditionalOnBean、@ConditionalOnMissingBean 根据容器中是否存在某个Bean来决定
Property Conditions @ConditionalOnProperty 根据配置文件中的属性值决定
Web Application Conditions @ConditionalOnWebApplication、@ConditionalOnNotWebApplication 根据是否为Web应用决定
Resource Conditions @ConditionalOnResource 根据是否存在指定资源文件决定
SpEL Expression Conditions @ConditionalOnExpression 通过SpEL表达式组合复杂条件
- @ConditionalOnClass和@ConditionalOnMissingBean的区别与使用场景?
· @ConditionalOnClass:检查类路径。只有当指定类在类路径中存在时,被标注的配置才生效。典型场景:自动配置类中检查依赖库是否存在,例如当类路径存在DataSource.class和EntityManager.class时才自动配置JPA。
· @ConditionalOnMissingBean:检查Spring容器。只有当指定类型的Bean在容器中不存在时,被标注的配置才生效。典型场景:提供默认实现,允许用户通过自定义Bean覆盖------用户定义了DataSource则用用户的,否则用默认的。
核心区别:前者基于类路径(编译/运行时是否存在某个类),后者基于IoC容器(是否已有某个类型的Bean)。
使用建议:官方建议在自动配置类上优先使用@ConditionalOnBean和@ConditionalOnMissingBean,因为这些注解在用户定义的Bean注册之后执行,能正确判断用户是否已提供自定义实现。
- @ConfigurationProperties和@Value的区别?各自适用场景?
对比维度 @ConfigurationProperties @Value
所属框架 Spring Boot特有 Spring框架核心
注入方式 批量注入:将配置文件中某一前缀下的所有属性映射到Bean的字段 逐个注入:每个字段单独使用@Value标注
松散绑定 ✅ 支持(如demo.item-price可绑定到itemPrice) ❌ 仅支持标准kebab-case
JSR-303校验 ✅ 支持 ❌ 不支持
复杂类型 ✅ 支持Map、List等 ❌ 通常仅支持简单类型
SpEL表达式 ❌ 不支持 ✅ 支持
配置中心动态刷新 ✅ 支持 ❌ 需配合@RefreshScope
适用场景:
· @ConfigurationProperties:适合结构化配置,如数据库连接、Redis配置等有成组属性的场景,尤其在微服务配置中心下推荐使用。
· @Value:适合单个离散配置值,或需要使用SpEL表达式的场景。
- @EnableConfigurationProperties的作用是什么?
@EnableConfigurationProperties用于显式地启用@ConfigurationProperties注解的类,将其注册为Spring容器中的Bean。
通常有两种使用方式:
- 在配置类上使用:@EnableConfigurationProperties(DataSourceProperties.class),将DataSourceProperties注册为Bean。
- 配合@ConfigurationProperties:在类上直接使用@ConfigurationProperties并配合@Component,也能自动注册,无需@EnableConfigurationProperties。
Spring Boot的自动配置大量使用此注解------例如DataSourceAutoConfiguration通过@EnableConfigurationProperties(DataSourceProperties.class)将配置属性类注入,再通过@ConfigurationProperties将application.yml中的spring.datasource.*属性绑定到该类的字段上。
- @Import注解的三种用法分别是什么?
@Import用于将外部配置类或组件导入当前Spring容器。主要有三种用法:
- 直接导入@Configuration配置类:将分散的配置类聚合到一个主配置类中。例如@Import({DatabaseConfig.class, RedisConfig.class})。
- 通过ImportSelector接口实现动态导入:根据运行时条件动态决定导入哪些类。selectImports()方法返回全类名数组。Spring Boot自动配置的AutoConfigurationImportSelector就是此用法的典型。
- 通过ImportBeanDefinitionRegistrar接口实现编程式注册:在运行时通过BeanDefinitionRegistry手动注册Bean定义,灵活性最高。
另外,@Import也可直接导入普通组件类(如@Component)。与@ComponentScan的隐式扫描不同,@Import是显式精确导入。
- @ImportResource的作用是什么?什么场景下使用?
@ImportResource用于导入传统的Spring XML配置文件,将XML中定义的Bean加载到Spring容器中。
适用场景:
· 遗留系统迁移:老项目使用XML配置Spring Bean,迁移到Spring Boot时逐步替换,通过@ImportResource兼容旧配置。
· 引入第三方库:某些第三方库仍以XML方式提供Spring配置,无法直接使用Java配置。
例如:@ImportResource("classpath:spring-beans.xml")会将spring-beans.xml中定义的Bean全部加载到容器。
- @Profile如何实现多环境配置?
@Profile用于指定某个Bean或配置类在特定环境(Profile)下才生效。Spring Boot通过spring.profiles.active激活对应的Profile。
使用方式:
· 类级别:@Profile("dev")标注在@Configuration或@Component类上,表示仅在dev环境生效。
· 方法级别:标注在@Bean方法上,该Bean仅在指定Profile下创建。
· 组合使用:@Profile({"dev", "test"})表示在dev或test环境下生效;@Profile("!prod")表示非prod环境生效。
激活方式包括:application.yml中配置spring.profiles.active: dev、命令行参数--spring.profiles.active=dev、或通过环境变量。
- @ConditionalOnWebApplication和@ConditionalOnNotWebApplication的使用场景?
· @ConditionalOnWebApplication:当前应用是Web应用时生效。典型场景:注册Web特有的组件,如MVC配置、过滤器、拦截器等。
· @ConditionalOnNotWebApplication:当前应用不是Web应用时生效。典型场景:批处理任务、后台服务等非Web环境下的配置,避免加载Web相关组件造成不必要的资源消耗。
Spring Boot在启动时会通过WebApplicationType推断应用类型(SERVLET、REACTIVE或NONE),这两个注解基于此推断结果进行条件判断。
- @ConditionalOnProperty的用法和参数含义?
@ConditionalOnProperty根据配置文件中的属性值决定配置是否生效。
核心参数:
· name:要检查的属性名(支持数组,多个属性需全部匹配)。
· havingValue:期望的属性值,只有属性值等于该值时条件才成立。
· matchIfMissing:当属性不存在时是否匹配,默认为false。
· prefix:属性前缀,如prefix = "app.cache"配合name = "enabled"即检查app.cache.enabled。
示例:@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")表示只有app.cache.enabled=true时才加载该配置。
- Spring Boot中如何自定义条件注解?
自定义条件注解分为三步:
- 实现Condition接口:创建类实现Condition接口,重写matches()方法。ConditionContext可获取容器信息,AnnotatedTypeMetadata可获取注解元数据。
java
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("my.feature.enabled", "false").equals("true");
}
}
- 组合@Conditional创建自定义注解:
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(MyCondition.class)
public @interface ConditionalOnMyFeature { }
-
使用自定义注解:标注在配置类或Bean方法上即可。
-
@Configuration类为什么默认是CGLIB代理?@Bean方法的调用机制是什么?
@Configuration类默认proxyBeanMethods = true,Spring会通过CGLIB创建该配置类的代理子类。当在一个@Bean方法中调用另一个@Bean方法时,代理会拦截调用并从容器中返回已存在的单例Bean,而不是每次都创建新实例。
java
@Configuration
public class AppConfig {
@Bean
public A a() { return new A(b()); } // b()被代理拦截
@Bean
public B b() { return new B(); }
}
调用a()中的b()时,代理从容器获取B的单例,而非重新创建,保证了Bean的单例性。
如果配置类中没有@Bean方法之间的互相调用,可设置proxyBeanMethods = false,跳过CGLIB代理,提升启动性能。Spring Boot的@SpringBootConfiguration默认开启了此代理。
三、自动配置原理(26-38题)
以下是第三部分(26-38题)的详细答案:
- Spring Boot自动配置的完整流程是什么?
自动配置的完整流程如下:
-
入口触发:应用启动类上的@SpringBootApplication组合注解,其内部的@EnableAutoConfiguration是自动配置的总开关。
-
导入选择器:@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入AutoConfigurationImportSelector。
-
加载配置类名:AutoConfigurationImportSelector的selectImports()方法被调用,通过SpringFactoriesLoader从META-INF/spring.factories(Spring Boot 2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中读取所有自动配置类的全限定名。
-
条件过滤:读取到的自动配置类经过@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)逐一过滤。
-
注册Bean:满足所有条件的自动配置类被注册到Spring容器中,执行其内部的@Bean方法完成Bean的创建。
-
用户覆盖:自动配置总是在用户自定义的Bean注册之后应用,确保用户可以通过自定义Bean覆盖默认配置。
-
META-INF/spring.factories文件的作用是什么?
spring.factories文件是Spring Boot SPI机制的核心配置文件,采用Properties格式(键=值,多个值用逗号分隔),定义了多种扩展点的实现类。对于自动配置,关键配置项是org.springframework.boot.autoconfigure.EnableAutoConfiguration,其值列出了所有需要被加载的自动配置类的全限定名。Spring Boot启动时会扫描所有jar包中的该文件,加载其中定义的自动配置类。
- Spring Boot 3.x中自动配置的加载机制有什么变化?
Spring Boot 3.x(从2.7开始过渡)移除了spring.factories中注册自动配置的支持,改用新文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。新文件每行一个自动配置类的全限定名,格式更简洁。
- SpringFactoriesLoader是如何工作的?
SpringFactoriesLoader是Spring框架的内部工具类,负责加载META-INF/spring.factories文件。工作流程:
-
获取ClassPath下所有META-INF/spring.factories文件的URL。
-
遍历URL,将每个文件解析为Properties对象。
-
根据传入的工厂类型(如EnableAutoConfiguration.class)作为Key,从Properties中获取对应的值列表。
-
去重、排序后返回类名列表。
-
自动配置类是如何被条件注解过滤的?请结合源码说明。
条件注解的核心是@Conditional,其Condition接口的matches()方法返回true时组件才被注册。Spring Boot的OnClassCondition、OnBeanCondition、OnPropertyCondition等子类分别实现不同的匹配逻辑。AutoConfigurationImportSelector加载所有候选配置类后,会逐个应用这些条件进行过滤,不满足条件的配置类被排除,不会注册到容器。最终生效的配置类会输出在自动配置报告中。
- 如何排除某个自动配置类?有哪几种方式?
有三种方式:
· 注解方式:在@SpringBootApplication或@EnableAutoConfiguration上使用exclude(指定Class对象)或excludeName(指定全限定名字符串)属性。
· 配置文件方式:在application.properties或application.yml中配置spring.autoconfigure.exclude属性。
· 注解和配置文件可同时使用,效果叠加。
- 如何查看当前应用生效了哪些自动配置?未生效的有哪些?
主要有两种方式:
· 开启Debug模式:在application.properties中添加debug=true,或在启动时加--debug参数,控制台会打印详细的自动配置报告,分为Positive matches(生效)和Negative matches(未生效及原因)。
· 使用Actuator端点:引入spring-boot-starter-actuator后访问/actuator/conditions端点(旧版为/autoconfig),可查看相同的报告信息。
- 为什么说自动配置是"开箱即用"的?
"开箱即用"指开发者引入依赖后无需任何额外配置即可使用该功能。因为Spring Boot启动时会根据类路径中的依赖自动推断并配置所需的Bean,且默认配置就是生产可用的。开发者只需关注业务逻辑,框架层面的基础设施配置由Spring Boot自动完成。
-
如何编写一个自定义Starter?需要哪些步骤?
-
创建两个模块:xxx-spring-boot-starter(空模块,仅依赖自动配置模块)和xxx-spring-boot-autoconfigure(核心实现)。
-
编写属性类:使用@ConfigurationProperties定义可配置属性。
-
编写服务类:实现核心业务逻辑。
-
编写自动配置类:使用@Configuration和条件注解,通过@EnableConfigurationProperties启用属性类,定义@Bean方法创建服务类实例。
-
注册自动配置类:在META-INF/spring.factories(2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x)中注册自动配置类的全限定名。
-
添加依赖:Starter模块依赖autoconfigure模块。
-
自定义Starter中spring.factories如何配置?
在src/main/resources/META-INF/spring.factories文件中配置:
properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.xxx.XxxAutoConfiguration
Spring Boot 3.x改用AutoConfiguration.imports文件,每行一个自动配置类全限定名。
- 自定义Starter的自动配置类应该如何设计才能保证灵活性?
· 使用条件注解:通过@ConditionalOnClass、@ConditionalOnMissingBean等让配置按需生效。
· 提供配置属性:通过@ConfigurationProperties暴露可配置参数。
· 允许覆盖:用@ConditionalOnMissingBean标注@Bean方法,让用户可通过自定义Bean覆盖默认实现。
· 合理的默认值:让Starter在无配置时也能工作。
· 模块化设计:将核心逻辑与自动配置分离。
- Spring Boot的自动配置和Spring Cloud的自动配置有什么关系?
Spring Cloud的自动配置建立在Spring Boot自动配置之上。Spring Cloud提供自己的Starter和自动配置类(如服务注册发现、配置中心、熔断等),通过spring.factories或AutoConfiguration.imports注册,同样使用@ConditionalOnClass、@ConditionalOnProperty等条件注解。加载时机上,Spring Boot完成基础自动配置后,Spring Cloud的自动配置在此基础上增强微服务相关能力。
- 自动配置中@EnableConfigurationProperties和@ConfigurationProperties的配合机制?
@ConfigurationProperties标注在类上,声明该类用于绑定配置属性。@EnableConfigurationProperties在自动配置类上显式注册这些属性类为Spring Bean。配合流程:
- 自动配置类使用@EnableConfigurationProperties(XxxProperties.class)。
- Spring Boot启动时将配置文件(如application.yml)中对应前缀的属性绑定到XxxProperties的字段上。
- 该XxxProperties Bean被注入到自动配置类中,用于创建其他Bean。
如果属性类本身已标注@Component或@ConfigurationProperties并配合@ComponentScan,则无需@EnableConfigurationProperties也可自动注册。
四、启动流程与源码深度(39-52题)
- Spring Boot的完整启动流程是怎样的?分几个阶段?
完整启动流程分为两大阶段:SpringApplication构造阶段和run()执行阶段。
· 构造阶段:设置主源类、推断Web应用类型、加载所有ApplicationContextInitializer和ApplicationListener。
· run()执行阶段(核心):
-
启动StopWatch计时器。
-
配置Headless属性。
-
获取并启动SpringApplicationRunListeners(发布starting事件)。
-
准备环境prepareEnvironment()(加载配置文件,发布environmentPrepared事件)。
-
打印Banner。
-
创建应用上下文createApplicationContext()(根据Web类型创建)。
-
准备上下文prepareContext()(设置环境、应用初始化器、加载主源)。
-
刷新上下文refreshContext()(最核心,启动内嵌容器、实例化单例Bean)。
-
刷新后处理afterRefresh()(执行Runner)。
-
发布started和ready事件,返回ConfigurableApplicationContext。
-
SpringApplication.run()方法内部做了哪些事情?
run()是启动的总入口,其核心逻辑如下(结合源码):
-
获取SpringApplicationRunListeners并调用starting()。
-
封装命令行参数为ApplicationArguments。
-
准备环境prepareEnvironment(),并调用监听器的environmentPrepared()。
-
打印Banner(printBanner())。
-
创建ApplicationContext(根据webApplicationType决定创建AnnotationConfigServletWebServerApplicationContext等)。
-
调用prepareContext(),关联上下文、环境、应用参数,并执行所有ApplicationContextInitializer的initialize(),随后加载主配置类(load())。
-
调用refreshContext(),实际调用AbstractApplicationContext.refresh()。
-
调用afterRefresh(),执行ApplicationRunner和CommandLineRunner。
-
调用listeners.started(context),发布ApplicationStartedEvent。
-
调用listeners.running(context),发布ApplicationReadyEvent。
-
异常时调用handleRunFailure(),发布ApplicationFailedEvent。
-
SpringApplication的构造过程做了什么?
SpringApplication的构造过程(new SpringApplication(primarySources))主要做了:
-
存储主源类(primarySources,通常是@SpringBootApplication标注的启动类)。
-
调用deduceWebApplicationType():通过类路径检测DispatcherHandler(响应式)、DispatcherServlet和ServletContainer(Servlet),推断应用类型(REACTIVE/SERVLET/NONE)。
-
调用setInitializers():通过SpringFactoriesLoader从spring.factories加载所有ApplicationContextInitializer实现类并实例化。
-
调用setListeners():通过SpringFactoriesLoader加载所有ApplicationListener实现类并实例化。
-
调用deduceMainApplicationClass():通过堆栈跟踪找到实际含有main方法的入口类。
-
如何推断Web应用类型(SERVLET/REACTIVE/NONE)?
通过WebApplicationType.deduceFromClasspath()实现。推断逻辑:
· 如果类路径存在org.springframework.web.reactive.DispatcherHandler(响应式处理器),且不存在org.springframework.web.servlet.DispatcherServlet和org.springframework.web.servlet.config.annotation.EnableWebMvc注解依赖,并且容器不是Servlet容器(javax.servlet.Servlet或jakarta.servlet.Servlet不存在),则推断为REACTIVE。
· 否则,如果类路径存在jakarta.servlet.Servlet(3.x)或javax.servlet.Servlet(2.x)以及org.springframework.web.context.ConfigurableWebApplicationContext,则推断为SERVLET。
· 否则为NONE(普通Java应用)。
注意:响应式优先检查,所以若同时引入Spring Web和WebFlux依赖,需配置spring.main.web-application-type强制指定,否则默认推测为REACTIVE可能导致冲突。
- ApplicationContextInitializer和ApplicationListener是什么时候加载的?
加载时机:在SpringApplication构造阶段,通过SpringFactoriesLoader加载(并非在run()时)。Spring Boot SPI机制从所有jar包的META-INF/spring.factories中读取org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener对应的实现类并实例化。
执行时机:
· ApplicationContextInitializer在prepareContext()阶段执行(调用initialize()方法)。
· ApplicationListener在构造时已加载,但真正的回调监听是在run()各阶段通过SpringApplicationRunListeners发布事件触发的。部分监听器(如ConfigFileApplicationListener)在environmentPrepared阶段就会被触发去加载配置文件。
- Spring Boot中的SpringApplicationRunListener有什么作用?
SpringApplicationRunListener是Spring Boot启动流程的核心监听器接口,用于在SpringApplication的run()方法执行过程中,在各个关键节点接收回调(starting、environmentPrepared、contextPrepared、contextLoaded、started、running、failed)。它只有一个实现类EventPublishingRunListener,作用是将这些生命周期事件桥接为Spring标准事件(ApplicationEvent),通过内部的ApplicationEventMulticaster(事件多播器)广播给所有注册的ApplicationListener。
P6追问点:为什么Spring Boot有SpringApplicationRunListener还要用ApplicationListener?因为前者是专为启动流程设计的内部SPI,而后者是Spring框架通用的扩展机制,为了解耦和复用。
- prepareContext()方法做了什么?
prepareContext()在上下文创建之后、刷新之前执行,主要任务包括:
-
将Environment对象设置到ApplicationContext中。
-
处理BeanNameGenerator和ResourceLoader。
-
应用所有ApplicationContextInitializer的initialize()方法(对上下文进行定制化增强)。
-
将ApplicationArguments(命令行参数封装)注册为单例Bean。
-
加载所有主源类(primarySources)到容器中(通过BeanDefinitionLoader)。
-
发布ApplicationPreparedEvent事件。
-
如果配置了spring.main.lazy-initialization=true,则注册LazyInitializationBeanFactoryPostProcessor以实现全局懒加载。
-
refreshContext()是整个启动流程中最核心的方法,它内部执行了哪些步骤?
refreshContext()实际调用的是Spring Framework的AbstractApplicationContext.refresh()(模板方法),共12个关键步骤:
- prepareRefresh():设置上下文状态,初始化属性源占位符,验证必需属性。
- obtainFreshBeanFactory():创建或刷新BeanFactory,加载Bean定义(即扫描@Component、@Bean等)。
- prepareBeanFactory():配置BeanFactory(设置ClassLoader、SpEL解析器、注册ApplicationContextAware等内置处理器)。
- postProcessBeanFactory():供子类扩展,允许在BeanFactory后置处理前添加特殊Bean定义。
- invokeBeanFactoryPostProcessors():关键! 执行所有BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,包括ConfigurationClassPostProcessor(解析@Configuration类)、PropertySourcesPlaceholderConfigurer(解析${...}占位符)。
- registerBeanPostProcessors():注册所有BeanPostProcessor,等待后续Bean实例化时拦截。
- initMessageSource():初始化国际化消息源。
- initApplicationEventMulticaster():初始化事件多播器。
- onRefresh():嵌入式容器启动的入口! 子类重写此方法启动内嵌Web服务器。
- registerListeners():注册所有ApplicationListener并广播早期事件。
- finishBeanFactoryInitialization():核心中的核心! 实例化所有非懒加载的单例Bean(包括业务Bean、数据源等)。
- finishRefresh():清理资源,发布ContextRefreshedEvent,启动生命周期处理器(Lifecycle)。
P6难点:必须能说清第5步和第11步的区别(前者处理Bean定义,后者创建Bean实例)。
- 内嵌Tomcat是在什么时候启动的?
内嵌Tomcat的启动发生在refreshContext()的onRefresh()方法中。具体来说:
· 对于Servlet环境,ServletWebServerApplicationContext重写了onRefresh(),内部调用createWebServer()。
· createWebServer()通过WebServerFactory(如TomcatServletWebServerFactory)创建WebServer实例(Tomcat),并调用getWebServer()方法初始化Tomcat的Connector、Engine、Host等组件。
· 注意:此时Tomcat只是初始化和绑定端口,真正启动(start())是在finishRefresh()阶段,由LifecycleProcessor的onRefresh()触发WebServer.start()。
P6追问:为什么不在onRefresh()直接启动?因为finishBeanFactoryInitialization()可能会耗时较长,延迟启动可以避免Tomcat过早接受请求导致处理未初始化的Bean。
- onRefresh()方法在启动流程中的作用是什么?
onRefresh()是Spring AbstractApplicationContext中定义的模板方法,供子类扩展。在Spring Boot中:
· Servlet环境:ServletWebServerApplicationContext.onRefresh() 创建嵌入式Web服务器(createWebServer()),初始化Tomcat/Jetty/Undertow的容器引擎和Connector,但此时不调用start()。
· Reactive环境:ReactiveWebServerApplicationContext.onRefresh() 类似,创建Netty Web服务器。
· 普通环境:空实现。
它是连接Spring容器生命周期与内嵌容器的桥梁,使得Web服务器的创建成为容器刷新过程的一部分。
- finishRefresh()方法做了什么?
finishRefresh()是refresh()模板方法的最后一步,主要职责:
- 清理上下文资源(如通过ResourceCaches)。
- 初始化LifecycleProcessor(生命周期处理器)并调用onRefresh(),触发所有实现了Lifecycle接口组件的启动,包括内嵌Web服务器的真正启动(webServer.start())。
- 发布ContextRefreshedEvent(ApplicationContext完全刷新完成事件)。
- 将ApplicationContext暴露给JMX(如果启用了MBean导出)。
与onRefresh()区别:onRefresh()负责创建容器实例,finishRefresh()负责启动容器(包括WebServer的start())并发布事件。
- ApplicationRunner和CommandLineRunner的区别?执行时机是什么?
执行时机:两者均在SpringApplication.run()的refreshContext()之后,在afterRefresh()方法中执行。
区别:
· CommandLineRunner:接收原始的String... args参数数组,需要自行解析选项参数。
· ApplicationRunner:接收封装好的ApplicationArguments对象,提供getOptionNames()、getOptionValues()等方法,方便获取--key=value格式的命名参数。
排序机制:都支持@Order注解或实现Ordered接口,值越小越先执行。若多个Runner相互独立,推荐用@Order指定顺序。
- Spring Boot启动过程中如何实现事件监听?有哪些关键事件?
事件监听通过观察者模式实现,核心组件是ApplicationEventMulticaster。启动流程通过SpringApplicationRunListeners在不同阶段发布事件,桥接到Spring标准事件体系。
关键事件(按时间顺序):
- ApplicationStartingEvent:应用开始启动,最早触发(Runner运行前)。
- ApplicationEnvironmentPreparedEvent:环境(Environment)准备完毕,配置文件尚未加载。
- ApplicationContextInitializedEvent:上下文创建并初始化,但Bean定义尚未加载。
- ApplicationPreparedEvent:Bean定义加载完毕,准备刷新上下文。
- ApplicationStartedEvent:上下文刷新完成,但Runner尚未执行。
- ApplicationReadyEvent:Runner执行完毕,应用完全就绪,可对外服务。
- ApplicationFailedEvent:启动过程中抛出异常时触发。
P6追问:ApplicationFailedEvent和ApplicationReadyEvent互斥,只触发其中一个。
- 如果让你手写一个简化的Spring Boot启动过程,核心逻辑是什么?
手写简化版核心逻辑(伪代码思路):
- 推断Web类型:扫描类路径判断是否有DispatcherServlet。
- 创建Spring容器:new AnnotationConfigServletWebServerApplicationContext()(或AnnotationConfigApplicationContext)。
- 扫描配置类:将主启动类(含@Configuration)注册为配置源。
- 手动触发自动配置:通过模拟AutoConfigurationImportSelector从META-INF/spring.factories加载自动配置类,并手动注册到BeanDefinitionRegistry。
- 调用context.refresh():
· 内部执行invokeBeanFactoryPostProcessors处理配置类。
· 在onRefresh()中启动内嵌Tomcat(new Tomcat().start())。
· 执行finishBeanFactoryInitialization()实例化所有单例Bean。 - 执行Runner:从容器中取出ApplicationRunner/CommandLineRunner实例并执行。
- 发布Ready事件:标记应用启动成功,阻塞主线程(如Thread.currentThread().join())保持服务运行。
P6深度:面试官若追问"如何阻塞",回答"一般通过CountDownLatch或直接让主线程等待,Spring Boot实际是启动一个非守护线程(如Tomcat的Acceptor线程)后,主线程通过JVM的ShutdownHook保持存活,或调用ApplicationContext的close()关闭"。
五、配置文件与外部化配置(53-62题)
- Spring Boot支持哪些配置文件格式?有什么区别?
Spring Boot默认支持 .properties 和 .yml(YAML)两种主流格式,此外在Spring Boot 2.4+后可通过spring.config.import支持.yaml、.xml以及外部Git/HTTP等扩展格式。
核心区别:
· 语法结构:.properties为扁平的键值对(如spring.datasource.url=xxx),.yml为树形层级结构(如spring: datasource: url: xxx),更适合表达复杂嵌套关系。
· 类型绑定:.yml天然支持Map、List等复杂类型,而.properties需要下标或0方式(如list0=a)。
· 内容合并:同一个.yml文件内可通过---分割多个文档块(多Profile配置),而.properties需拆分为application-{profile}.properties。
· 加载顺序:两者在同一位置(如类路径根目录)时,.properties的优先级高于.yml(由PropertiesPropertySourceLoader先于YamlPropertySourceLoader加载决定)。
P6补充:两者的配置最终都会被加载为PropertySource对象,只是Loader实现不同,性能上无显著差异,推荐YAML用于复杂配置,Properties用于简单启动参数。
- application.properties和application.yml的加载优先级?
加载优先级由文件存储位置决定,而非文件格式。位置优先级(从高到低)为:
- 命令行参数(最高)
- 当前目录下的/config子目录(file:./config/)
- 当前目录(file:./)
- 类路径下的/config包(classpath:/config/)
- 类路径根目录(classpath:/)------最低(即默认位置)。
在同一位置下若同时存在application.properties和application.yml,.properties优先(因为PropertiesPropertySourceLoader在YamlPropertySourceLoader之前执行)。
- Spring Boot外部化配置的加载顺序和优先级规则是什么?
Spring Boot外部化配置遵循"后者覆盖前者"原则,通过PropertySource的有序列表(List)实现,靠前的优先级更高。完整顺序(从高到低)为:
- Devtools全局配置(~/.spring-boot-devtools.properties,仅DevTools激活时)。
- @TestPropertySource(单元测试中的注解)。
- 测试properties属性(如@SpringBootTest(properties = {"key=value"}))。
- 命令行参数(--server.port=8081)。
- SPRING_APPLICATION_JSON(环境变量或系统属性中的JSON内联配置)。
- ServletConfig初始化参数。
- ServletContext初始化参数。
- JNDI属性(java:comp/env)。
- Java系统属性(System.getProperties(),即-Dkey=value)。
- 操作系统环境变量(SPRING_DATASOURCE_URL等)。
- random.*属性(RandomValuePropertySource)。
- Jar包外部的application-{profile}.properties/yml。
- Jar包内部的application-{profile}.properties/yml。
- Jar包外部的application.properties/yml。
- Jar包内部的application.properties/yml(最低)。
- @PropertySource(通过注解加载的配置)。
- 默认属性(SpringApplication.setDefaultProperties()设置的)。
P6追问:优先级核心逻辑在ConfigDataEnvironment中实现,通过ConfigDataLocation解析器将所有外部源打平为PropertySource列表,然后按添加顺序逆序(后加的先取)进行组合。
- 如何通过命令行参数覆盖配置文件中的属性?
通过--前缀传递参数,例如java -jar app.jar --server.port=8081 --app.name=demo。Spring Boot将--后的key=value解析为SimpleCommandLinePropertySource,该源位于PropertySource列表的最前列(仅次Devtools),因此可以覆盖所有配置文件中的同名属性。
注意:如果参数包含特殊字符(如点号),需用引号包裹,例如--"spring.datasource.password=123"。如果需要传递JSON结构,建议用环境变量SPRING_APPLICATION_JSON替代。
- spring.config.location和spring.config.additional-location的区别?
两者都用于指定配置文件位置,但行为不同:
· spring.config.location:完全替换默认搜索路径(classpath:/,classpath:/config/,file:/,file:./config/),仅加载指定位置的文件。例如--spring.config.location=file:/opt/conf/,Spring Boot只去/opt/conf/目录找application文件。
· spring.config.additional-location:追加到默认搜索路径之前(即优先级高于默认路径),而不覆盖默认路径。例如--spring.config.additional-location=file:/opt/conf/,则搜索顺序变为:/opt/conf → classpath:/ → classpath:/config/ → file:/ → file:./config/。
重要:additional-location中指定的位置优先级高于默认位置,因此可以用于"补丁式"覆盖特定环境的配置,而不丢失默认配置。
- @Value注解支持哪些类型的注入?SpEL表达式如何用?
@Value支持如下类型注入:
· 基本类型:int、long、boolean等。
· 字符串及占位符:@Value(" a p p . n a m e " ) 。 ⋅ 数组 / 集合: @ V a l u e ( " {app.name}")。 · 数组/集合:@Value(" app.name")。⋅数组/集合:@Value("{list:1,2,3}")(需配合SpEL解析为List)。
· java.net.URL、Locale、Charset等常见类型(Spring自动转换)。
· SpEL表达式:通过#{...}语法。
SpEL表达式常用场景:
· 系统属性:@Value("#{systemProperties'user.region'}")
· 调用Bean方法:@Value("#{myService.getConfig()}")
· 运算表达式:@Value("#{2 * T(Math).PI * 10}")
· 三元运算符:@Value("#{server.port > 8080 ? 'high' : 'low'}")
· 集合操作:@Value("#{${my.list}.split(',')}")
P6陷阱:@Value不支持@ConfigurationProperties的松散绑定(如demo.item-price不能自动映射到itemPrice字段),也不能用于复杂嵌套对象的批量绑定,且不支持JSR-303校验。
- @ConfigurationProperties实现类型安全配置绑定的原理?
核心原理基于Spring Boot的 Binder机制(2.0引入,替代早期的RelaxedDataBinder)。绑定流程如下:
- ConfigurationPropertiesBindingPostProcessor(BeanPostProcessor实现)在Bean实例化后、初始化前拦截所有标注@ConfigurationProperties的Bean。
- 通过Binder将当前Environment中的所有PropertySource,按照注解的prefix前缀过滤,匹配的键值对通过松绑定(Relaxed Binding)规则映射到Bean的字段上。
· 松绑定规则:demo.item-price → demo.itemPrice、demo_item_price、DEMO_ITEM_PRICE 均能绑定到itemPrice字段。 - 若字段标注了@Validated(JSR-303注解),则触发校验(如@NotNull、@Min等),校验失败抛出BindValidationException。
- 通过setter或字段直接反射赋值完成绑定。
P6源码点:Binder的核心方法是bind(ConfigurationPropertyName name, Bindable target),ConfigurationPropertyName负责解析前缀,Bindable封装目标类型,通过DataObjectBinder(支持构造器绑定)和JavaBeanBinder(支持setter)两种策略实现绑定。Spring Boot 2.2+推荐使用构造器绑定(配合@ConstructorBinding)以实现不可变配置。
- 配置文件中${...}占位符是如何解析的?
${...}占位符解析由 PropertySourcesPlaceholderConfigurer(BeanFactoryPostProcessor)完成。它实现了EnvironmentAware和PriorityOrdered,在invokeBeanFactoryPostProcessors阶段被触发。
解析过程:
- 遍历当前Environment中的所有PropertySource,查找与${key}匹配的值。
- 支持默认值语法${key:defaultValue},若key不存在则使用defaultValue。
- 支持递归解析(如 a p p . n a m e 的值又包含 {app.name}的值又包含 app.name的值又包含{app.version},会继续解析)。
- 在YAML中支持${...}跨文件引用,因为所有配置文件最终合并为一个PropertySource集合。
P6注意:@Value("${...}")中的占位符由DefaultPlaceholderResolver在EmbeddedValueResolver中处理,本质与PropertySourcesPlaceholderConfigurer逻辑一致。若出现循环引用占位符(A引用B,B引用A),会抛出PlaceholderResolutionException。
- 如何实现配置的热更新?
纯Spring Boot(不含Spring Cloud):默认不支持自动热更新(配置在启动时一次性加载并绑定)。变通方案:
· 通过@RefreshScope(需引入Spring Cloud Context)标注Bean,配合/actuator/refresh端点(POST请求),触发Environment变更并重建该Bean。
· 自定义实现:监听EnvironmentChangeEvent,在配置变更时重新绑定@ConfigurationProperties Bean(但需自行实现变更检测和刷新逻辑)。
Spring Cloud生态(标准做法):
· 引入spring-cloud-starter-config,使用Config Server作为配置中心。
· 在@ConfigurationProperties Bean上标注@RefreshScope。
· 调用/actuator/refresh端点,Spring会:
- 重新拉取远程配置。
- 更新Environment中的PropertySource。
- 销毁并重建所有@RefreshScope标注的Bean。
· 也可通过消息总线(Spring Cloud Bus)自动广播刷新事件。
P6生产建议:配置热更新与容器优雅停机结合------先更新配置再重启实例,或使用K8s ConfigMap挂载卷,配合spring.config.import=optional:file:/config/和spring.cloud.kubernetes.reload.enabled=true实现零停机配置刷新。
- bootstrap.yml和application.yml的区别?什么场景使用bootstrap.yml?
区别:
· 加载顺序:bootstrap.yml由父级ApplicationContext(Bootstrap Context)加载,在application.yml之前加载,优先级更高。
· 用途:bootstrap.yml用于引导阶段的配置,通常包含Spring Cloud Config Server的URI、加密/解密密钥、服务注册发现等基础设施配置。而application.yml用于应用业务层配置。
· 生命周期:Bootstrap Context在应用主上下文启动前创建,先解析bootstrap.yml再初始化主上下文。
使用场景:
· Spring Cloud Config:配置spring.cloud.config.uri来指定Config Server地址,以便在启动时拉取远程配置。
· 加密/解密:配置encrypt.key用于敏感属性加解密。
· 服务注册:配置spring.cloud.nacos.discovery.server-addr等,让应用启动后注册到注册中心。
重要变化(Spring Boot 2.4+):官方不再推荐使用bootstrap.yml(默认禁用),推荐改用spring.config.import导入外部配置。例如application.yml中配置spring.config.import: "optional:configserver:http://localhost:8888"。如需保留bootstrap.yml,需显式添加依赖org.springframework.cloud:spring-cloud-starter-bootstrap或在配置中启用spring.cloud.bootstrap.enabled=true。
P6要点总结:外部化配置是生产环境治理的核心能力。P6级需掌握:
- 优先级心智:命令行 > 环境变量 > 系统属性 > 文件位置(外>内)。
- 源码深度:能说出ConfigDataEnvironment的process()方法如何合并PropertySource,以及Binder的松散绑定与构造器绑定原理。
- 生产方案:配置热更新不是"启动时刷新",而是通过@RefreshScope + Actuator + 配置中心(如Apollo/Nacos)的组合方案,并能指出纯Spring Boot无法实现自动热更新的根本原因(配置绑定在finishBeanFactoryInitialization()阶段已完成,除非重建Bean)。
六、Web开发与嵌入式容器(63-72题)
- Spring Boot中DispatcherServlet的自动配置是如何实现的?
DispatcherServlet的自动配置由 DispatcherServletAutoConfiguration 类实现,其核心逻辑如下:
- 默认注册:通过@ConditionalOnClass(DispatcherServlet.class)确保类路径存在Spring MVC时生效。
- 默认配置属性:通过@EnableConfigurationProperties(WebMvcProperties.class)将spring.mvc.*属性绑定到WebMvcProperties。
- 注册DispatcherServlet Bean:在@Configuration类中定义DispatcherServlet的Bean,默认名称dispatcherServlet。
- 注册DispatcherServletRegistration Bean:将DispatcherServlet包装为ServletRegistrationBean,用于注册到内嵌Servlet容器,并配置servlet-mapping(默认为/,可通过spring.mvc.servlet.path修改)。
- 条件覆盖:使用@ConditionalOnMissingBean,允许用户自定义DispatcherServlet或DispatcherServletRegistration覆盖默认配置。
源码关键点:DispatcherServletAutoConfiguration内部有两个配置类:
· DefaultDispatcherServletConfiguration:定义DispatcherServlet Bean。
· DispatcherServletRegistrationConfiguration:定义DispatcherServletRegistrationBean。
若用户提供了自定义的DispatcherServlet,则自动配置会跳过默认注册(@ConditionalOnMissingBean生效)。
- Spring Boot如何处理静态资源?默认的静态资源路径有哪些?
Spring Boot通过 WebMvcAutoConfiguration 自动配置了静态资源处理。其核心是ResourceHttpRequestHandler,默认从以下路径查找静态资源(按顺序):
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
- /(ServletContext根目录,即webapp/,仅打包为WAR时有效)
处理逻辑:当请求路径匹配到静态资源(如/css/style.css)时,按上述顺序查找,找到即返回;若所有路径均未找到,则转交给DispatcherServlet处理业务请求。
默认的静态资源映射路径为/**(优先级低于DispatcherServlet的/映射),但WebMvcAutoConfiguration会配置/webjars/**映射到classpath:/META-INF/resources/webjars/,用于支持WebJars。
- 如何自定义Spring Boot的静态资源路径?
有三种方式:
方式一:配置文件自定义(推荐)
在application.properties或application.yml中配置:
properties
spring.web.resources.static-locations=classpath:/my-static/,file:/opt/static/
spring.web.resources.cache.period=3600 # 可选:缓存时间
该配置会覆盖默认的4个路径,只使用自定义路径。
方式二:实现WebMvcConfigurer接口
java
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/my-assets/", "file:/external/")
.setCachePeriod(3600);
}
}
此方式可自定义请求路径映射,而非仅修改默认/**。
方式三:继承WebMvcConfigurationSupport(不推荐)
会完全接管Spring MVC配置,导致自动配置失效,需手动配置所有内容。
P6注意:spring.web.resources.static-locations是Spring Boot 2.x+的统一前缀(原spring.resources.static-locations已废弃)。
- Spring Boot中如何配置拦截器?
配置拦截器需要实现HandlerInterceptor接口,并通过WebMvcConfigurer.addInterceptors()注册。步骤如下:
- 自定义拦截器类:
java
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 前置逻辑
return true;
}
}
- 注册拦截器:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/login"); // 排除路径
}
}
P6注意:addPathPatterns和excludePathPatterns支持Ant风格路径表达式。多个拦截器按添加顺序执行。拦截器位于DispatcherServlet内部,仅拦截由DispatcherServlet处理的请求(即Web层),不拦截静态资源(除非特意配置)。
- Spring Boot如何实现CORS跨域配置?
Spring Boot提供两种CORS配置方式:
方式一:全局配置(推荐)
通过WebMvcConfigurer.addCorsMappings():
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET", "POST", "PUT")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
方式二:注解方式(细粒度)
在@RequestMapping或@RestController上使用@CrossOrigin:
java
@CrossOrigin(origins = "https://example.com")
@RestController
public class MyController { ... }
方式三:CorsFilter(基于Spring Security时常用)
java
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 配置...
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
执行顺序:CORS处理在Spring MVC的HandlerMapping中完成(通过CorsInterceptor或CorsProcessor),早于拦截器。若使用Spring Security,需确保CorsFilter在Security过滤器链之前执行(通过http.cors()启用)。
- @RestController和@Controller的区别?
· @Controller:标记一个类为Spring MVC控制器,其方法返回值通常为视图名称(如String),需配合@ResponseBody注解才能将返回值直接写入HTTP响应体。
· @RestController = @Controller + @ResponseBody的组合注解,类中的所有方法默认都隐式包含@ResponseBody,因此方法返回值直接作为响应内容(JSON/XML等),常用于RESTful API。
若@Controller类中的某个方法需要返回JSON,可单独加@ResponseBody。@RestController则省去了每个方法上的重复注解。
- Spring Boot中如何统一处理全局异常?
使用@RestControllerAdvice + @ExceptionHandler 实现全局异常处理。
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理特定异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
// 提取错误信息
return ResponseEntity.badRequest().body(new ErrorResponse(...));
}
// 处理所有未捕获异常(兜底)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("SYSTEM_ERROR", ex.getMessage()));
}
}
原理:@RestControllerAdvice本质是@ControllerAdvice + @ResponseBody。Spring MVC在DispatcherServlet的processDispatchResult()中,当HandlerExecutionChain或HandlerAdapter抛出异常时,会调用HandlerExceptionResolver链,其中ExceptionHandlerExceptionResolver会查找@ControllerAdvice中匹配的@ExceptionHandler方法执行。
P6注意:@ExceptionHandler优先级规则------精准匹配(异常子类)优先于父类匹配;@ControllerAdvice中的全局处理优先于@ExceptionHandler在@Controller内部的局部处理。若使用Spring Security,安全相关异常(如AccessDeniedException)需通过@ExceptionHandler在@ControllerAdvice中处理,或配置自定义AccessDeniedHandler。
- 嵌入式Tomcat的线程池是如何配置的?
Tomcat线程池配置由 TomcatServletWebServerFactory 控制,其内部有两个核心组件:
· maxThreads:最大工作线程数(默认200)。
· minSpareThreads:核心(最小空闲)线程数(默认10)。
配置方式有两种:
配置文件(推荐):
properties
server.tomcat.threads.max=500
server.tomcat.threads.min-spare=50
server.tomcat.accept-count=100 # 等待队列长度
server.tomcat.max-connections=8192 # 最大连接数
编程方式:
java
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxThreads", "500");
connector.setProperty("minSpareThreads", "50");
});
return factory;
}
关键参数关系:
· max-connections:最大并发连接数(NIO模式下默认10000),超过时新连接被阻塞等待。
· accept-count:当连接数达到max-connections时,等待队列长度,默认100。
· max-threads:实际请求处理线程数,线程池满后请求将等待。
- Spring Boot如何从Tomcat切换到Jetty或Undertow?
通过依赖排除和引入实现:
切换到Jetty:
xml
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
切换到Undertow:
xml
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Spring Boot会根据类路径中存在的WebServerFactory自动装配对应的容器。切换后,server.tomcat.*配置失效,改用server.jetty.或server.undertow.。
P6对比:
· Tomcat:成熟稳定,默认首选。
· Jetty:轻量级,适合开发调试或长连接场景(支持WebSocket更好)。
· Undertow:高并发场景下性能更好,内存占用更低,NIO模型更先进。
- 嵌入式容器的启动原理是什么?WebServerFactory是如何工作的?
核心组件:
· WebServerFactory:创建WebServer的工厂接口,子类有TomcatServletWebServerFactory、JettyServletWebServerFactory等。
· WebServer:封装内嵌容器的实例,提供start()、stop()、getPort()等方法。
启动流程(结合源码):
- 自动配置:ServletWebServerFactoryAutoConfiguration根据类路径中的工厂类(如TomcatServletWebServerFactory)创建默认的WebServerFactory Bean。
- 创建容器:在ServletWebServerApplicationContext的onRefresh()中调用createWebServer()方法。
· createWebServer()调用getWebServerFactory()获取WebServerFactory实例。
· 调用WebServerFactory.getWebServer(...)方法,传入ServletContextInitializer(用于注册DispatcherServlet等)。
· getWebServer()内部创建并初始化具体的容器实例(如Tomcat、Jetty等),设置端口、连接器、协议等。 - 启动容器:在refresh()的finishRefresh()阶段,LifecycleProcessor.onRefresh()触发WebServer.start(),启动内嵌服务器并开始监听端口。
WebServerFactory的工作机制:
· 工厂模式:将容器的创建与配置分离,通过WebServerFactory的getWebServer()方法根据传入的ServletContextInitializer初始化ServletContext,并返回WebServer对象。
· 定制化扩展:可通过实现WebServerFactoryCustomizer接口来修改工厂配置(如server.port、server.tomcat.*等属性最终通过TomcatWebServerFactoryCustomizer应用到工厂)。
P6源码要点:
· ServletWebServerApplicationContext是ApplicationContext的子类,重写了onRefresh()和finishRefresh()。
· 容器的start()并不是在onRefresh()中直接调用,而是在finishRefresh()中通过Lifecycle机制触发,目的是确保所有单例Bean(包括Spring Security Filter链等)在容器启动前已经初始化完成。
· WebServer启动后,主线程通过Thread.currentThread().join()或CountDownLatch保持存活,实际在SpringApplication.run()中调用context后并不会阻塞,而是由容器线程(如Tomcat的Acceptor线程)维持运行。
P6要点总结:Web开发与嵌入式容器是Spring Boot的应用入口层,P6级需掌握:
- 自动配置源码:DispatcherServletAutoConfiguration、WebMvcAutoConfiguration、ServletWebServerFactoryAutoConfiguration的加载与条件机制。
- 容器生命周期:明确区分onRefresh()创建容器与finishRefresh()启动容器,并能说清为什么延迟启动(避免请求进入时Bean未初始化)。
- 性能调优:Tomcat线程池参数关系与生产环境推荐值(如maxThreads=200500,accept-count=100200)。
- 扩展能力:能通过WebServerFactoryCustomizer深度定制容器(如添加HTTPS支持、调整缓冲区大小等)。
七、数据访问与事务(73-81题)
- Spring Boot中数据源的自动配置是如何实现的?
数据源的自动配置由 DataSourceAutoConfiguration 类实现,核心逻辑如下:
- 触发条件:通过@ConditionalOnClass检查类路径是否存在DataSource、EmbeddedDatabaseType等核心类,且配置文件中未显式排除自动配置。
- 加载配置属性:通过@EnableConfigurationProperties(DataSourceProperties.class)将spring.datasource.*属性绑定到DataSourceProperties。
- 创建数据源:根据配置选择合适的创建策略:
· 若配置了spring.datasource.url(或jdbc-url),通过 DataSourceBuilder 根据类路径中的连接池驱动自动检测并创建连接池(默认HikariCP)。
· 若未配置URL,但类路径存在嵌入式数据库驱动(H2、Derby、HSQL),则自动创建嵌入式内存数据库供开发测试使用。
· 通过@ConditionalOnMissingBean确保用户可自定义DataSource Bean覆盖默认配置。 - 初始化数据库:配合DataSourceInitializer,在数据源创建后自动执行schema.sql(建表)和data.sql(初始数据)。
源码关键点:DataSourceAutoConfiguration内部有多个嵌套配置类,如PooledDataSourceConfiguration(池化数据源)、EmbeddedDatabaseConfiguration(嵌入式数据库)。Spring Boot 2.x默认强制HikariCP,若类路径无HikariCP则回退到Tomcat JDBC Pool。
- Spring Boot默认使用什么数据库连接池?为什么?
Spring Boot 2.x+默认使用 HikariCP(Hikari Connection Pool)。
选择原因(P6级需深入):
- 性能极优:HikariCP通过字节码精简(Javassist生成委托类)、无锁并发设计(ConcurrentBag数据结构)、优化代理机制(减少字节码生成开销),在微基准测试中比Tomcat Pool快约25%,比DBCP2快约50%。
- 轻量级:核心Jar包仅约130KB,依赖极少。
- 可靠性与稳定性:经过大量生产环境验证,连接泄漏检测、超时控制等机制完善。
- 自动适配:Spring Boot在DataSourceBuilder中硬编码了HikariCP的优先检测顺序(HikariDataSource > TomcatDataSource > DBCP2DataSource)。
P6补充:若类路径无HikariCP,Spring Boot会按顺序尝试Tomcat JDBC Pool和Commons DBCP2。
- 如何切换Spring Boot的默认连接池?
方式一:排除HikariCP依赖,引入目标连接池
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入Tomcat JDBC Pool -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
Spring Boot自动检测到tomcat-jdbc后会自动切换(DataSourceBuilder按优先级检测)。
方式二:通过spring.datasource.type强制指定
properties
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
此方式要求目标连接池已在类路径中。
方式三:完全自定义DataSource Bean(最灵活)
java
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new MyCustomDataSource(); // 任意第三方连接池
}
- Spring Boot中@Transactional注解的事务传播机制有哪几种?
Spring定义了7种传播行为(通过Propagation枚举),P6必须能清晰区分:
传播行为 含义 典型场景
REQUIRED(默认) 当前存在事务则加入,不存在则新建。 绝大部分业务方法
SUPPORTS 当前存在事务则加入,不存在则以非事务方式执行。 查询方法,非强事务依赖
MANDATORY 当前必须存在事务,否则抛出异常。 强制要求调用方已开启事务
REQUIRES_NEW 挂起当前事务(若存在),新建独立事务执行。 独立日志记录、审计,不受主事务回滚影响
NOT_SUPPORTED 以非事务方式执行,若当前存在事务则挂起。 非核心操作,避免长事务锁
NEVER 以非事务方式执行,若当前存在事务则抛出异常。 明确禁止事务的操作
NESTED 当前存在事务则在嵌套事务中执行(Savepoint机制),否则新建。 部分回滚场景(依赖JDBC Savepoint,非所有ORM支持)
核心区别:REQUIRES_NEW是完全独立的新事务(物理独立),而NESTED是嵌套在现有事务中的逻辑子事务(回滚到Savepoint)。
- Spring Boot声明式事务的实现原理是什么?
声明式事务基于 AOP(动态代理) 实现,核心流程如下:
- 后置处理器:TransactionManagementConfigurationSelector(通过@EnableTransactionManagement导入)向容器注册InfrastructureAdvisorAutoProxyCreator(Bean后置处理器)。
- 创建代理:InfrastructureAdvisorAutoProxyCreator在Bean初始化后,扫描所有带有@Transactional(或匹配切入点)的方法/类,为其创建JDK动态代理或CGLIB代理。
- 拦截逻辑:代理对象调用目标方法时,TransactionInterceptor(实现了MethodInterceptor)拦截执行。
· TransactionInterceptor内部调用PlatformTransactionManager(事务管理器)的getTransaction()方法,根据传播行为获取或创建事务。
· 执行目标方法(invocation.proceed())。
· 若目标方法抛出未捕获的RuntimeException或Error,则调用TransactionAspectSupport的completeTransactionAfterThrowing()进行事务回滚。
· 若方法正常返回,则调用TransactionAspectSupport的commitTransactionAfterReturning()提交事务。
源码核心链路:
@Transactional → TransactionInterceptor → TransactionAspectSupport.invokeWithinTransaction() → AbstractPlatformTransactionManager.getTransaction() → DataSourceTransactionManager.doGetTransaction()(获取数据库连接并设置自动提交为false)。
- @Transactional在哪些场景下会失效?
P6级必问,失效场景汇总如下:
- 非public方法:默认AOP代理仅对public方法生效,protected/private不拦截(可通过AspectJ织入解决,但Spring Boot默认JDK/CGLIB代理不支持)。
- 自调用(内部调用):同一个类中,A方法(无事务)调用B方法(有事务),实际是通过this调用而非代理对象,事务不生效。解决:通过ApplicationContext获取代理Bean或@Autowired注入自己调用。
- 异常类型不匹配:默认仅回滚RuntimeException和Error,Checked Exception(如IOException)不会回滚。需配置@Transactional(rollbackFor = Exception.class)。
- 异常被catch后未重新抛出:目标方法内部catch了异常并处理,未抛给代理拦截器,事务拦截器感知不到异常,会正常提交。
- 事务管理器未正确配置:多数据源场景下,未指定transactionManager,导致使用了错误的事务管理器。
- 数据库引擎不支持事务:如MySQL的MyISAM引擎不支持事务,需改为InnoDB。
- Propagation设置不当:如Propagation.NOT_SUPPORTED或NEVER会导致不会开启事务。
- 类未被Spring管理:@Transactional标注的类未通过@Component等注解注册到Spring容器,代理根本未创建。
- Spring Boot中编程式事务如何实现?
两种实现方式:
方式一:TransactionTemplate(推荐)
java
@Service
public class MyService {
@Autowired
private TransactionTemplate transactionTemplate;
public void doBiz() {
transactionTemplate.execute(status -> {
// 业务操作...
// 遇到异常时调用 status.setRollbackOnly();
// 返回结果
return result;
});
}
}
execute()方法在无异常时自动提交,抛出RuntimeException时自动回滚。
方式二:直接使用PlatformTransactionManager
java
@Autowired
private PlatformTransactionManager transactionManager;
public void doBiz() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务操作...
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
适用场景:需要精细化控制事务边界(如循环中的部分提交),或无法使用@Transactional的场景(如lambda/内部类)。
- TransactionTemplate和@Transactional的区别?
对比维度 @Transactional(声明式) TransactionTemplate(编程式)
实现方式 AOP动态代理,基于注解 模板方法模式,显式编程
代码侵入 无侵入(仅注解) 有侵入(业务代码中包含事务逻辑)
灵活性 低(粒度仅到方法级) 高(粒度细到代码块,可控制单条SQL等)
易用性 简洁,推荐绝大多数场景 稍繁琐,但逻辑清晰
异常回滚 自动根据rollbackFor规则回滚 需手动status.setRollbackOnly()
调试难度 较难(代理链复杂) 容易(纯Java调用链)
适用场景 标准业务事务(90%以上) 复杂事务边界、批量任务、需要部分回滚的场景
P6建议:优先使用@Transactional保持代码清晰;仅在事务边界不规律(如循环中需要单独提交)时使用TransactionTemplate。
- 多数据源场景下如何管理事务?
多数据源事务管理关键点:
- 分别配置两个DataSource和两个PlatformTransactionManager:
java
@Primary
@Bean(name = "primaryTxManager")
public PlatformTransactionManager primaryTxManager(@Qualifier("primaryDs") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean(name = "secondaryTxManager")
public PlatformTransactionManager secondaryTxManager(@Qualifier("secondaryDs") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
- 在@Transactional中指定事务管理器:
java
@Transactional(transactionManager = "primaryTxManager")
public void doPrimaryBiz() { ... }
@Transactional(transactionManager = "secondaryTxManager")
public void doSecondaryBiz() { ... }
- 跨数据源分布式事务(XA):
· JTA方案:引入spring-boot-starter-jta-atomikos或Bitronix,使用JtaTransactionManager统一管理多个XADataSource,实现两阶段提交(2PC)。
· 非XA方案(最终一致性):若无法承受2PC的性能开销,推荐使用事务消息(如RocketMQ)或本地事件表+定时轮询补偿,实现最终一致性。
P6进阶:不建议在微服务跨库场景中使用强分布式事务(性能瓶颈),应通过TCC(Try-Confirm-Cancel)或Saga模式实现业务补偿。若必须在同一个应用内多数据源强一致,Atomikos是轻量级选择,但需注意性能损耗(吞吐量下降约30%~50%)。
八、Actuator与生产监控(82-88题)
- Spring Boot Actuator是什么?有什么作用?
Actuator是Spring Boot的生产就绪功能模块,通过spring-boot-starter-actuator引入。它提供了一系列RESTful API端点(也可通过JMX暴露),用于监控和管理运行中的应用。
核心作用:
· 健康检查:通过/health端点判断应用是否存活,供K8s等编排系统用作存活探针(Liveness Probe)和就绪探针(Readiness Probe)。
· 性能监控:通过/metrics端点收集JVM内存、GC、线程池、HTTP请求耗时等指标。
· 环境诊断:通过/env、/configprops、/beans等端点查看配置、Bean和自动配置条件,辅助线上问题排查。
· 运行时管理:通过/loggers动态调整日志级别,通过/shutdown(需显式开启)优雅关闭应用。
Actuator让应用在推送到生产环境后依然可观测、可管理。
- Actuator提供了哪些常用端点?分别有什么作用?
以下为常用内置端点(完整列表见官方文档):
端点ID 描述
health 展示应用健康状态(UP/DOWN),最核心的生产端点
info 展示自定义应用信息(版本、构建信息等)
metrics 展示JVM、系统、HTTP等性能指标
env 展示Spring Environment中的所有配置属性(敏感值会被脱敏)
beans 展示容器中所有Spring Bean的完整列表
mappings 展示所有@RequestMapping的URL映射路径
configprops 展示所有@ConfigurationProperties的整理列表
conditions 展示自动配置的评估条件及匹配/不匹配原因
loggers 查看和修改应用中日志级别
caches 展示应用中可用的缓存
httpexchanges 展示最近100个HTTP请求-响应交换信息
scheduledtasks 展示所有定时任务
shutdown 优雅关闭应用(默认禁用)
flyway/liquibase 展示数据库迁移信息
- 如何启用和禁用Actuator端点?
Spring Boot中端点有启用(Enabled)和暴露(Exposed)两个维度,只有同时启用且暴露的端点才可通过HTTP/JMX访问。
启用/禁用单个端点:
properties
management.endpoint.shutdown.enabled=true # 启用shutdown端点(默认false)
management.endpoint.metrics.enabled=false # 禁用metrics端点(默认true)
启用/禁用所有端点(全局开关):
properties
management.endpoints.enabled-by-default=false # 默认禁用所有,再单独启用需要的
management.endpoint.health.enabled=true # 单独启用health
禁用端点会将其从ApplicationContext中移除,而不仅仅是隐藏。
暴露端点(对外可访问) :
properties
# 仅暴露health和info(默认行为)
management.endpoints.web.exposure.include=health,info
# 暴露除env和beans外的所有端点
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans
默认只暴露/health和/info,其他端点需显式配置暴露。
- 如何自定义Actuator端点?
有两种方式:
方式一:自定义健康检查(HealthIndicator) ------ 最常用:
java
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean isHealthy = checkExternalService(); // 自定义检查逻辑
if (isHealthy) {
return Health.up().withDetail("service", "Available").build();
}
return Health.down().withDetail("service", "Unavailable").build();
}
}
访问/actuator/health时会自动聚合该指示器的结果。
方式二:自定义通用端点(@Endpoint) ------ 用于暴露业务指标:
java
@Component
@Endpoint(id = "cache-stats") // 访问路径:/actuator/cache-stats
public class CacheStatsEndpoint {
@ReadOperation // GET请求
public Map<String, Object> stats() {
return Map.of("hitRate", 0.95, "size", 1024);
}
@WriteOperation // POST请求
public void clear() { /* 清理缓存 */ }
}
支持@ReadOperation(GET)、@WriteOperation(POST)、@DeleteOperation(DELETE)三种操作。
端点路径可通过management.endpoints.web.base-path全局修改(默认/actuator)。
- 如何保护Actuator端点的安全?
Actuator端点暴露大量敏感信息(环境变量、配置、Bean结构等),在生产环境中必须妥善保护。推荐以下措施:
- 使用Spring Security进行认证授权:
java
@Configuration
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint()) // 仅匹配Actuator端点
.authorizeHttpRequests(requests -> requests
.anyRequest().hasRole("ACTUATOR_ADMIN") // 需要ACTUATOR_ADMIN角色
)
.httpBasic(withDefaults()); // HTTP Basic认证
return http.build();
}
}
Spring Boot提供了EndpointRequest便捷匹配器,用于精准匹配Actuator端点。
- 绑定到内网端口:
properties
management.server.port=8081 # 独立管理端口
management.server.address=127.0.0.1 # 仅本地访问
理想做法是将管理端口绑定到内网专用端口,不暴露在公网。
- 最小化暴露原则:
properties
management.endpoints.web.exposure.include=health,info # 只暴露必要的
-
使用防火墙或网关:通过Spring Cloud Gateway等网关层统一控制Actuator端点的访问。
-
敏感信息脱敏:/env和/configprops中的敏感值(如密码、密钥)会被自动脱敏(显示为******)。
-
/health端点的详细信息和状态是如何聚合的?
/health端点通过HealthIndicator机制聚合所有健康检查结果。
状态聚合规则:
· Spring Boot使用OrderedHealthAggregator按优先级聚合各HealthIndicator的状态。
· 状态优先级(从高到低):DOWN > OUT_OF_SERVICE > UP > UNKNOWN。
· 如果所有组件都是UP,整体状态为UP;只要有一个组件是DOWN,整体即为DOWN。
详细信息控制:
properties
management.endpoint.health.show-details=always # 始终显示详细
management.endpoint.health.show-details=when-authorized # 认证后显示(默认)
management.endpoint.health.show-details=never # 从不显示详情[reference:48]
开启详情后,响应会包含各组件状态:
json
{
"status": "UP",
"components": {
"db": { "status": "UP", "details": { "database": "H2" } },
"diskSpace": { "status": "UP", "details": { "total": 154GB, "free": 123GB } }
}
}
支持层级路径查询:/actuator/health/db查询数据库健康,/actuator/health/broker/us1查询嵌套组件。
- Actuator的Metrics指标如何与Prometheus集成?
Spring Boot通过Micrometer(度量门面,类似日志领域的SLF4J)实现与Prometheus的集成:
- 添加依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
- 暴露Prometheus端点:
properties
management.endpoints.web.exposure.include=health,info,prometheus # 暴露prometheus端点[reference:53]
-
访问指标:访问http://localhost:8080/actuator/prometheus,即可获取Prometheus格式的指标数据。
-
自定义业务指标(可选):使用@Timed注解标注方法,或通过MeterRegistry手动注册指标。
java
@Timed(value = "order.create.time", description = "订单创建耗时")
public void createOrder() { ... }
集成后自动暴露的指标包括:JVM内存/GC、线程池、HTTP请求耗时、数据库连接池、缓存命中率等。Prometheus可定期拉取这些指标,配合Grafana实现可视化监控和告警。
P6要点总结:Actuator是生产环境可观测性的核心组件。P6级需掌握:
- 端点机制:理解"启用+暴露"两层模型,以及内置端点的功能边界。
- 自定义扩展:能通过HealthIndicator和@Endpoint按需扩展监控能力。
- 安全防护:能结合Spring Security、独立管理端口、最小化暴露等策略设计生产级安全方案,并了解CVE-2026-40976等Actuator安全漏洞风险。
- 监控生态:理解Micrometer作为度量门面的设计思想,以及与Prometheus/Grafana的集成链路。
九、日志与测试(89-94题)
- Spring Boot默认使用什么日志框架?如何切换?
Spring Boot默认使用 Logback 作为日志实现,通过spring-boot-starter-logging模块引入(该模块是spring-boot-starter的传递依赖)。默认组合为:SLF4J(门面) + Logback(实现),同时提供了log4j-to-slf4j桥接,确保Apache Log4j 2的API调用也被路由到SLF4J。
切换为Log4j 2:
在pom.xml中排除默认日志模块,并引入Log4j 2 Starter:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
切换后,配置文件自动变为log4j2-spring.xml或log4j2.xml。
- Spring Boot中日志级别如何配置?支持细粒度配置吗?
支持,既支持全局级别配置,也支持包级别和类级别的细粒度配置。
配置文件方式(最常用):
properties
logging.level.root=INFO # 全局级别
logging.level.com.example.service=DEBUG # 包级别
logging.level.com.example.controller.UserController=TRACE # 类级别(精度最高)
动态调整(生产环境利器):
通过Actuator的/loggers端点动态修改,无需重启。例如发送POST请求到/actuator/loggers/com.example.service,Body为{"configuredLevel":"DEBUG"}。该调整立即生效,且支持回滚(设为null恢复默认级别)。
P6注意:级别继承规则为"就近优先"------类级别 > 包级别 > 父包 > root。若频繁调整,注意日志框架对Logger实例的缓存,过细的包级配置可能轻微影响性能。
- Logback的配置文件在Spring Boot中如何加载?
Spring Boot通过LogbackLoggingSystem初始化Logback,加载顺序如下(优先级从高到低):
- 在类路径查找logback-spring.xml(官方强烈推荐,因为可使用Spring扩展特性)。
- 若不存在,查找logback.xml。
- 若仍不存在,则使用Spring Boot内置的defaults.xml基础配置(输出到控制台,级别INFO)。
logback-spring.xml的扩展优势:
· 使用标签读取Spring Boot配置文件(application.yml)中的属性:
xml
<springProperty name="appName" source="spring.application.name"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [${appName}] %-5level %logger{36} - %msg%n"/>
· 使用标签区分环境加载不同配置块:
xml
<springProfile name="dev">
<logger name="com.example" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<logger name="com.example" level="WARN"/>
</springProfile>
- @SpringBootTest注解的作用和原理是什么?
@SpringBootTest是Spring Boot提供的集成测试核心注解,用于加载完整的ApplicationContext,模拟真实应用启动环境。
作用:
· 启动完整的Spring容器,加载所有@Configuration、自动配置和Bean。
· 允许通过webEnvironment属性指定Web环境类型(如MOCK、RANDOM_PORT、DEFINED_PORT)。
· 可与@TestConfiguration或@MockBean配合,定制测试上下文。
原理(源码层次):
· @SpringBootTest通过@BootstrapWith(SpringBootTestContextBootstrapper.class)指定引导类。
· SpringBootTestContextBootstrapper在构建上下文时,会查找@SpringBootConfiguration(默认是启动类)作为配置源。
· 创建SpringApplication实例,并应用与生产几乎相同的初始化流程(监听器、初始化器等),但会使用DefaultTestContext特殊处理。
· 实际通过SpringApplication.run(...)启动上下文,但若指定webEnvironment = WebEnvironment.MOCK,则不会启动内嵌Web服务器,仅模拟Servlet环境。
P6误区提醒:默认@SpringBootTest会加载所有自动配置,启动较慢。生产级测试应优先使用切片测试(如@WebMvcTest、@DataJpaTest)替代全量加载。
- @MockBean和@SpyBean的区别和使用场景?
二者均基于Mockito框架,用于在Spring测试容器中替换或包装Bean。
对比维度 @MockBean @SpyBean
行为模式 创建一个全新的模拟对象,所有方法默认返回空值(0/false/null)。 包装一个真实的Spring Bean实例,未打桩的方法执行真实逻辑。
对容器的影响 若容器已有同类型Bean,则直接替换。 若容器已有同类型Bean,则用Spy包装并替换。
方法调用 默认返回空,需显式when(...).thenReturn(...)打桩。 默认执行真实方法,可对特定方法打桩覆盖其行为。
重置行为 默认每个测试方法执行前重置(可配置@MockBean(reset = MockReset.BEFORE))。 默认不重置(Spy是真实对象,状态共享)。
典型场景 模拟外部依赖(如数据库Repository、第三方API客户端),隔离被测类。 模拟部分方法(如日志记录、审计),同时保留核心业务逻辑的真实执行,或验证真实Bean的内部状态。
使用示例:
java
@SpringBootTest
public class OrderServiceTest {
@MockBean // 完全mock,不依赖真实DB
private UserRepository userRepository;
@SpyBean // 真实对象,但mock其中的sendEmail()方法
private NotificationService notificationService;
@Test
public void testCreateOrder() {
when(userRepository.findById(1L)).thenReturn(Optional.of(new User()));
doNothing().when(notificationService).sendEmail(any());
// orderService调用时,userRepository走mock,notificationService的sendEmail被跳过,其他方法真实执行
}
}
- Spring Boot集成测试如何避免启动完整上下文?
集成测试启动慢的根本原因是加载了所有自动配置和Bean。P6级别需掌握以下几种上下文裁剪策略:
- 使用切片测试(Slice Test)------最核心、最推荐
· @WebMvcTest:仅加载Controller层(@RestController、@ControllerAdvice、Filter、Interceptor等),不会扫描@Service和@Repository,需手动Mock Service层。
· @DataJpaTest:仅加载JPA相关组件(DataSource、EntityManager、Repository),默认为嵌入式内存数据库(替换真实DB),默认事务回滚。
· @RestClientTest:仅加载RestTemplate/WebClient相关配置。
· @JsonTest:仅加载JSON序列化/反序列化组件(ObjectMapper)。
· 这些切片注解通过@TypeExcludeFilters自动排除不必要的自动配置类。
- 使用@ContextConfiguration指定最小化配置
java
@SpringBootTest
@ContextConfiguration(classes = {MyService.class, MyConfig.class})
public class MyServiceTest { ... }
仅加载指定的配置类,跳过整个启动类的扫描。
-
使用@ActiveProfiles("test")配合配置文件
在application-test.yml中禁用不必要的自动配置(如spring.autoconfigure.exclude),或通过spring.main.lazy-initialization=true延迟加载非必需Bean。
-
控制Mock环境
java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
默认即为MOCK,不启动真实内嵌服务器,避免端口绑定和网络开销。
- 使用@Import只导入需要的Bean
配合@TestConfiguration内部静态类,仅导入测试所需的Bean定义。
P6要点总结:日志与测试是生产级应用的"体外生命体征监测仪"。P6级需做到:
- 日志策略:能根据环境(dev/prod)规划日志级别,并能通过Actuator在紧急故障时动态调低级别以捕获Debug信息,事后恢复,避免重启。
- 配置加载机制:理解Spring Boot对日志框架的SPI初始化顺序(LoggingSystem → 配置文件解析 → 应用配置覆盖),能解决多环境日志分离问题。
- 测试分层思想:明确区分单元测试(@Mockito)、切片测试(@WebMvcTest/@DataJpaTest)和全量集成测试(@SpringBootTest),在CI/CD流水线中合理分配测试执行时间,优先运行切片测试提升构建效率。
- 测试容器隔离:能利用@DirtiesContext、@TestInstance管理测试上下文缓存,避免测试间污染导致的随机失败。
十、微服务与Spring Cloud(95-100题)
- Spring Boot和Spring Cloud的关系是什么?
Spring Cloud构建在Spring Boot的基础之上,二者是"底座"与"上层建筑"的关系:
· Spring Boot 提供独立的、生产级的Spring应用框架,通过自动配置和内嵌容器让应用"开箱即跑"。
· Spring Cloud 是一套分布式系统开发的工具集,基于Spring Boot的自动配置和约定风格,将服务注册发现、配置管理、负载均衡、熔断降级、API网关等微服务模式封装为开箱即用的Starter。
本质联系:Spring Cloud依赖Spring Boot的@Conditional机制和Environment抽象来实现自身组件的动态装配。一个Spring Cloud应用本质仍是一个Spring Boot应用,只是多引入了微服务相关的自动配置类。版本上,Spring Cloud以版本序列(如2021.x、2022.x)与Spring Boot的主版本对齐,二者必须保持兼容矩阵。
- Spring Cloud Alibaba和Spring Cloud有什么区别和联系?
· Spring Cloud(官方生态) 定义了一套微服务标准接口规范(如DiscoveryClient、LoadBalancerClient、ConfigData等),默认实现包括Netflix OSS(Eureka、Ribbon、Hystrix)、Spring Cloud Gateway、Spring Cloud Config等。其中Netflix组件已进入维护模式,官方推荐替代方案(如Spring Cloud LoadBalancer替换Ribbon)。
· Spring Cloud Alibaba 是Spring Cloud规范在阿里巴巴开源组件上的具体实现,提供:
· Nacos:服务注册/发现 + 动态配置中心(替代Eureka+Config)。
· Sentinel:流量控制/熔断降级(替代Hystrix)。
· RocketMQ:消息驱动(替代RabbitMQ/Kafka的Stream实现)。
· Seata:分布式事务(替代无)。
联系:Spring Cloud Alibaba遵循Spring Cloud的编程模型(使用@EnableDiscoveryClient、@LoadBalanced等标准注解),因此应用可以无缝迁移。选择上,国内生产环境广泛采用Alibaba体系,因其功能更丰富、文档中文友好、性能优异,且Nacos同时解决注册和配置两个核心痛点。
- Spring Boot如何实现服务的注册与发现?
以Nacos为例(Eureka同理):
- 引入依赖:
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 配置文件:
properties
spring.application.name=order-service
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
- 开启发现客户端:在启动类标注@EnableDiscoveryClient(可选,Spring Cloud Commons已支持自动开启)。
- 服务调用:使用@LoadBalanced注解的RestTemplate或WebClient,通过服务名(而非IP)发起调用:
java
@Autowired
private RestTemplate restTemplate;
public String callUser() {
return restTemplate.getForObject("http://user-service/api/user", String.class);
}
底层原理:NacosDiscoveryClient在应用启动时向Nacos Server发送注册心跳(默认5秒)。BlockingLoadBalancerClient(或ReactiveLoadBalancer)拦截http://{serviceId}的请求,从DiscoveryClient获取可用实例列表,通过负载均衡策略(默认轮询)选取一个实例,替换为目标IP:Port发起实际HTTP调用。
P6追问:如何自定义负载均衡策略?实现ReactiveLoadBalancer或配置@LoadBalancerClient指定配置类。
- Spring Cloud中Config配置中心和Spring Boot外部化配置如何协同?
Spring Cloud Config Server将Git/SVN/Vault中的配置文件通过HTTP API暴露。Spring Boot应用作为Config Client,在启动引导阶段拉取远程配置,并与本地配置合并。
协同流程与优先级:
- 引导阶段:客户端加载bootstrap.yml(若存在),解析spring.cloud.config.uri和spring.application.name。
- 拉取远程配置:Config Client向Server发起/{name}/{profile}/{label}请求,获取远程PropertySource。
- 合并到Environment:远程配置被包装为CompositePropertySource,其优先级高于本地application.yml(即远程配置覆盖本地同名属性)。
- 属性绑定:后续@Value或@ConfigurationProperties按合并后的Environment进行绑定。
动态刷新:
· 配置类标注@RefreshScope。
· 调用/actuator/refresh端点(POST请求),Spring会重新拉取配置并刷新Environment。
· 与Spring Cloud Bus结合可实现批量刷新(通过消息总线通知所有客户端)。
P6注意:Spring Boot 2.4+官方不再推荐bootstrap.yml,改为通过spring.config.import=optional:configserver:http://localhost:8888直接导入配置中心,使配置加载更透明,遵循标准application.yml的优先级语义。
- Spring Boot应用如何接入Sentinel实现熔断限流?
Sentinel是阿里开源的流量治理组件,接入步骤如下:
- 引入依赖:
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置文件:
properties
spring.cloud.sentinel.transport.dashboard=localhost:8080 # Sentinel控制台地址
spring.cloud.sentinel.datasource.ds1.file.file=classpath:flow-rules.json # 可选的规则文件
- 定义资源(两种方式):
· 注解方式(推荐):在方法上使用@SentinelResource,通过value指定资源名,blockHandler指定限流触发回调,fallback指定异常熔断回调。
java
@SentinelResource(value = "getUser", blockHandler = "blockHandler", fallback = "fallbackHandler")
public User getUser(Long id) { ... }
· 代码方式:try (Entry entry = SphU.entry("resourceName")) { ... }。
- 规则配置:通过Sentinel Dashboard动态推送规则(实时生效),或在项目初始化时加载JSON规则文件(FlowRuleManager.loadRules())。
核心能力:
· 流量控制:基于QPS/并发线程数限流,支持直接/关联/链路三种流控模式。
· 熔断降级:基于平均RT、异常比例、异常数三种策略自动熔断。
· 热点参数限流:针对URL参数/IP进行精细化限流。
· 系统自适应保护:根据系统Load、CPU使用率等自适应调节流量。
P6原理:Sentinel利用滑动窗口(LeapArray)统计实时指标,通过责任链模式(ProcessorSlotChain)依次执行NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot、FlowSlot、DegradeSlot等插槽,实现高性能(单机QPS可达数万)。
- 在微服务架构下,Spring Boot应用如何实现链路追踪?
标准方案采用 Spring Cloud Sleuth(已进入维护模式)结合 Micrometer Tracing(新官方标准),通常配合Zipkin或Jaeger进行可视化展示。
最新推荐实践(基于Micrometer Tracing + Brave):
- 引入依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
- 配置文件:
properties
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0 # 采样率(生产建议0.1~0.5)
- 自动注入Trace信息:Spring Boot会自动在HTTP请求头中生成并传播traceId(全局唯一)和spanId(单个服务内跨度),并在日志中自动打印(需配置logging.pattern.level=%5p ${spring.application.name:},%X{traceId:-},%X{spanId:-})。
实现原理(Brave):
· 通过拦截器(HandlerInterceptor) 或过滤器(Filter) 拦截HTTP请求。
· 生成Trace上下文(TraceContext),放入MDC(Mapped Diagnostic Context)供日志输出。
· 在调用下游服务时,将b3头(X-B3-TraceId等)注入HTTP Header,实现Trace传播。
· 完成Span后异步上报至Zipkin/Jaeger,形成依赖拓扑图和调用链时间轴。
P6进阶:在大规模微服务场景下,需关注采样策略(基于速率或动态采样)以避免存储压力和网络开销。若使用OpenTelemetry(未来标准),则通过opentelemetry-spring-boot-starter接入,实现One Agent多Exporter(同时上报Zipkin、Prometheus、ELK)。
P6要点总结:第十部分是微服务架构落地的综合考察,P6级需跳出单一框架,具备选型与架构视野:
- 关系认知:能清晰画出Spring Boot → Spring Cloud → 具体实现(Netflix/Alibaba)的依赖层次。
- 原理深度:不仅要会配Nacos/Sentinel/Zipkin,更要说清其核心机制(如Nacos的AP/CP模式切换、Sentinel的滑动窗口算法、Brave的Trace传播模型)。
- 生产权衡:面对"强一致 vs 高可用"、"高吞吐 vs 全链路追踪"等冲突时,能给出合理的折中方案(如AP优先、采样率调优)。
- 演进意识:关注技术演进方向------bootstrap.yml被import替代、Netflix组件被替换、Sleuth演进为Micrometer Tracing,展示出持续跟踪技术热点的能力。
总结:P6级别考察的核心是知其然更知其所以然------不仅要能用Spring Boot,更要理解自动配置的源码实现、启动流程的每个环节、生产环境的监控与调优。面试中如果能在回答中结合源码分析(如AutoConfigurationImportSelector、SpringFactoriesLoader、refreshContext()等关键类和方法),会是极大的加分项。