Springboot核心面试题

SpringBoot 面试题 100 道(对标阿里 P6 级别)

按难度和领域分层。P6级别要求候选人不仅会用框架,更要深入理解源码、具备架构思维和性能调优能力,以下题目覆盖从原理源码到生产实践的全链路。


一、基础与核心概念(1-10题)

  1. 说说Spring Boot和Spring的本质区别是什么?

    Spring Boot本质上是Spring框架的扩展,并非替代品。Spring提供通用的IoC、AOP等基础能力,而Spring Boot是"脚手架",通过"约定优于配置"和自动配置机制,消除大量样板代码与XML配置,让开发者能快速创建可运行的独立应用。

  2. Spring Boot的"约定优于配置"如何理解?具体体现在哪些方面?

    其核心思想是框架提供合理的默认值,开发者只需规定应用中不符合约定的部分。体现方面:

· 自动配置:根据类路径下的依赖自动配置相应的Bean。

· 默认配置:如默认的静态资源路径、默认的ErrorController等。

· 起步依赖:通过引入Starter即可获得一组预配置好的依赖。

· 内嵌容器:默认内嵌Tomcat,无需额外部署WAR包。

  1. Spring Boot的核心设计理念有哪些?
    主要包含:

· 约定优于配置:核心范式。

· 自动配置:基于条件注解智能推断并加载组件。

· 开箱即用:提供固化的生产级别应用视图。

· 起步依赖:简化依赖管理。

· 内嵌容器:使应用可独立运行。

· 生产就绪:内置Actuator等监控功能。

  1. Spring Boot的起步依赖(Starter)机制是如何工作的?

    Starter是一组方便的依赖描述符,将具备某功能的相关依赖打包在一起,简化导入过程。它通过Maven/Gradle的传递依赖机制,引入一个Starter即可获得该场景所需的所有依赖。官方Starter遵循spring-boot-starter-*命名格式,并在父POM中统一管理版本,有效避免版本冲突。

  2. Spring Boot为什么要使用内嵌式容器?相比外置容器有什么优缺点?

    优点:实现"开箱即用"的轻量级部署,简化CI/CD流程,方便微服务独立部署和弹性伸缩。

    缺点:基础内存占用较高(120MB以上);生产环境缺少外部容器的高级管理监控功能;每个应用独立包含容器,资源利用率低于共享容器。

  3. Spring Boot和Spring MVC是什么关系?

    Spring MVC是基于Spring的一个Web层MVC框架。Spring Boot包含并扩展了Spring MVC,通过自动配置简化了Spring MVC应用的搭建。可以将Spring理解为"引擎",Spring MVC是基于引擎的"车身",Spring Boot则是能快速组装整车的"生产线"。

  4. Spring Boot支持哪些内嵌容器?如何切换?

    支持Tomcat(默认)、Jetty、Undertow,以及Reactor Netty(WebFlux)。切换方式:在pom.xml中排除spring-boot-starter-tomcat依赖,并引入对应Starter,如spring-boot-starter-jetty。

  5. 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原生镜像有更好支持。

  1. 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方法。

  2. 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题)的详细答案:

  1. @SpringBootApplication由哪三个注解组成?各自的作用是什么?

@SpringBootApplication是一个组合注解,由以下三个核心注解组成:

· @SpringBootConfiguration:标注当前类为配置类,是@Configuration的Spring Boot专用替代品,允许注册额外的Bean或导入其他配置类。

· @EnableAutoConfiguration:开启Spring Boot的自动配置机制。

· @ComponentScan:启用组件扫描,默认扫描当前类所在包及其子包下的所有组件。

此外,@SpringBootApplication还通过@AliasFor提供了exclude、excludeName、scanBasePackages等属性,用于定制@EnableAutoConfiguration和@ComponentScan的行为。

  1. @EnableAutoConfiguration的底层实现原理是什么?

@EnableAutoConfiguration是Spring Boot自动配置的核心入口,其源码如下:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { ... }

核心实现原理分为两步:

  1. @AutoConfigurationPackage:将标注该注解的类所在包注册为自动配置的基础包,用于后续扫描。
  2. @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注册之后应用,以确保用户可以覆盖默认配置。

  1. @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的快捷定制。

  1. @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特有的配置入口。

  1. @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表达式组合复杂条件

  1. @ConditionalOnClass和@ConditionalOnMissingBean的区别与使用场景?

· @ConditionalOnClass:检查类路径。只有当指定类在类路径中存在时,被标注的配置才生效。典型场景:自动配置类中检查依赖库是否存在,例如当类路径存在DataSource.class和EntityManager.class时才自动配置JPA。

· @ConditionalOnMissingBean:检查Spring容器。只有当指定类型的Bean在容器中不存在时,被标注的配置才生效。典型场景:提供默认实现,允许用户通过自定义Bean覆盖------用户定义了DataSource则用用户的,否则用默认的。

核心区别:前者基于类路径(编译/运行时是否存在某个类),后者基于IoC容器(是否已有某个类型的Bean)。

使用建议:官方建议在自动配置类上优先使用@ConditionalOnBean和@ConditionalOnMissingBean,因为这些注解在用户定义的Bean注册之后执行,能正确判断用户是否已提供自定义实现。

  1. @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表达式的场景。

  1. @EnableConfigurationProperties的作用是什么?

@EnableConfigurationProperties用于显式地启用@ConfigurationProperties注解的类,将其注册为Spring容器中的Bean。

通常有两种使用方式:

  1. 在配置类上使用:@EnableConfigurationProperties(DataSourceProperties.class),将DataSourceProperties注册为Bean。
  2. 配合@ConfigurationProperties:在类上直接使用@ConfigurationProperties并配合@Component,也能自动注册,无需@EnableConfigurationProperties。

Spring Boot的自动配置大量使用此注解------例如DataSourceAutoConfiguration通过@EnableConfigurationProperties(DataSourceProperties.class)将配置属性类注入,再通过@ConfigurationProperties将application.yml中的spring.datasource.*属性绑定到该类的字段上。

  1. @Import注解的三种用法分别是什么?

@Import用于将外部配置类或组件导入当前Spring容器。主要有三种用法:

  1. 直接导入@Configuration配置类:将分散的配置类聚合到一个主配置类中。例如@Import({DatabaseConfig.class, RedisConfig.class})。
  2. 通过ImportSelector接口实现动态导入:根据运行时条件动态决定导入哪些类。selectImports()方法返回全类名数组。Spring Boot自动配置的AutoConfigurationImportSelector就是此用法的典型。
  3. 通过ImportBeanDefinitionRegistrar接口实现编程式注册:在运行时通过BeanDefinitionRegistry手动注册Bean定义,灵活性最高。

另外,@Import也可直接导入普通组件类(如@Component)。与@ComponentScan的隐式扫描不同,@Import是显式精确导入。

  1. @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全部加载到容器。

  1. @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、或通过环境变量。

  1. @ConditionalOnWebApplication和@ConditionalOnNotWebApplication的使用场景?

· @ConditionalOnWebApplication:当前应用是Web应用时生效。典型场景:注册Web特有的组件,如MVC配置、过滤器、拦截器等。

· @ConditionalOnNotWebApplication:当前应用不是Web应用时生效。典型场景:批处理任务、后台服务等非Web环境下的配置,避免加载Web相关组件造成不必要的资源消耗。

Spring Boot在启动时会通过WebApplicationType推断应用类型(SERVLET、REACTIVE或NONE),这两个注解基于此推断结果进行条件判断。

  1. @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时才加载该配置。

  1. Spring Boot中如何自定义条件注解?

自定义条件注解分为三步:

  1. 实现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");
    }
}
  1. 组合@Conditional创建自定义注解:
java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(MyCondition.class)
public @interface ConditionalOnMyFeature { }
  1. 使用自定义注解:标注在配置类或Bean方法上即可。

  2. @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题)的详细答案:

  1. Spring Boot自动配置的完整流程是什么?

自动配置的完整流程如下:

  1. 入口触发:应用启动类上的@SpringBootApplication组合注解,其内部的@EnableAutoConfiguration是自动配置的总开关。

  2. 导入选择器:@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入AutoConfigurationImportSelector。

  3. 加载配置类名:AutoConfigurationImportSelector的selectImports()方法被调用,通过SpringFactoriesLoader从META-INF/spring.factories(Spring Boot 2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中读取所有自动配置类的全限定名。

  4. 条件过滤:读取到的自动配置类经过@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)逐一过滤。

  5. 注册Bean:满足所有条件的自动配置类被注册到Spring容器中,执行其内部的@Bean方法完成Bean的创建。

  6. 用户覆盖:自动配置总是在用户自定义的Bean注册之后应用,确保用户可以通过自定义Bean覆盖默认配置。

  7. META-INF/spring.factories文件的作用是什么?

spring.factories文件是Spring Boot SPI机制的核心配置文件,采用Properties格式(键=值,多个值用逗号分隔),定义了多种扩展点的实现类。对于自动配置,关键配置项是org.springframework.boot.autoconfigure.EnableAutoConfiguration,其值列出了所有需要被加载的自动配置类的全限定名。Spring Boot启动时会扫描所有jar包中的该文件,加载其中定义的自动配置类。

  1. Spring Boot 3.x中自动配置的加载机制有什么变化?

Spring Boot 3.x(从2.7开始过渡)移除了spring.factories中注册自动配置的支持,改用新文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。新文件每行一个自动配置类的全限定名,格式更简洁。

  1. SpringFactoriesLoader是如何工作的?

SpringFactoriesLoader是Spring框架的内部工具类,负责加载META-INF/spring.factories文件。工作流程:

  1. 获取ClassPath下所有META-INF/spring.factories文件的URL。

  2. 遍历URL,将每个文件解析为Properties对象。

  3. 根据传入的工厂类型(如EnableAutoConfiguration.class)作为Key,从Properties中获取对应的值列表。

  4. 去重、排序后返回类名列表。

  5. 自动配置类是如何被条件注解过滤的?请结合源码说明。

条件注解的核心是@Conditional,其Condition接口的matches()方法返回true时组件才被注册。Spring Boot的OnClassCondition、OnBeanCondition、OnPropertyCondition等子类分别实现不同的匹配逻辑。AutoConfigurationImportSelector加载所有候选配置类后,会逐个应用这些条件进行过滤,不满足条件的配置类被排除,不会注册到容器。最终生效的配置类会输出在自动配置报告中。

  1. 如何排除某个自动配置类?有哪几种方式?

有三种方式:

· 注解方式:在@SpringBootApplication或@EnableAutoConfiguration上使用exclude(指定Class对象)或excludeName(指定全限定名字符串)属性。

· 配置文件方式:在application.properties或application.yml中配置spring.autoconfigure.exclude属性。

· 注解和配置文件可同时使用,效果叠加。

  1. 如何查看当前应用生效了哪些自动配置?未生效的有哪些?

主要有两种方式:

· 开启Debug模式:在application.properties中添加debug=true,或在启动时加--debug参数,控制台会打印详细的自动配置报告,分为Positive matches(生效)和Negative matches(未生效及原因)。

· 使用Actuator端点:引入spring-boot-starter-actuator后访问/actuator/conditions端点(旧版为/autoconfig),可查看相同的报告信息。

  1. 为什么说自动配置是"开箱即用"的?

"开箱即用"指开发者引入依赖后无需任何额外配置即可使用该功能。因为Spring Boot启动时会根据类路径中的依赖自动推断并配置所需的Bean,且默认配置就是生产可用的。开发者只需关注业务逻辑,框架层面的基础设施配置由Spring Boot自动完成。

  1. 如何编写一个自定义Starter?需要哪些步骤?

  2. 创建两个模块:xxx-spring-boot-starter(空模块,仅依赖自动配置模块)和xxx-spring-boot-autoconfigure(核心实现)。

  3. 编写属性类:使用@ConfigurationProperties定义可配置属性。

  4. 编写服务类:实现核心业务逻辑。

  5. 编写自动配置类:使用@Configuration和条件注解,通过@EnableConfigurationProperties启用属性类,定义@Bean方法创建服务类实例。

  6. 注册自动配置类:在META-INF/spring.factories(2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x)中注册自动配置类的全限定名。

  7. 添加依赖:Starter模块依赖autoconfigure模块。

  8. 自定义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文件,每行一个自动配置类全限定名。

  1. 自定义Starter的自动配置类应该如何设计才能保证灵活性?

· 使用条件注解:通过@ConditionalOnClass、@ConditionalOnMissingBean等让配置按需生效。

· 提供配置属性:通过@ConfigurationProperties暴露可配置参数。

· 允许覆盖:用@ConditionalOnMissingBean标注@Bean方法,让用户可通过自定义Bean覆盖默认实现。

· 合理的默认值:让Starter在无配置时也能工作。

· 模块化设计:将核心逻辑与自动配置分离。

  1. Spring Boot的自动配置和Spring Cloud的自动配置有什么关系?

Spring Cloud的自动配置建立在Spring Boot自动配置之上。Spring Cloud提供自己的Starter和自动配置类(如服务注册发现、配置中心、熔断等),通过spring.factories或AutoConfiguration.imports注册,同样使用@ConditionalOnClass、@ConditionalOnProperty等条件注解。加载时机上,Spring Boot完成基础自动配置后,Spring Cloud的自动配置在此基础上增强微服务相关能力。

  1. 自动配置中@EnableConfigurationProperties和@ConfigurationProperties的配合机制?

@ConfigurationProperties标注在类上,声明该类用于绑定配置属性。@EnableConfigurationProperties在自动配置类上显式注册这些属性类为Spring Bean。配合流程:

  1. 自动配置类使用@EnableConfigurationProperties(XxxProperties.class)。
  2. Spring Boot启动时将配置文件(如application.yml)中对应前缀的属性绑定到XxxProperties的字段上。
  3. 该XxxProperties Bean被注入到自动配置类中,用于创建其他Bean。
    如果属性类本身已标注@Component或@ConfigurationProperties并配合@ComponentScan,则无需@EnableConfigurationProperties也可自动注册。

四、启动流程与源码深度(39-52题)

  1. Spring Boot的完整启动流程是怎样的?分几个阶段?

完整启动流程分为两大阶段:SpringApplication构造阶段和run()执行阶段。

· 构造阶段:设置主源类、推断Web应用类型、加载所有ApplicationContextInitializer和ApplicationListener。

· run()执行阶段(核心):

  1. 启动StopWatch计时器。

  2. 配置Headless属性。

  3. 获取并启动SpringApplicationRunListeners(发布starting事件)。

  4. 准备环境prepareEnvironment()(加载配置文件,发布environmentPrepared事件)。

  5. 打印Banner。

  6. 创建应用上下文createApplicationContext()(根据Web类型创建)。

  7. 准备上下文prepareContext()(设置环境、应用初始化器、加载主源)。

  8. 刷新上下文refreshContext()(最核心,启动内嵌容器、实例化单例Bean)。

  9. 刷新后处理afterRefresh()(执行Runner)。

  10. 发布started和ready事件,返回ConfigurableApplicationContext。

  11. SpringApplication.run()方法内部做了哪些事情?

run()是启动的总入口,其核心逻辑如下(结合源码):

  1. 获取SpringApplicationRunListeners并调用starting()。

  2. 封装命令行参数为ApplicationArguments。

  3. 准备环境prepareEnvironment(),并调用监听器的environmentPrepared()。

  4. 打印Banner(printBanner())。

  5. 创建ApplicationContext(根据webApplicationType决定创建AnnotationConfigServletWebServerApplicationContext等)。

  6. 调用prepareContext(),关联上下文、环境、应用参数,并执行所有ApplicationContextInitializer的initialize(),随后加载主配置类(load())。

  7. 调用refreshContext(),实际调用AbstractApplicationContext.refresh()。

  8. 调用afterRefresh(),执行ApplicationRunner和CommandLineRunner。

  9. 调用listeners.started(context),发布ApplicationStartedEvent。

  10. 调用listeners.running(context),发布ApplicationReadyEvent。

  11. 异常时调用handleRunFailure(),发布ApplicationFailedEvent。

  12. SpringApplication的构造过程做了什么?

SpringApplication的构造过程(new SpringApplication(primarySources))主要做了:

  1. 存储主源类(primarySources,通常是@SpringBootApplication标注的启动类)。

  2. 调用deduceWebApplicationType():通过类路径检测DispatcherHandler(响应式)、DispatcherServlet和ServletContainer(Servlet),推断应用类型(REACTIVE/SERVLET/NONE)。

  3. 调用setInitializers():通过SpringFactoriesLoader从spring.factories加载所有ApplicationContextInitializer实现类并实例化。

  4. 调用setListeners():通过SpringFactoriesLoader加载所有ApplicationListener实现类并实例化。

  5. 调用deduceMainApplicationClass():通过堆栈跟踪找到实际含有main方法的入口类。

  6. 如何推断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可能导致冲突。

  1. 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阶段就会被触发去加载配置文件。

  1. 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框架通用的扩展机制,为了解耦和复用。

  1. prepareContext()方法做了什么?

prepareContext()在上下文创建之后、刷新之前执行,主要任务包括:

  1. 将Environment对象设置到ApplicationContext中。

  2. 处理BeanNameGenerator和ResourceLoader。

  3. 应用所有ApplicationContextInitializer的initialize()方法(对上下文进行定制化增强)。

  4. 将ApplicationArguments(命令行参数封装)注册为单例Bean。

  5. 加载所有主源类(primarySources)到容器中(通过BeanDefinitionLoader)。

  6. 发布ApplicationPreparedEvent事件。

  7. 如果配置了spring.main.lazy-initialization=true,则注册LazyInitializationBeanFactoryPostProcessor以实现全局懒加载。

  8. refreshContext()是整个启动流程中最核心的方法,它内部执行了哪些步骤?

refreshContext()实际调用的是Spring Framework的AbstractApplicationContext.refresh()(模板方法),共12个关键步骤:

  1. prepareRefresh():设置上下文状态,初始化属性源占位符,验证必需属性。
  2. obtainFreshBeanFactory():创建或刷新BeanFactory,加载Bean定义(即扫描@Component、@Bean等)。
  3. prepareBeanFactory():配置BeanFactory(设置ClassLoader、SpEL解析器、注册ApplicationContextAware等内置处理器)。
  4. postProcessBeanFactory():供子类扩展,允许在BeanFactory后置处理前添加特殊Bean定义。
  5. invokeBeanFactoryPostProcessors():关键! 执行所有BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,包括ConfigurationClassPostProcessor(解析@Configuration类)、PropertySourcesPlaceholderConfigurer(解析${...}占位符)。
  6. registerBeanPostProcessors():注册所有BeanPostProcessor,等待后续Bean实例化时拦截。
  7. initMessageSource():初始化国际化消息源。
  8. initApplicationEventMulticaster():初始化事件多播器。
  9. onRefresh():嵌入式容器启动的入口! 子类重写此方法启动内嵌Web服务器。
  10. registerListeners():注册所有ApplicationListener并广播早期事件。
  11. finishBeanFactoryInitialization():核心中的核心! 实例化所有非懒加载的单例Bean(包括业务Bean、数据源等)。
  12. finishRefresh():清理资源,发布ContextRefreshedEvent,启动生命周期处理器(Lifecycle)。

P6难点:必须能说清第5步和第11步的区别(前者处理Bean定义,后者创建Bean实例)。

  1. 内嵌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。

  1. onRefresh()方法在启动流程中的作用是什么?

onRefresh()是Spring AbstractApplicationContext中定义的模板方法,供子类扩展。在Spring Boot中:

· Servlet环境:ServletWebServerApplicationContext.onRefresh() 创建嵌入式Web服务器(createWebServer()),初始化Tomcat/Jetty/Undertow的容器引擎和Connector,但此时不调用start()。

· Reactive环境:ReactiveWebServerApplicationContext.onRefresh() 类似,创建Netty Web服务器。

· 普通环境:空实现。

它是连接Spring容器生命周期与内嵌容器的桥梁,使得Web服务器的创建成为容器刷新过程的一部分。

  1. finishRefresh()方法做了什么?

finishRefresh()是refresh()模板方法的最后一步,主要职责:

  1. 清理上下文资源(如通过ResourceCaches)。
  2. 初始化LifecycleProcessor(生命周期处理器)并调用onRefresh(),触发所有实现了Lifecycle接口组件的启动,包括内嵌Web服务器的真正启动(webServer.start())。
  3. 发布ContextRefreshedEvent(ApplicationContext完全刷新完成事件)。
  4. 将ApplicationContext暴露给JMX(如果启用了MBean导出)。

与onRefresh()区别:onRefresh()负责创建容器实例,finishRefresh()负责启动容器(包括WebServer的start())并发布事件。

  1. ApplicationRunner和CommandLineRunner的区别?执行时机是什么?

执行时机:两者均在SpringApplication.run()的refreshContext()之后,在afterRefresh()方法中执行。

区别:

· CommandLineRunner:接收原始的String... args参数数组,需要自行解析选项参数。

· ApplicationRunner:接收封装好的ApplicationArguments对象,提供getOptionNames()、getOptionValues()等方法,方便获取--key=value格式的命名参数。

排序机制:都支持@Order注解或实现Ordered接口,值越小越先执行。若多个Runner相互独立,推荐用@Order指定顺序。

  1. Spring Boot启动过程中如何实现事件监听?有哪些关键事件?

事件监听通过观察者模式实现,核心组件是ApplicationEventMulticaster。启动流程通过SpringApplicationRunListeners在不同阶段发布事件,桥接到Spring标准事件体系。

关键事件(按时间顺序):

  1. ApplicationStartingEvent:应用开始启动,最早触发(Runner运行前)。
  2. ApplicationEnvironmentPreparedEvent:环境(Environment)准备完毕,配置文件尚未加载。
  3. ApplicationContextInitializedEvent:上下文创建并初始化,但Bean定义尚未加载。
  4. ApplicationPreparedEvent:Bean定义加载完毕,准备刷新上下文。
  5. ApplicationStartedEvent:上下文刷新完成,但Runner尚未执行。
  6. ApplicationReadyEvent:Runner执行完毕,应用完全就绪,可对外服务。
  7. ApplicationFailedEvent:启动过程中抛出异常时触发。

P6追问:ApplicationFailedEvent和ApplicationReadyEvent互斥,只触发其中一个。

  1. 如果让你手写一个简化的Spring Boot启动过程,核心逻辑是什么?

手写简化版核心逻辑(伪代码思路):

  1. 推断Web类型:扫描类路径判断是否有DispatcherServlet。
  2. 创建Spring容器:new AnnotationConfigServletWebServerApplicationContext()(或AnnotationConfigApplicationContext)。
  3. 扫描配置类:将主启动类(含@Configuration)注册为配置源。
  4. 手动触发自动配置:通过模拟AutoConfigurationImportSelector从META-INF/spring.factories加载自动配置类,并手动注册到BeanDefinitionRegistry。
  5. 调用context.refresh():
    · 内部执行invokeBeanFactoryPostProcessors处理配置类。
    · 在onRefresh()中启动内嵌Tomcat(new Tomcat().start())。
    · 执行finishBeanFactoryInitialization()实例化所有单例Bean。
  6. 执行Runner:从容器中取出ApplicationRunner/CommandLineRunner实例并执行。
  7. 发布Ready事件:标记应用启动成功,阻塞主线程(如Thread.currentThread().join())保持服务运行。

P6深度:面试官若追问"如何阻塞",回答"一般通过CountDownLatch或直接让主线程等待,Spring Boot实际是启动一个非守护线程(如Tomcat的Acceptor线程)后,主线程通过JVM的ShutdownHook保持存活,或调用ApplicationContext的close()关闭"。


五、配置文件与外部化配置(53-62题)

  1. 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用于简单启动参数。

  1. application.properties和application.yml的加载优先级?

加载优先级由文件存储位置决定,而非文件格式。位置优先级(从高到低)为:

  1. 命令行参数(最高)
  2. 当前目录下的/config子目录(file:./config/)
  3. 当前目录(file:./)
  4. 类路径下的/config包(classpath:/config/)
  5. 类路径根目录(classpath:/)------最低(即默认位置)。

在同一位置下若同时存在application.properties和application.yml,.properties优先(因为PropertiesPropertySourceLoader在YamlPropertySourceLoader之前执行)。

  1. Spring Boot外部化配置的加载顺序和优先级规则是什么?

Spring Boot外部化配置遵循"后者覆盖前者"原则,通过PropertySource的有序列表(List)实现,靠前的优先级更高。完整顺序(从高到低)为:

  1. Devtools全局配置(~/.spring-boot-devtools.properties,仅DevTools激活时)。
  2. @TestPropertySource(单元测试中的注解)。
  3. 测试properties属性(如@SpringBootTest(properties = {"key=value"}))。
  4. 命令行参数(--server.port=8081)。
  5. SPRING_APPLICATION_JSON(环境变量或系统属性中的JSON内联配置)。
  6. ServletConfig初始化参数。
  7. ServletContext初始化参数。
  8. JNDI属性(java:comp/env)。
  9. Java系统属性(System.getProperties(),即-Dkey=value)。
  10. 操作系统环境变量(SPRING_DATASOURCE_URL等)。
  11. random.*属性(RandomValuePropertySource)。
  12. Jar包外部的application-{profile}.properties/yml。
  13. Jar包内部的application-{profile}.properties/yml。
  14. Jar包外部的application.properties/yml。
  15. Jar包内部的application.properties/yml(最低)。
  16. @PropertySource(通过注解加载的配置)。
  17. 默认属性(SpringApplication.setDefaultProperties()设置的)。

P6追问:优先级核心逻辑在ConfigDataEnvironment中实现,通过ConfigDataLocation解析器将所有外部源打平为PropertySource列表,然后按添加顺序逆序(后加的先取)进行组合。

  1. 如何通过命令行参数覆盖配置文件中的属性?

通过--前缀传递参数,例如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替代。

  1. 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中指定的位置优先级高于默认位置,因此可以用于"补丁式"覆盖特定环境的配置,而不丢失默认配置。

  1. @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校验。

  1. @ConfigurationProperties实现类型安全配置绑定的原理?

核心原理基于Spring Boot的 Binder机制(2.0引入,替代早期的RelaxedDataBinder)。绑定流程如下:

  1. ConfigurationPropertiesBindingPostProcessor(BeanPostProcessor实现)在Bean实例化后、初始化前拦截所有标注@ConfigurationProperties的Bean。
  2. 通过Binder将当前Environment中的所有PropertySource,按照注解的prefix前缀过滤,匹配的键值对通过松绑定(Relaxed Binding)规则映射到Bean的字段上。
    · 松绑定规则:demo.item-price → demo.itemPrice、demo_item_price、DEMO_ITEM_PRICE 均能绑定到itemPrice字段。
  3. 若字段标注了@Validated(JSR-303注解),则触发校验(如@NotNull、@Min等),校验失败抛出BindValidationException。
  4. 通过setter或字段直接反射赋值完成绑定。

P6源码点:Binder的核心方法是bind(ConfigurationPropertyName name, Bindable target),ConfigurationPropertyName负责解析前缀,Bindable封装目标类型,通过DataObjectBinder(支持构造器绑定)和JavaBeanBinder(支持setter)两种策略实现绑定。Spring Boot 2.2+推荐使用构造器绑定(配合@ConstructorBinding)以实现不可变配置。

  1. 配置文件中${...}占位符是如何解析的?

${...}占位符解析由 PropertySourcesPlaceholderConfigurer(BeanFactoryPostProcessor)完成。它实现了EnvironmentAware和PriorityOrdered,在invokeBeanFactoryPostProcessors阶段被触发。

解析过程:

  1. 遍历当前Environment中的所有PropertySource,查找与${key}匹配的值。
  2. 支持默认值语法${key:defaultValue},若key不存在则使用defaultValue。
  3. 支持递归解析(如 a p p . n a m e 的值又包含 {app.name}的值又包含 app.name的值又包含{app.version},会继续解析)。
  4. 在YAML中支持${...}跨文件引用,因为所有配置文件最终合并为一个PropertySource集合。

P6注意:@Value("${...}")中的占位符由DefaultPlaceholderResolver在EmbeddedValueResolver中处理,本质与PropertySourcesPlaceholderConfigurer逻辑一致。若出现循环引用占位符(A引用B,B引用A),会抛出PlaceholderResolutionException。

  1. 如何实现配置的热更新?

纯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会:

  1. 重新拉取远程配置。
  2. 更新Environment中的PropertySource。
  3. 销毁并重建所有@RefreshScope标注的Bean。
    · 也可通过消息总线(Spring Cloud Bus)自动广播刷新事件。

P6生产建议:配置热更新与容器优雅停机结合------先更新配置再重启实例,或使用K8s ConfigMap挂载卷,配合spring.config.import=optional:file:/config/和spring.cloud.kubernetes.reload.enabled=true实现零停机配置刷新。

  1. 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级需掌握:

  1. 优先级心智:命令行 > 环境变量 > 系统属性 > 文件位置(外>内)。
  2. 源码深度:能说出ConfigDataEnvironment的process()方法如何合并PropertySource,以及Binder的松散绑定与构造器绑定原理。
  3. 生产方案:配置热更新不是"启动时刷新",而是通过@RefreshScope + Actuator + 配置中心(如Apollo/Nacos)的组合方案,并能指出纯Spring Boot无法实现自动热更新的根本原因(配置绑定在finishBeanFactoryInitialization()阶段已完成,除非重建Bean)。

六、Web开发与嵌入式容器(63-72题)

  1. Spring Boot中DispatcherServlet的自动配置是如何实现的?

DispatcherServlet的自动配置由 DispatcherServletAutoConfiguration 类实现,其核心逻辑如下:

  1. 默认注册:通过@ConditionalOnClass(DispatcherServlet.class)确保类路径存在Spring MVC时生效。
  2. 默认配置属性:通过@EnableConfigurationProperties(WebMvcProperties.class)将spring.mvc.*属性绑定到WebMvcProperties。
  3. 注册DispatcherServlet Bean:在@Configuration类中定义DispatcherServlet的Bean,默认名称dispatcherServlet。
  4. 注册DispatcherServletRegistration Bean:将DispatcherServlet包装为ServletRegistrationBean,用于注册到内嵌Servlet容器,并配置servlet-mapping(默认为/,可通过spring.mvc.servlet.path修改)。
  5. 条件覆盖:使用@ConditionalOnMissingBean,允许用户自定义DispatcherServlet或DispatcherServletRegistration覆盖默认配置。

源码关键点:DispatcherServletAutoConfiguration内部有两个配置类:

· DefaultDispatcherServletConfiguration:定义DispatcherServlet Bean。

· DispatcherServletRegistrationConfiguration:定义DispatcherServletRegistrationBean。

若用户提供了自定义的DispatcherServlet,则自动配置会跳过默认注册(@ConditionalOnMissingBean生效)。


  1. Spring Boot如何处理静态资源?默认的静态资源路径有哪些?

Spring Boot通过 WebMvcAutoConfiguration 自动配置了静态资源处理。其核心是ResourceHttpRequestHandler,默认从以下路径查找静态资源(按顺序):

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/
  4. classpath:/public/
  5. /(ServletContext根目录,即webapp/,仅打包为WAR时有效)

处理逻辑:当请求路径匹配到静态资源(如/css/style.css)时,按上述顺序查找,找到即返回;若所有路径均未找到,则转交给DispatcherServlet处理业务请求。

默认的静态资源映射路径为/**(优先级低于DispatcherServlet的/映射),但WebMvcAutoConfiguration会配置/webjars/**映射到classpath:/META-INF/resources/webjars/,用于支持WebJars。


  1. 如何自定义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已废弃)。


  1. Spring Boot中如何配置拦截器?

配置拦截器需要实现HandlerInterceptor接口,并通过WebMvcConfigurer.addInterceptors()注册。步骤如下:

  1. 自定义拦截器类:
java 复制代码
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 前置逻辑
        return true;
    }
}
  1. 注册拦截器:
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层),不拦截静态资源(除非特意配置)。


  1. 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()启用)。


  1. @RestController和@Controller的区别?

· @Controller:标记一个类为Spring MVC控制器,其方法返回值通常为视图名称(如String),需配合@ResponseBody注解才能将返回值直接写入HTTP响应体。

· @RestController = @Controller + @ResponseBody的组合注解,类中的所有方法默认都隐式包含@ResponseBody,因此方法返回值直接作为响应内容(JSON/XML等),常用于RESTful API。

若@Controller类中的某个方法需要返回JSON,可单独加@ResponseBody。@RestController则省去了每个方法上的重复注解。


  1. 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。


  1. 嵌入式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:实际请求处理线程数,线程池满后请求将等待。


  1. 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模型更先进。


  1. 嵌入式容器的启动原理是什么?WebServerFactory是如何工作的?

核心组件:

· WebServerFactory:创建WebServer的工厂接口,子类有TomcatServletWebServerFactory、JettyServletWebServerFactory等。

· WebServer:封装内嵌容器的实例,提供start()、stop()、getPort()等方法。

启动流程(结合源码):

  1. 自动配置:ServletWebServerFactoryAutoConfiguration根据类路径中的工厂类(如TomcatServletWebServerFactory)创建默认的WebServerFactory Bean。
  2. 创建容器:在ServletWebServerApplicationContext的onRefresh()中调用createWebServer()方法。
    · createWebServer()调用getWebServerFactory()获取WebServerFactory实例。
    · 调用WebServerFactory.getWebServer(...)方法,传入ServletContextInitializer(用于注册DispatcherServlet等)。
    · getWebServer()内部创建并初始化具体的容器实例(如Tomcat、Jetty等),设置端口、连接器、协议等。
  3. 启动容器:在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级需掌握:

  1. 自动配置源码:DispatcherServletAutoConfiguration、WebMvcAutoConfiguration、ServletWebServerFactoryAutoConfiguration的加载与条件机制。
  2. 容器生命周期:明确区分onRefresh()创建容器与finishRefresh()启动容器,并能说清为什么延迟启动(避免请求进入时Bean未初始化)。
  3. 性能调优:Tomcat线程池参数关系与生产环境推荐值(如maxThreads=200500,accept-count=100200)。
  4. 扩展能力:能通过WebServerFactoryCustomizer深度定制容器(如添加HTTPS支持、调整缓冲区大小等)。

七、数据访问与事务(73-81题)

  1. Spring Boot中数据源的自动配置是如何实现的?

数据源的自动配置由 DataSourceAutoConfiguration 类实现,核心逻辑如下:

  1. 触发条件:通过@ConditionalOnClass检查类路径是否存在DataSource、EmbeddedDatabaseType等核心类,且配置文件中未显式排除自动配置。
  2. 加载配置属性:通过@EnableConfigurationProperties(DataSourceProperties.class)将spring.datasource.*属性绑定到DataSourceProperties。
  3. 创建数据源:根据配置选择合适的创建策略:
    · 若配置了spring.datasource.url(或jdbc-url),通过 DataSourceBuilder 根据类路径中的连接池驱动自动检测并创建连接池(默认HikariCP)。
    · 若未配置URL,但类路径存在嵌入式数据库驱动(H2、Derby、HSQL),则自动创建嵌入式内存数据库供开发测试使用。
    · 通过@ConditionalOnMissingBean确保用户可自定义DataSource Bean覆盖默认配置。
  4. 初始化数据库:配合DataSourceInitializer,在数据源创建后自动执行schema.sql(建表)和data.sql(初始数据)。

源码关键点:DataSourceAutoConfiguration内部有多个嵌套配置类,如PooledDataSourceConfiguration(池化数据源)、EmbeddedDatabaseConfiguration(嵌入式数据库)。Spring Boot 2.x默认强制HikariCP,若类路径无HikariCP则回退到Tomcat JDBC Pool。


  1. Spring Boot默认使用什么数据库连接池?为什么?

Spring Boot 2.x+默认使用 HikariCP(Hikari Connection Pool)。

选择原因(P6级需深入):

  1. 性能极优:HikariCP通过字节码精简(Javassist生成委托类)、无锁并发设计(ConcurrentBag数据结构)、优化代理机制(减少字节码生成开销),在微基准测试中比Tomcat Pool快约25%,比DBCP2快约50%。
  2. 轻量级:核心Jar包仅约130KB,依赖极少。
  3. 可靠性与稳定性:经过大量生产环境验证,连接泄漏检测、超时控制等机制完善。
  4. 自动适配:Spring Boot在DataSourceBuilder中硬编码了HikariCP的优先检测顺序(HikariDataSource > TomcatDataSource > DBCP2DataSource)。

P6补充:若类路径无HikariCP,Spring Boot会按顺序尝试Tomcat JDBC Pool和Commons DBCP2。


  1. 如何切换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(); // 任意第三方连接池
}

  1. Spring Boot中@Transactional注解的事务传播机制有哪几种?

Spring定义了7种传播行为(通过Propagation枚举),P6必须能清晰区分:

传播行为 含义 典型场景

REQUIRED(默认) 当前存在事务则加入,不存在则新建。 绝大部分业务方法

SUPPORTS 当前存在事务则加入,不存在则以非事务方式执行。 查询方法,非强事务依赖

MANDATORY 当前必须存在事务,否则抛出异常。 强制要求调用方已开启事务

REQUIRES_NEW 挂起当前事务(若存在),新建独立事务执行。 独立日志记录、审计,不受主事务回滚影响

NOT_SUPPORTED 以非事务方式执行,若当前存在事务则挂起。 非核心操作,避免长事务锁

NEVER 以非事务方式执行,若当前存在事务则抛出异常。 明确禁止事务的操作

NESTED 当前存在事务则在嵌套事务中执行(Savepoint机制),否则新建。 部分回滚场景(依赖JDBC Savepoint,非所有ORM支持)

核心区别:REQUIRES_NEW是完全独立的新事务(物理独立),而NESTED是嵌套在现有事务中的逻辑子事务(回滚到Savepoint)。


  1. Spring Boot声明式事务的实现原理是什么?

声明式事务基于 AOP(动态代理) 实现,核心流程如下:

  1. 后置处理器:TransactionManagementConfigurationSelector(通过@EnableTransactionManagement导入)向容器注册InfrastructureAdvisorAutoProxyCreator(Bean后置处理器)。
  2. 创建代理:InfrastructureAdvisorAutoProxyCreator在Bean初始化后,扫描所有带有@Transactional(或匹配切入点)的方法/类,为其创建JDK动态代理或CGLIB代理。
  3. 拦截逻辑:代理对象调用目标方法时,TransactionInterceptor(实现了MethodInterceptor)拦截执行。
    · TransactionInterceptor内部调用PlatformTransactionManager(事务管理器)的getTransaction()方法,根据传播行为获取或创建事务。
    · 执行目标方法(invocation.proceed())。
    · 若目标方法抛出未捕获的RuntimeException或Error,则调用TransactionAspectSupport的completeTransactionAfterThrowing()进行事务回滚。
    · 若方法正常返回,则调用TransactionAspectSupport的commitTransactionAfterReturning()提交事务。

源码核心链路:

@Transactional → TransactionInterceptor → TransactionAspectSupport.invokeWithinTransaction() → AbstractPlatformTransactionManager.getTransaction() → DataSourceTransactionManager.doGetTransaction()(获取数据库连接并设置自动提交为false)。


  1. @Transactional在哪些场景下会失效?

P6级必问,失效场景汇总如下:

  1. 非public方法:默认AOP代理仅对public方法生效,protected/private不拦截(可通过AspectJ织入解决,但Spring Boot默认JDK/CGLIB代理不支持)。
  2. 自调用(内部调用):同一个类中,A方法(无事务)调用B方法(有事务),实际是通过this调用而非代理对象,事务不生效。解决:通过ApplicationContext获取代理Bean或@Autowired注入自己调用。
  3. 异常类型不匹配:默认仅回滚RuntimeException和Error,Checked Exception(如IOException)不会回滚。需配置@Transactional(rollbackFor = Exception.class)。
  4. 异常被catch后未重新抛出:目标方法内部catch了异常并处理,未抛给代理拦截器,事务拦截器感知不到异常,会正常提交。
  5. 事务管理器未正确配置:多数据源场景下,未指定transactionManager,导致使用了错误的事务管理器。
  6. 数据库引擎不支持事务:如MySQL的MyISAM引擎不支持事务,需改为InnoDB。
  7. Propagation设置不当:如Propagation.NOT_SUPPORTED或NEVER会导致不会开启事务。
  8. 类未被Spring管理:@Transactional标注的类未通过@Component等注解注册到Spring容器,代理根本未创建。

  1. 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/内部类)。


  1. TransactionTemplate和@Transactional的区别?

对比维度 @Transactional(声明式) TransactionTemplate(编程式)

实现方式 AOP动态代理,基于注解 模板方法模式,显式编程

代码侵入 无侵入(仅注解) 有侵入(业务代码中包含事务逻辑)

灵活性 低(粒度仅到方法级) 高(粒度细到代码块,可控制单条SQL等)

易用性 简洁,推荐绝大多数场景 稍繁琐,但逻辑清晰

异常回滚 自动根据rollbackFor规则回滚 需手动status.setRollbackOnly()

调试难度 较难(代理链复杂) 容易(纯Java调用链)

适用场景 标准业务事务(90%以上) 复杂事务边界、批量任务、需要部分回滚的场景

P6建议:优先使用@Transactional保持代码清晰;仅在事务边界不规律(如循环中需要单独提交)时使用TransactionTemplate。


  1. 多数据源场景下如何管理事务?

多数据源事务管理关键点:

  1. 分别配置两个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);
}
  1. 在@Transactional中指定事务管理器:
java 复制代码
@Transactional(transactionManager = "primaryTxManager")
public void doPrimaryBiz() { ... }

@Transactional(transactionManager = "secondaryTxManager")
public void doSecondaryBiz() { ... }
  1. 跨数据源分布式事务(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题)

  1. 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让应用在推送到生产环境后依然可观测、可管理。

  1. 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 展示数据库迁移信息

  1. 如何启用和禁用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,其他端点需显式配置暴露。

  1. 如何自定义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)。

  1. 如何保护Actuator端点的安全?

Actuator端点暴露大量敏感信息(环境变量、配置、Bean结构等),在生产环境中必须妥善保护。推荐以下措施:

  1. 使用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端点。

  1. 绑定到内网端口:
properties 复制代码
management.server.port=8081              # 独立管理端口
management.server.address=127.0.0.1      # 仅本地访问

理想做法是将管理端口绑定到内网专用端口,不暴露在公网。

  1. 最小化暴露原则:
properties 复制代码
management.endpoints.web.exposure.include=health,info   # 只暴露必要的
  1. 使用防火墙或网关:通过Spring Cloud Gateway等网关层统一控制Actuator端点的访问。

  2. 敏感信息脱敏:/env和/configprops中的敏感值(如密码、密钥)会被自动脱敏(显示为******)。

  3. /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查询嵌套组件。

  1. Actuator的Metrics指标如何与Prometheus集成?

Spring Boot通过Micrometer(度量门面,类似日志领域的SLF4J)实现与Prometheus的集成:

  1. 添加依赖:
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>
  1. 暴露Prometheus端点:
properties 复制代码
management.endpoints.web.exposure.include=health,info,prometheus   # 暴露prometheus端点[reference:53]
  1. 访问指标:访问http://localhost:8080/actuator/prometheus,即可获取Prometheus格式的指标数据。

  2. 自定义业务指标(可选):使用@Timed注解标注方法,或通过MeterRegistry手动注册指标。

java 复制代码
@Timed(value = "order.create.time", description = "订单创建耗时")
public void createOrder() { ... }

集成后自动暴露的指标包括:JVM内存/GC、线程池、HTTP请求耗时、数据库连接池、缓存命中率等。Prometheus可定期拉取这些指标,配合Grafana实现可视化监控和告警。

P6要点总结:Actuator是生产环境可观测性的核心组件。P6级需掌握:

  1. 端点机制:理解"启用+暴露"两层模型,以及内置端点的功能边界。
  2. 自定义扩展:能通过HealthIndicator和@Endpoint按需扩展监控能力。
  3. 安全防护:能结合Spring Security、独立管理端口、最小化暴露等策略设计生产级安全方案,并了解CVE-2026-40976等Actuator安全漏洞风险。
  4. 监控生态:理解Micrometer作为度量门面的设计思想,以及与Prometheus/Grafana的集成链路。

九、日志与测试(89-94题)

  1. 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。


  1. 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实例的缓存,过细的包级配置可能轻微影响性能。


  1. Logback的配置文件在Spring Boot中如何加载?

Spring Boot通过LogbackLoggingSystem初始化Logback,加载顺序如下(优先级从高到低):

  1. 在类路径查找logback-spring.xml(官方强烈推荐,因为可使用Spring扩展特性)。
  2. 若不存在,查找logback.xml。
  3. 若仍不存在,则使用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>

  1. @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)替代全量加载。


  1. @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被跳过,其他方法真实执行
    }
}

  1. Spring Boot集成测试如何避免启动完整上下文?

集成测试启动慢的根本原因是加载了所有自动配置和Bean。P6级别需掌握以下几种上下文裁剪策略:

  1. 使用切片测试(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自动排除不必要的自动配置类。

  1. 使用@ContextConfiguration指定最小化配置
java 复制代码
@SpringBootTest
@ContextConfiguration(classes = {MyService.class, MyConfig.class})
public class MyServiceTest { ... }

仅加载指定的配置类,跳过整个启动类的扫描。

  1. 使用@ActiveProfiles("test")配合配置文件

    在application-test.yml中禁用不必要的自动配置(如spring.autoconfigure.exclude),或通过spring.main.lazy-initialization=true延迟加载非必需Bean。

  2. 控制Mock环境

java 复制代码
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)

默认即为MOCK,不启动真实内嵌服务器,避免端口绑定和网络开销。

  1. 使用@Import只导入需要的Bean
    配合@TestConfiguration内部静态类,仅导入测试所需的Bean定义。

P6要点总结:日志与测试是生产级应用的"体外生命体征监测仪"。P6级需做到:

  1. 日志策略:能根据环境(dev/prod)规划日志级别,并能通过Actuator在紧急故障时动态调低级别以捕获Debug信息,事后恢复,避免重启。
  2. 配置加载机制:理解Spring Boot对日志框架的SPI初始化顺序(LoggingSystem → 配置文件解析 → 应用配置覆盖),能解决多环境日志分离问题。
  3. 测试分层思想:明确区分单元测试(@Mockito)、切片测试(@WebMvcTest/@DataJpaTest)和全量集成测试(@SpringBootTest),在CI/CD流水线中合理分配测试执行时间,优先运行切片测试提升构建效率。
  4. 测试容器隔离:能利用@DirtiesContext、@TestInstance管理测试上下文缓存,避免测试间污染导致的随机失败。

十、微服务与Spring Cloud(95-100题)

  1. 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的主版本对齐,二者必须保持兼容矩阵。

  1. 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同时解决注册和配置两个核心痛点。

  1. Spring Boot如何实现服务的注册与发现?

以Nacos为例(Eureka同理):

  1. 引入依赖:
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 配置文件:
properties 复制代码
spring.application.name=order-service
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
  1. 开启发现客户端:在启动类标注@EnableDiscoveryClient(可选,Spring Cloud Commons已支持自动开启)。
  2. 服务调用:使用@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指定配置类。

  1. Spring Cloud中Config配置中心和Spring Boot外部化配置如何协同?

Spring Cloud Config Server将Git/SVN/Vault中的配置文件通过HTTP API暴露。Spring Boot应用作为Config Client,在启动引导阶段拉取远程配置,并与本地配置合并。

协同流程与优先级:

  1. 引导阶段:客户端加载bootstrap.yml(若存在),解析spring.cloud.config.uri和spring.application.name
  2. 拉取远程配置:Config Client向Server发起/{name}/{profile}/{label}请求,获取远程PropertySource。
  3. 合并到Environment:远程配置被包装为CompositePropertySource,其优先级高于本地application.yml(即远程配置覆盖本地同名属性)。
  4. 属性绑定:后续@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的优先级语义。

  1. Spring Boot应用如何接入Sentinel实现熔断限流?

Sentinel是阿里开源的流量治理组件,接入步骤如下:

  1. 引入依赖:
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 配置文件:
properties 复制代码
spring.cloud.sentinel.transport.dashboard=localhost:8080   # Sentinel控制台地址
spring.cloud.sentinel.datasource.ds1.file.file=classpath:flow-rules.json  # 可选的规则文件
  1. 定义资源(两种方式):
    · 注解方式(推荐):在方法上使用@SentinelResource,通过value指定资源名,blockHandler指定限流触发回调,fallback指定异常熔断回调。
java 复制代码
@SentinelResource(value = "getUser", blockHandler = "blockHandler", fallback = "fallbackHandler")
public User getUser(Long id) { ... }

· 代码方式:try (Entry entry = SphU.entry("resourceName")) { ... }。

  1. 规则配置:通过Sentinel Dashboard动态推送规则(实时生效),或在项目初始化时加载JSON规则文件(FlowRuleManager.loadRules())。

核心能力:

· 流量控制:基于QPS/并发线程数限流,支持直接/关联/链路三种流控模式。

· 熔断降级:基于平均RT、异常比例、异常数三种策略自动熔断。

· 热点参数限流:针对URL参数/IP进行精细化限流。

· 系统自适应保护:根据系统Load、CPU使用率等自适应调节流量。

P6原理:Sentinel利用滑动窗口(LeapArray)统计实时指标,通过责任链模式(ProcessorSlotChain)依次执行NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot、FlowSlot、DegradeSlot等插槽,实现高性能(单机QPS可达数万)。

  1. 在微服务架构下,Spring Boot应用如何实现链路追踪?

标准方案采用 Spring Cloud Sleuth(已进入维护模式)结合 Micrometer Tracing(新官方标准),通常配合Zipkin或Jaeger进行可视化展示。

最新推荐实践(基于Micrometer Tracing + Brave):

  1. 引入依赖:
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>
  1. 配置文件:
properties 复制代码
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0        # 采样率(生产建议0.1~0.5)
  1. 自动注入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级需跳出单一框架,具备选型与架构视野:

  1. 关系认知:能清晰画出Spring Boot → Spring Cloud → 具体实现(Netflix/Alibaba)的依赖层次。
  2. 原理深度:不仅要会配Nacos/Sentinel/Zipkin,更要说清其核心机制(如Nacos的AP/CP模式切换、Sentinel的滑动窗口算法、Brave的Trace传播模型)。
  3. 生产权衡:面对"强一致 vs 高可用"、"高吞吐 vs 全链路追踪"等冲突时,能给出合理的折中方案(如AP优先、采样率调优)。
  4. 演进意识:关注技术演进方向------bootstrap.yml被import替代、Netflix组件被替换、Sleuth演进为Micrometer Tracing,展示出持续跟踪技术热点的能力。

总结:P6级别考察的核心是知其然更知其所以然------不仅要能用Spring Boot,更要理解自动配置的源码实现、启动流程的每个环节、生产环境的监控与调优。面试中如果能在回答中结合源码分析(如AutoConfigurationImportSelector、SpringFactoriesLoader、refreshContext()等关键类和方法),会是极大的加分项。

相关推荐
器灵科技1 小时前
Seedance2.5 VS MiniMax H3 同日上线:AI短剧创作者该怎么选?
java·人工智能·阿里云·prompt·aigc
xbgRS1 小时前
Mybatis源码-核心流程及核心类
java·mybatis
zzh___zzh1 小时前
Java Stream API 常用方法笔记
java·笔记
萌动的小火苗2 小时前
1、python基础面试题
java·开发语言·python
运维行者_2 小时前
网络监控与ITSM集成:从告警到工单,实现运维自动化闭环
开发语言·网络·分布式·后端·架构·flask·php
子兮曰2 小时前
解剖 Claude Code:从入口架构逆向工程看 AI 编程工具的信任边界设计
前端·后端·claude
颜进强2 小时前
前端看后端 15:什么是 DNS?
前端·后端·ai编程
文艺理科生2 小时前
LangChain.js-v1-记忆管理最佳实践
前端·javascript·后端
麻雀聊技术2 小时前
一重启 AI 就失忆?用 Spring AI + PostgreSQL 3步搞定“长期记忆”持久化(附完整代码)
java·spring·openai