主题
五、Spring Boot(10 题)
5.1 ★★★★Spring Boot 的自动配置原理是什么?@EnableAutoConfiguration 的实现原理?spring.factories 文件的作用?
📖 点击查看答案
核心原理:基于约定优于配置,通过 @Conditional 条件注解 + SPI 机制,根据类路径中的依赖自动装配 Bean。
@EnableAutoConfiguration 实现原理:
- 该注解通过
@Import(AutoConfigurationImportSelector.class)导入选择器 AutoConfigurationImportSelector#selectImports调用SpringFactoriesLoader.loadFactoryNames()- 扫描所有依赖 jar 包下
META-INF/spring.factories文件(Spring Boot 2.7+ 同时支持META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) - 将其中
EnableAutoConfigurationkey 对应的全类名作为候选配置类加载 - 通过
@ConditionalOnXXX条件过滤,只装配满足条件的配置类
spring.factories 文件作用:
- SPI 配置文件,key=接口/注解全限定名,value=实现类全限定名(可多个,逗号分隔)
- Spring Boot 启动时扫描该文件,完成自动配置类、监听器、初始化器等组件的注册
java
// 自动配置类示例
@Configuration
@ConditionalOnClass(DataSource.class) // 类路径存在 DataSource 才生效
@ConditionalOnProperty(prefix = "spring.datasource", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(DataSourceProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() { return new MyService(); }
}spring.factories 示例:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration,\
com.example.OtherAutoConfiguration关键点:
@ConditionalOnClass:类路径存在指定类时生效@ConditionalOnMissingBean:容器中不存在该 Bean 时才创建,允许用户覆盖默认配置@ConditionalOnProperty:配置文件满足条件时生效@ConditionalOnBean/@ConditionalOnWebApplication等多种条件注解- Spring Boot 3.x 推荐使用
AutoConfiguration.imports文件替代spring.factories
5.2 ★★@SpringBootApplication 注解底层做了什么?包含哪三个注解?
📖 点击查看答案
三个核心注解:
@SpringBootConfiguration:本质是@Configuration,标记主配置类,允许通过@Bean注册 Bean@EnableAutoConfiguration:开启自动配置机制,加载spring.factories中的配置类@ComponentScan:自动扫描主类所在包及其子包下的@Component/@Service/@Controller等组件
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 1. 配置类
@EnableAutoConfiguration // 2. 自动配置
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
}) // 3. 组件扫描
public @interface SpringBootApplication {
// 排除特定自动配置类
Class<?>[] exclude() default {};
// 排除特定自动配置类名
String[] excludeName() default {};
// 扫描包路径
String[] scanBasePackages() default {};
// 扫描的基类
Class<?>[] scanBasePackageClasses() default {};
}关键点:
@ComponentScan默认扫描主类所在包及子包,这也是为什么主类要放在根包下excludeFilters排除自动配置类,避免重复- 可通过
scanBasePackages自定义扫描路径 @SpringBootConfiguration与@Configuration等价,但语义更明确
5.3 ★★★Spring Boot 应用的启动过程是怎样?SpringApplication.run() 内部做了哪些事?
📖 点击查看答案
启动流程(SpringApplication.run() 核心步骤):
创建 SpringApplication 实例:
- 推断应用类型(SERVLET / REACTIVE / NONE),根据 classpath 是否存在相关类
- 从
META-INF/spring.factories加载ApplicationContextInitializer和ApplicationListener - 推断主类(通过堆栈找到 main 方法所在类)
run() 方法执行:
- 获取并启动
SpringApplicationRunListener(默认EventPublishingRunListener),发布ApplicationStartingEvent - 准备环境(
Environment):加载application.yml/application.properties、命令行参数、系统属性等,发布ApplicationEnvironmentPreparedEvent - 打印 Banner
- 创建
ApplicationContext(根据应用类型选择AnnotationConfigServletWebServerApplicationContext等) - 准备上下文:设置环境、执行
ApplicationContextInitializer、注册主配置类,发布ApplicationContextInitializedEvent和ApplicationPreparedEvent - refresh 上下文:核心,解析配置类、扫描 Bean、创建 Bean、启动内嵌 Web 容器(Tomcat)
- afterRefresh:注册
ApplicationRunner/CommandLineRunner回调 - 发布
ApplicationReadyEvent/ApplicationFailedEvent
- 获取并启动
java
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context); // 核心:IOC 容器初始化 + Web 容器启动
afterRefresh(context, applicationArguments);
listeners.started(context);
callRunners(context, applicationArguments); // 执行 ApplicationRunner
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
}
return context;
}关键扩展点:
SpringApplicationRunListener:监听启动各阶段事件ApplicationContextInitializer:在 context refresh 前初始化上下文ApplicationListener:响应各种启动事件ApplicationRunner/CommandLineRunner:容器启动完成后执行自定义逻辑Banner接口:自定义启动 Banner
5.4 ★★★★Spring Boot 配置文件加载顺序是怎样的?多环境配置(Profile)如何实现?配置优先级顺序是怎样的?
📖 点击查看答案
配置文件加载位置(优先级从高到低,高优先级覆盖低优先级):
- 命令行参数(
--server.port=8080) SPRING_APPLICATION_JSON环境变量中的 JSON- ServletConfig / ServletContext 初始化参数
- JNDI 属性
- Java 系统属性(
System.getProperties()) - 操作系统环境变量
RandomValuePropertySource(random.*)- jar 包外的
application-{profile}.yml/properties - jar 包内的
application-{profile}.yml/properties - jar 包外的
application.yml/properties - jar 包内的
application.yml/properties @PropertySource注解指定的配置SpringApplication.setDefaultProperties设置的默认值
Profile 多环境配置:
- 文件命名:
application-{profile}.yml,如application-dev.yml、application-prod.yml - 激活方式:
- 配置文件中:
spring.profiles.active=dev - 命令行:
--spring.profiles.active=prod - 环境变量:
SPRING_PROFILES_ACTIVE=prod - 默认配置文件
application-default.yml在未指定时生效
- 配置文件中:
yaml
# application.yml
spring:
profiles:
active: dev # 激活 dev 配置
group: # Spring Boot 2.4+ 支持组
"prod": ["prod-db", "prod-mq"]
include: common # 始终包含的配置Spring Boot 2.4+ 配置变更:
- 配置文件不再默认支持多文档覆盖(需使用
spring.config.activate.on-profile) spring.profiles.include只能在主文档中使用- 引入
spring.config.import支持导入其他配置源(如 Nacos、Consul)
优先级总结:命令行 > 环境变量 > 外部 jar 同目录配置 > 内部配置 > 默认值。
5.3 ★★★Spring Boot 条件注解 @ConditionalOnXXX 系列有哪些?各自的使用场景和工作原理是什么?
📖 点击查看答案
条件注解分类:
类条件:
@ConditionalOnClass:类路径存在指定类时生效,用于自动配置类是否生效@ConditionalOnMissingClass:类路径不存在指定类时生效
Bean 条件:
@ConditionalOnBean:容器中存在指定 Bean 时生效@ConditionalOnMissingBean:容器中不存在指定 Bean 时生效(最常用,允许用户覆盖默认 Bean)
属性条件:
@ConditionalOnProperty:配置文件满足条件时生效java@ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true", matchIfMissing = false)
资源条件:
@ConditionalOnResource:类路径存在指定资源文件时生效java@ConditionalOnResource(resources = "classpath:custom.properties")
Web 应用条件:
@ConditionalOnWebApplication:是 Web 应用时生效(SERVLET / REACTIVE)@ConditionalOnNotWebApplication:非 Web 应用时生效
其他条件:
@ConditionalOnExpression:SpEL 表达式为 true 时生效@ConditionalOnJava:JDK 版本满足条件时生效@ConditionalOnSingleCandidate:容器中指定类型的 Bean 唯一或存在首选 Bean 时生效@ConditionalOnDeploymentID:部署 ID 匹配时生效
工作原理:
- 所有条件注解都基于
@Conditional元注解,指向Condition接口实现 - Spring 在注册 Bean 时调用
Condition#matches()判断是否满足条件 - 配置类的条件在配置类解析阶段判断,Bean 的条件在 Bean 注册阶段判断
@ConditionalOnClass通过 ASM 读取类字节码判断,避免直接Class.forName触发ClassNotFoundException
java
@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户未自定义时才创建默认 DataSource
public DataSource dataSource() { /* ... */ }
}自定义条件:
java
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("app.enabled").equals("true");
}
}
@Conditional(MyCondition.class)5.6 ★★★Spring Boot 的 Starter 是什么?工作原理是什么?如何自定义一个 Starter?
📖 点击查看答案
Starter 是什么:
- 一组约定的依赖集合 + 自动配置类,引入即可开箱即用
- 命名约定:官方
spring-boot-starter-xxx,第三方xxx-spring-boot-starter - 解决"依赖冲突 + 配置繁琐"问题,提供合理的默认配置
工作原理:
- 引入 Starter 依赖,jar 包通过
META-INF/spring.factories(或 3.x 的AutoConfiguration.imports)注册自动配置类 - Spring Boot 启动时扫描
spring.factories,加载自动配置类 - 自动配置类通过
@ConditionalOnXXX条件判断决定是否生效 - 通过
@EnableConfigurationProperties绑定配置文件属性到XxxProperties
自定义 Starter 步骤:
- 创建
XxxProperties配置属性类:
java
@ConfigurationProperties(prefix = "hello")
@Data
public class HelloProperties {
private String name = "World";
private int times = 1;
}- 创建核心服务类(非配置类):
java
public class HelloService {
private HelloProperties properties;
public String sayHello() {
return "Hello, " + properties.getName();
}
}- 创建自动配置类:
java
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnProperty(prefix = "hello", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HelloAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties);
}
}- 注册自动配置类:
src/main/resources/META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.hello.HelloAutoConfigurationSpring Boot 3.x 使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.hello.HelloAutoConfiguration- 使用:引入依赖后在
application.yml配置hello.name=Spring即可使用。
5.7 ★★★★Spring Boot Actuator 提供了哪些监控端点?如何自定义健康检查?生产环境如何做访问控制?
📖 点击查看答案
常用端点(默认 /actuator/{endpoint}):
/health:应用健康状态(数据库、Redis、磁盘等)/info:应用基本信息/metrics:JVM、Tomcat、HTTP 等指标/env:环境变量、配置属性/loggers:动态调整日志级别/threaddump:线程栈信息/heapdump:堆转储文件/beans:所有 Bean 列表/mappings:所有@RequestMapping映射/configprops:配置属性/shutdown:优雅关闭应用(默认关闭)/prometheus:Prometheus 监控格式(需引入 micrometer-registry-prometheus)
端点暴露配置:
yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus # 暴露的端点
exclude: env,beans # 排除的端点
endpoint:
health:
show-details: always # 显示健康详情(默认 never)
shutdown:
enabled: true # 开启优雅关闭
server:
port: 8081 # 独立管理端口(推荐)自定义健康检查:
java
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().withDetail("status", "OK").build();
}
}
// 或自定义端点
@Component
@Endpoint(id = "custom")
public class CustomEndpoint {
@ReadOperation
public Map<String, Object> info() {
Map<String, Object> map = new HashMap<>();
map.put("version", "1.0");
return map;
}
@WriteOperation
public void update(String name) { /* ... */ }
}生产环境访问控制:
- 独立管理端口:
management.server.port=8081,不对外暴露 - Spring Security 集成:Actuator 端点自动纳入 Security 过滤链yaml
spring.security.user.name=admin spring.security.user.password=xxx - IP 白名单:通过 Nginx 或防火墙限制管理端口的访问 IP
- 敏感信息脱敏:
/env默认对密码类属性脱敏 - 禁用危险端点:
/shutdown、/heapdump等在生产环境谨慎开放
5.8 ★★Spring Boot 中如何实现统一异常处理?@ControllerAdvice 和 @ExceptionHandler 的原理?
📖 点击查看答案
统一异常处理实现:
java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.fail(400, msg);
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "系统繁忙");
}
}@ControllerAdvice 原理:
- 本质是
@Component,会被 Spring 扫描注册 - 在
ExceptionHandlerExceptionResolver初始化时,initExceptionHandlerAdviceCache()扫描所有@ControllerAdvice类,建立@ExceptionHandler方法缓存 - 拦截到异常时,
ExceptionHandlerExceptionResolver#doResolveHandlerMethodException在缓存中查找匹配的@ExceptionHandler方法 - 找到则通过反射调用并返回结果,找不到则交给下一个
HandlerExceptionResolver处理 @ResponseBody/@RestControllerAdvice通过RequestResponseBodyMethodProcessor将返回值序列化为 JSON
@ExceptionHandler 匹配规则:
- 优先匹配异常类型最精确的
@ExceptionHandler方法 - 支持 Throwable 及其子类,按深度匹配
@ResponseStatus注解可指定 HTTP 状态码
@RestControllerAdvice = @ControllerAdvice + @ResponseBody,返回值直接序列化为 JSON。
5.9 ★★Spring Boot 内嵌了哪些 Web 容器?如何切换为 Undertow 或 Jetty?如何定制参数?
📖 点击查看答案
内嵌容器:
- Tomcat:默认容器,
spring-boot-starter-web默认引入 - Jetty:轻量级,适合长连接场景
- Undertow:高性能,Servlet 3.1+,内存占用少
- Reactor Netty:
spring-boot-starter-webflux默认
切换为 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>切换为 Jetty:将 spring-boot-starter-tomcat 替换为 spring-boot-starter-jetty。
定制参数(以 Tomcat 为例):
yaml
server:
port: 8080
tomcat:
max-connections: 8192 # 最大连接数
threads:
max: 200 # 最大工作线程数
min-spare: 10 # 最小空闲线程数
accept-count: 100 # 等待队列长度
max-http-form-post-size: 2MB # 表单最大大小
connection-timeout: 20s # 连接超时
uri-encoding: UTF-8
max-swallow-size: 2MB # 异常请求最大吞入字节数
compression:
enabled: true # 开启 Gzip
mime-types: application/json,text/html
ssl:
key-store: classpath:keystore.p12
key-store-password: xxx
key-store-type: PKCS12编程式定制:
java
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customizer() {
return factory -> {
factory.setPort(8081);
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxThreads", "300");
});
};
}5.10 ★★Spring Boot 如何实现优雅停机(Graceful Shutdown)?为什么需要优雅停机?
📖 点击查看答案
为什么需要优雅停机:
- 避免请求处理到一半被强制中断,导致数据不一致
- 让正在处理的请求完成,提升用户体验
- 释放资源(连接池、文件句柄、消息队列消费者)
- 通知注册中心下线,避免流量继续打到该实例
Spring Boot 2.3+ 内置优雅停机:
yaml
server:
shutdown: graceful # 开启优雅停机(默认 immediate)
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 优雅停机超时时间原理:
- JVM 收到
SIGTERM信号(kill -15)或SIGINT信号(Ctrl+C) - Spring 调用
SpringApplication注册的 shutdown hook WebServerGracefulShutdownLifecycle拒绝接收新请求,等待已有请求处理完成- 超时后强制关闭
- 依次关闭
LifecycleBean(如@PreDestroy方法) - 关闭 Web 容器、IOC 容器
配合 actuator shutdown 端点:
yaml
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown@PreDestroy 资源释放:
java
@Component
public class MyService {
@PreDestroy
public void cleanup() {
// 释放线程池、连接等资源
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
}
}K8s 场景完整方案:
- PreStop 钩子:先调用注册中心下线接口(如 Eureka
/service-registry/instance-status/DOWN)或actuator/shutdown terminationGracePeriodSeconds: 45(大于优雅停机超时)- 等待流量摘除(服务消费者刷新注册表)
- 容器收到 SIGTERM,执行 Spring 优雅停机