Spring Boot 详解
一、Spring Boot 概述与设计理念
1.1 什么是 Spring Boot
Spring Boot 是基于 Spring 框架的快速开发脚手架,旨在简化 Spring 应用的初始搭建和开发过程。它通过"约定优于配置"的理念,让开发者以最少的配置快速创建生产级别的应用。
1.2 核心设计理念
| 理念 | 说明 |
|---|---|
| 约定优于配置 | 提供默认配置,减少显式配置量 |
| 自动配置 | 根据类路径依赖自动装配 Bean |
| 起步依赖 | 通过 Starter 聚合相关依赖 |
| 生产就绪 | 内嵌 Actuator 提供监控、健康检查 |
| 无代码生成 | 不依赖 XML,纯 Java 配置 |
1.3 Spring Boot 版本演进
Spring Boot 1.x → Spring Framework 4.x,Java 6+
Spring Boot 2.x → Spring Framework 5.x,Java 8+,引入 WebFlux
Spring Boot 3.x → Spring Framework 6.x,Java 17+,Jakarta EE,GraalVM Native
1.4 快速开始
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
<!-- pom.xml 核心依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
二、启动流程源码分析(SpringApplication.run)
2.1 启动入口
public static void main(String[] args) {
// 两步走:创建实例 + 执行run
SpringApplication.run(Application.class, args);
}
// 等价于:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class, args);
ConfigurableApplicationContext context = app.run(args);
}
2.2 SpringApplication 构造阶段
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
// 1. 保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 推断应用类型:SERVLET / REACTIVE / NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3. 从 spring.factories 加载 ApplicationContextInitializer
this.initializers = getSpringFactoriesInstances(
ApplicationContextInitializer.class);
// 4. 从 spring.factories 加载 ApplicationListener
this.listeners = getSpringFactoriesInstances(ApplicationListener.class);
// 5. 推断主启动类(通过堆栈找到 main 方法所在类)
this.mainApplicationClass = deduceMainApplicationClass();
}
WebApplicationType 推断逻辑:
static WebApplicationType deduceFromClasspath() {
// 存在 org.springframework.web.reactive.DispatcherHandler
// 且不存在 org.springframework.web.servlet.DispatcherServlet
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS)) {
return WebApplicationType.REACTIVE;
}
// 存在 javax.servlet.Servlet 或 DispatcherServlet
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
2.3 run() 方法主流程
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
// 1. 创建 BootstrapContext
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 2. 获取所有 SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 3. 发布 ApplicationStartingEvent
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 4. 准备 Environment
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(
listeners, bootstrapContext, applicationArguments);
// 5. 配置忽略的 Bean 信息
configureIgnoreBeanInfo(environment);
// 6. 打印 Banner
Banner printedBanner = printBanner(environment);
// 7. 创建 ApplicationContext
context = createApplicationContext();
// 8. 准备上下文
prepareContext(bootstrapContext, context, environment,
listeners, applicationArguments, printedBanner);
// 9. 刷新上下文(核心!触发自动配置和Bean创建)
refreshContext(context);
// 10. 刷新后处理(模板方法,默认空实现)
afterRefresh(context, applicationArguments);
// 11. 发布 ApplicationStartedEvent
listeners.started(context, timeTakenToStartup);
// 12. 执行 Runner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
// 13. 发布 ApplicationReadyEvent
listeners.ready(context, timeTakenToReady);
return context;
}
2.4 refreshContext 核心流程
// AbstractApplicationContext.refresh() 的12个步骤
public void refresh() {
// 1. prepareRefresh() - 准备工作,设置启动时间、活跃标志
// 2. obtainFreshBeanFactory() - 获取 BeanFactory
// 3. prepareBeanFactory() - 配置 BeanFactory 标准特性
// 4. postProcessBeanFactory() - 子类扩展点
// 5. invokeBeanFactoryPostProcessors() - 执行 BeanFactoryPostProcessor
// ★ 这里触发 ConfigurationClassPostProcessor,解析自动配置
// 6. registerBeanPostProcessors() - 注册 BeanPostProcessor
// 7. initMessageSource() - 初始化消息源(国际化)
// 8. initApplicationEventMulticaster() - 初始化事件广播器
// 9. onRefresh() - 子类扩展(Spring Boot 在此创建内嵌服务器)
// 10. registerListeners() - 注册监听器
// 11. finishBeanFactoryInitialization() - 实例化所有非懒加载单例 Bean
// 12. finishRefresh() - 完成刷新,发布 ContextRefreshedEvent
}
2.5 启动事件顺序
ApplicationStartingEvent → 应用开始启动
ApplicationEnvironmentPreparedEvent → Environment 准备好
ApplicationContextInitializedEvent → Context 创建并初始化
ApplicationPreparedEvent → Context 加载完成但未刷新
ContextRefreshedEvent → Context 刷新完成
ApplicationStartedEvent → 应用已启动,Runner 未执行
AvailabilityChangeEvent(LIVENESS) → 存活状态变更
ApplicationReadyEvent → 应用就绪,可接收请求
AvailabilityChangeEvent(READINESS)→ 就绪状态变更
三、自动配置原理
3.1 @SpringBootApplication 拆解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 本质是 @Configuration
@EnableAutoConfiguration // 核心:开启自动配置
@ComponentScan // 扫描当前包及子包
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
}
3.2 @EnableAutoConfiguration 机制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AutoConfigurationPackage // 注册主包路径
@Import(AutoConfigurationImportSelector.class) // 导入自动配置选择器
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
3.3 AutoConfigurationImportSelector 核心逻辑
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public AutoConfigurationEntry getAutoConfigurationEntry(
AnnotationMetadata annotationMetadata) {
// 1. 获取 @EnableAutoConfiguration 注解属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 2. 获取所有候选自动配置类
List<String> configurations = getCandidateConfigurations(
annotationMetadata, attributes);
// 3. 去重
configurations = removeDuplicates(configurations);
// 4. 获取排除项(exclude)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 5. 校验排除项合法性
checkExcludedClasses(configurations, exclusions);
// 6. 移除被排除的
configurations.removeAll(exclusions);
// 7. 过滤:通过 AutoConfigurationImportFilter(基于条件)
configurations = getConfigurationClassFilter()
.filter(configurations);
// 8. 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
// Spring Boot 2.x: 从 spring.factories 加载
// Spring Boot 3.x: 从 AutoConfiguration.imports 加载
List<String> configurations = ImportCandidates.load(
AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found");
return configurations;
}
}
3.4 自动配置类示例(DataSourceAutoConfiguration)
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ Hikari.class, Tomcat.class, Dbcp2.class,
OracleUcp.class, Simple.class })
protected static class PooledDataSourceConfiguration {
}
}
四、spring.factories vs AutoConfiguration.imports
4.1 Spring Boot 2.x:spring.factories
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration,\
com.example.AnotherAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
com.example.MyInitializer
org.springframework.context.ApplicationListener=\
com.example.MyListener
问题:
- 所有扩展点混在一个文件中,职责不清
- 文件过大时加载性能差
- 无法利用 IDE 的编译期检查
4.2 Spring Boot 3.x:AutoConfiguration.imports
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
com.example.AnotherAutoConfiguration
优势:
- 每行一个类名,简洁清晰
- 专用于自动配置,与其他扩展点分离
- 支持 IDE 导航和重构
- 可配合
@AutoConfiguration注解使用
4.3 @AutoConfiguration 注解(Spring Boot 3)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {
@AliasFor(annotation = Configuration.class)
String value() default "";
Class<?>[] before() default {};
String[] beforeName() default {};
Class<?>[] after() default {};
String[] afterName() default {};
}
4.4 兼容性说明
Spring Boot 2.7:同时支持两种方式(过渡期)
Spring Boot 3.0:移除 spring.factories 对自动配置的支持
其他扩展点(Initializer/Listener)仍用 spring.factories
或迁移到 META-INF/spring/ 下对应文件
五、条件注解详解
5.1 条件注解体系
// 所有条件注解的元注解
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass { ... }
5.2 常用条件注解
// 类路径条件
@ConditionalOnClass(name = "javax.sql.DataSource") // 类存在
@ConditionalOnMissingClass("javax.sql.DataSource") // 类不存在
// Bean 条件
@ConditionalOnBean(DataSource.class) // Bean 存在
@ConditionalOnMissingBean(DataSource.class) // Bean 不存在
@ConditionalOnSingleCandidate(DataSource.class) // 唯一候选
// 属性条件
@ConditionalOnProperty(
prefix = "spring.cache",
name = "type",
havingValue = "redis",
matchIfMissing = false // 属性不存在时是否匹配
)
// 资源条件
@ConditionalOnResource(resources = "classpath:custom.properties")
// Web 应用条件
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnNotWebApplication
// 表达式条件
@ConditionalOnExpression("${server.enabled:true} && ${app.debug:false}")
// Java 版本条件(Spring Boot 3.1+)
@ConditionalOnJava(JavaVersion.SEVENTEEN)
// 云环境条件
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
5.3 条件评估时机
ConfigurationPhase 枚举:
├── PARSE_CONFIGURATION → 解析配置类时评估(@ConditionalOnClass)
└── REGISTER_BEAN → 注册 Bean 时评估(@ConditionalOnBean)
注意:@ConditionalOnBean 依赖于 Bean 注册顺序,
自动配置类默认在用户配置之后处理,因此可以检测到用户定义的 Bean。
5.4 自定义 Condition
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name", "");
return os.toLowerCase().contains("linux");
}
}
// 使用
@Configuration
@Conditional(OnLinuxCondition.class)
public class LinuxSpecificConfiguration {
// 仅在 Linux 环境生效
}
5.5 条件评估报告
# application.yml
debug: true # 或 --debug 启动参数
启动时输出:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:(匹配成功的自动配置)
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource'
- @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory)
Negative matches:(未匹配的自动配置)
-----------------
MongoAutoConfiguration:
- @ConditionalOnClass did not find required class 'com.mongodb.client.MongoClient'
六、Starter 机制与自定义 Starter
6.1 Starter 的本质
Starter 是一个依赖聚合 + 自动配置的打包方式:
spring-boot-starter-web
├── spring-boot-starter(核心)
├── spring-boot-starter-json(Jackson)
├── spring-boot-starter-tomcat(内嵌Tomcat)
├── spring-web
└── spring-webmvc
6.2 自定义 Starter 项目结构
my-spring-boot-starter/
├── my-spring-boot-autoconfigure/ # 自动配置模块
│ ├── src/main/java/
│ │ └── com/example/my/
│ │ ├── MyAutoConfiguration.java
│ │ ├── MyProperties.java
│ │ └── MyService.java
│ └── src/main/resources/
│ └── META-INF/spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│
└── my-spring-boot-starter/ # Starter 模块(仅 pom)
└── pom.xml(依赖 autoconfigure + 第三方库)
6.3 编写配置属性类
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
/** 是否启用 */
private boolean enabled = true;
/** 服务名称 */
private String name = "default";
/** 超时时间(毫秒) */
private Duration timeout = Duration.ofSeconds(30);
/** 重试配置 */
private Retry retry = new Retry();
// getter/setter 省略
public static class Retry {
private int maxAttempts = 3;
private Duration backoff = Duration.ofSeconds(1);
// getter/setter 省略
}
}
6.4 编写核心服务
public class MyService {
private final MyProperties properties;
public MyService(MyProperties properties) {
this.properties = properties;
}
public String execute(String input) {
if (!properties.isEnabled()) {
throw new IllegalStateException("MyService is disabled");
}
// 业务逻辑
return properties.getName() + ": " + input;
}
}
6.5 编写自动配置类
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnProperty(prefix = "my.service", name = "enabled",
havingValue = "true", matchIfMissing = true)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "my.service", name = "health-indicator",
havingValue = "true")
public MyServiceHealthIndicator myServiceHealthIndicator(MyService service) {
return new MyServiceHealthIndicator(service);
}
}
6.6 注册自动配置
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.my.MyAutoConfiguration
6.7 Starter 模块 pom.xml
<project>
<groupId>com.example</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 第三方依赖 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-core-library</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>
6.8 使用自定义 Starter
# application.yml
my:
service:
enabled: true
name: "MyApp"
timeout: 60s
retry:
max-attempts: 5
backoff: 2s
@Service
public class BusinessService {
@Autowired
private MyService myService; // 自动注入
public void doWork() {
String result = myService.execute("task");
}
}
七、配置管理
7.1 application.yml 基本语法
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: my-service
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: ${DB_PASSWORD:default} # 环境变量,带默认值
hikari:
maximum-pool-size: 20
minimum-idle: 5
logging:
level:
root: INFO
com.example: DEBUG
7.2 application.properties 等价写法
server.port=8080
server.servlet.context-path=/api
spring.application.name=my-service
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=${DB_PASSWORD:default}
spring.datasource.hikari.maximum-pool-size=20
logging.level.root=INFO
logging.level.com.example=DEBUG
7.3 Profile 多环境配置
# application.yml(公共配置)
spring:
profiles:
active: dev # 激活 dev 环境
group:
production:
- proddb
- promq
---
# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
---
# application-prod.yml
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-host:3306/prod_db
7.4 激活 Profile 的方式
# 1. 配置文件
spring.profiles.active=prod
# 2. 命令行参数
java -jar app.jar --spring.profiles.active=prod
# 3. 环境变量
export SPRING_PROFILES_ACTIVE=prod
# 4. JVM 参数
java -Dspring.profiles.active=prod -jar app.jar
7.5 @Profile 注解
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Service
@Profile({"prod", "staging"})
public class ProductionService implements MyService {
// 仅在 prod/staging 环境生效
}
7.6 类型安全配置绑定
@Component
@ConfigurationProperties(prefix = "app.mail")
@Validated // 支持 JSR-303 校验
public class MailProperties {
@NotBlank
private String host;
@Min(1) @Max(65535)
private int port = 25;
private String username;
private String password;
private boolean sslEnabled = false;
// getter/setter
}
app:
mail:
host: smtp.example.com
port: 587
username: user
password: pass
ssl-enabled: true
7.7 @Value vs @ConfigurationProperties
| 特性 | @Value | @ConfigurationProperties |
|---|---|---|
| 松散绑定 | 不支持 | 支持(my-name → myName) |
| SpEL | 支持 | 不支持 |
| 校验 | 不支持 | 支持 @Validated |
| 批量绑定 | 不支持 | 支持 |
| 元数据提示 | 无 | 有(spring-configuration-processor) |
八、配置优先级与外部化配置
8.1 完整优先级(从高到低)
1. 命令行参数 --server.port=9090
2. SPRING_APPLICATION_JSON 环境变量中的 JSON
3. ServletConfig / ServletContext 初始化参数
4. JNDI 属性(java:comp/env)
5. Java 系统属性(-Dserver.port=9090)
6. 操作系统环境变量(SERVER_PORT=9090)
7. random.* 随机值属性源
8. jar 包外 application-{profile}.yml
9. jar 包内 application-{profile}.yml
10. jar 包外 application.yml
11. jar 包内 application.yml
12. @PropertySource 注解引入
13. SpringApplication.setDefaultProperties()
8.2 外部化配置加载
# 指定配置文件位置(替换默认)
java -jar app.jar --spring.config.location=file:/opt/config/
# 追加配置位置(不替换默认)
java -jar app.jar --spring.config.additional-location=file:/opt/config/
# 指定配置文件名
java -jar app.jar --spring.config.name=myapp
# 导入其他配置(Spring Boot 2.4+)
# application.yml
spring:
config:
import:
- optional:classpath:extra.yml
- optional:file:/opt/config/override.yml
- configtree:/etc/config/ # Kubernetes ConfigMap
8.3 配置加密(Jasypt)
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
spring:
datasource:
password: ENC(加密后的密文)
jasypt:
encryptor:
algorithm: PBEWithMD5AndDES
password: ${JASYPT_KEY} # 通过环境变量传入密钥
九、Actuator 监控端点
9.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
9.2 端点配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,env,beans,loggers
# include: "*" # 暴露所有(生产慎用)
base-path: /actuator
endpoint:
health:
show-details: when_authorized # never/when_authorized/always
shutdown:
enabled: false # 默认关闭
server:
port: 9090 # 管理端口独立
health:
db:
enabled: true
redis:
enabled: true
9.3 常用端点
| 端点 | 说明 |
|---|---|
| GET /actuator/health | 健康检查 |
| GET /actuator/info | 应用信息 |
| GET /actuator/metrics | 指标列表 |
| GET /actuator/metrics/{name} | 具体指标 |
| GET /actuator/env | 环境变量 |
| GET /actuator/beans | 所有 Bean |
| GET /actuator/mappings | URL 映射 |
| GET /actuator/configprops | 配置属性 |
| GET /actuator/loggers | 日志级别 |
| POST /actuator/loggers/{name} | 修改日志级别 |
| GET /actuator/threaddump | 线程转储 |
| GET /actuator/heapdump | 堆转储(下载) |
| POST /actuator/shutdown | 优雅停机 |
9.4 自定义 HealthIndicator
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean serviceUp = checkExternalService();
if (serviceUp) {
return Health.up()
.withDetail("service", "external-api")
.withDetail("latency", "50ms")
.build();
}
return Health.down()
.withDetail("service", "external-api")
.withDetail("error", "Connection timeout")
.build();
}
private boolean checkExternalService() {
// 检查外部服务可用性
return true;
}
}
9.5 自定义 Endpoint
@Endpoint(id = "custom")
@Component
public class CustomEndpoint {
@ReadOperation // GET /actuator/custom
public Map<String, Object> info() {
Map<String, Object> map = new HashMap<>();
map.put("version", "1.0.0");
map.put("timestamp", System.currentTimeMillis());
return map;
}
@WriteOperation // POST /actuator/custom
public String update(@Selector String key, String value) {
// 执行写操作
return "Updated " + key + " = " + value;
}
@DeleteOperation // DELETE /actuator/custom/{key}
public String delete(@Selector String key) {
return "Deleted " + key;
}
}
9.6 集成 Prometheus
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: prometheus
访问 /actuator/prometheus 获取 Prometheus 格式指标数据。
十、内嵌 Web 服务器
10.1 默认 Tomcat 配置
server:
port: 8080
servlet:
context-path: /
tomcat:
threads:
max: 200
min-spare: 10
max-connections: 8192
accept-count: 100
connection-timeout: 20000
compression:
enabled: true
mime-types: text/html,application/json
ssl:
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
10.2 切换到 Jetty
<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>
10.3 切换到 Undertow
<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>
10.4 编程式自定义
@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setPort(9090);
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setMaxThreads(300);
protocol.setMinSpareThreads(20);
});
factory.addContextCustomizers(context -> {
context.setSessionTimeout(30, TimeUnit.MINUTES);
});
}
}
10.5 WAR 部署(外置服务器)
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
<packaging>war</packaging>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
十一、日志管理
11.1 默认日志配置
Spring Boot 默认使用 SLF4J + Logback:
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: WARN
file:
name: logs/app.log
max-size: 100MB
max-history: 30
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
total-size-cap: 3GB
11.2 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="APP_NAME"
source="spring.application.name" defaultValue="app"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 按 Profile 区分 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>
11.3 切换到 Log4j2
<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>
11.4 日志分组
logging:
group:
mygroup: com.example.service, com.example.repository
level:
mygroup: DEBUG # 一次设置整组级别
十二、异常处理
12.1 默认错误处理机制
Spring Boot 默认注册 BasicErrorController,处理 /error 请求:
// 默认 JSON 错误响应
{
"timestamp": "2024-01-01T10:00:00.000+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/users"
}
12.2 全局异常处理(推荐)
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException ex) {
ErrorResponse error = new ErrorResponse(
ex.getCode(),
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(ex.getHttpStatus()).body(error);
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining("; "));
ErrorResponse error = new ErrorResponse(400, message, LocalDateTime.now());
return ResponseEntity.badRequest().body(error);
}
// 处理资源未找到
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(404, ex.getMessage(), LocalDateTime.now());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
// 兜底处理
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
log.error("Unhandled exception", ex);
ErrorResponse error = new ErrorResponse(
500, "Internal Server Error", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
12.3 统一错误响应体
@Data
@AllArgsConstructor
public class ErrorResponse {
private int code;
private String message;
private LocalDateTime timestamp;
private String traceId; // 链路追踪ID
}
// 自定义业务异常
public class BusinessException extends RuntimeException {
private final int code;
private final HttpStatus httpStatus;
public BusinessException(int code, String message, HttpStatus httpStatus) {
super(message);
this.code = code;
this.httpStatus = httpStatus;
}
}
12.4 ProblemDetail(Spring Boot 3 / RFC 7807)
# application.yml
spring:
mvc:
problemdetails:
enabled: true
@RestControllerAdvice
public class ProblemDetailExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
problem.setTitle("Resource Not Found");
problem.setProperty("timestamp", Instant.now());
problem.setProperty("resourceId", ex.getResourceId());
return problem;
}
}
响应格式:
{
"type": "about:blank",
"title": "Resource Not Found",
"status": 404,
"detail": "User with id 123 not found",
"instance": "/api/users/123",
"timestamp": "2024-01-01T10:00:00Z",
"resourceId": "123"
}
十三、Spring Boot 3 新特性
13.1 Jakarta EE 迁移
// Spring Boot 2.x
import javax.servlet.http.HttpServletRequest;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
// Spring Boot 3.x
import jakarta.servlet.http.HttpServletRequest;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;
13.2 GraalVM Native Image
<!-- pom.xml -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
# 构建 Native Image
mvn -Pnative native:compile
# 或使用 Buildpacks
mvn -Pnative spring-boot:build-image
// AOT 处理:注册反射信息
@RegisterReflectionForBinding(UserDTO.class)
@Configuration
public class NativeConfig {
}
// RuntimeHints(编程式)
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("my-config/*");
hints.reflection().registerType(MyClass.class,
MemberCategory.INVOKE_PUBLIC_METHODS);
}
}
@ImportRuntimeHints(MyRuntimeHints.class)
@Configuration
public class AppConfig {
}
13.3 HTTP 接口客户端(声明式)
// 定义接口
@HttpExchange("/users")
public interface UserClient {
@GetExchange
List<User> findAll();
@GetExchange("/{id}")
User findById(@PathVariable Long id);
@PostExchange
User create(@RequestBody UserCreateRequest request);
@DeleteExchange("/{id}")
void delete(@PathVariable Long id);
}
// 配置 Bean
@Configuration
public class HttpClientConfig {
@Bean
public UserClient userClient() {
RestClient restClient = RestClient.builder()
.baseUrl("http://localhost:8080/api")
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(restClient))
.build();
return factory.createClient(UserClient.class);
}
}
13.4 虚拟线程支持(Spring Boot 3.2+)
spring:
threads:
virtual:
enabled: true # Tomcat 使用虚拟线程处理请求
// 编程式使用虚拟线程
@Bean
public TaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(
Executors.newVirtualThreadPerTaskExecutor());
}
13.5 Docker Compose 支持(Spring Boot 3.1+)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
# compose.yaml(项目根目录)
services:
postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
redis:
image: redis:7
ports:
- "6379:6379"
启动时自动连接 Docker Compose 中的服务,无需手动配置连接信息。
13.6 可观测性(Observability)
@RestController
public class OrderController {
private final ObservationRegistry observationRegistry;
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return Observation.createNotStarted("get-order", observationRegistry)
.lowCardinalityKeyValue("order.type", "standard")
.highCardinalityKeyValue("order.id", id.toString())
.observe(() -> orderService.findById(id));
}
}
十四、自定义 AutoConfiguration 实战
14.1 实战场景:自定义限流 Starter
需求: 提供基于注解的接口限流能力,支持配置限流策略。
14.2 项目结构
ratelimit-spring-boot-starter/
├── ratelimit-spring-boot-autoconfigure/
│ ├── src/main/java/com/example/ratelimit/
│ │ ├── RateLimitAutoConfiguration.java
│ │ ├── RateLimitProperties.java
│ │ ├── RateLimitAspect.java
│ │ ├── RateLimiter.java
│ │ ├── SlidingWindowRateLimiter.java
│ │ └── RateLimitExceededException.java
│ └── src/main/resources/META-INF/spring/
│ └── ...AutoConfiguration.imports
└── ratelimit-spring-boot-starter/
└── pom.xml
14.3 注解定义
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/** 限流 key 前缀 */
String key() default "";
/** 时间窗口内最大请求数 */
int permits() default 100;
/** 时间窗口(秒) */
int window() default 60;
/** 限流维度:IP / USER / GLOBAL */
RateLimitType type() default RateLimitType.IP;
}
public enum RateLimitType {
IP, USER, GLOBAL
}
14.4 限流器实现
public interface RateLimiter {
boolean tryAcquire(String key, int permits, int windowSeconds);
}
public class SlidingWindowRateLimiter implements RateLimiter {
private final ConcurrentHashMap<String, Deque<Long>> windows =
new ConcurrentHashMap<>();
@Override
public boolean tryAcquire(String key, int permits, int windowSeconds) {
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000L;
Deque<Long> timestamps = windows.computeIfAbsent(key,
k -> new ConcurrentLinkedDeque<>());
// 移除窗口外的记录
while (!timestamps.isEmpty() && timestamps.peekFirst() < windowStart) {
timestamps.pollFirst();
}
if (timestamps.size() < permits) {
timestamps.addLast(now);
return true;
}
return false;
}
}
14.5 AOP 切面
@Aspect
public class RateLimitAspect {
private final RateLimiter rateLimiter;
private final RateLimitProperties properties;
public RateLimitAspect(RateLimiter rateLimiter, RateLimitProperties properties) {
this.rateLimiter = rateLimiter;
this.properties = properties;
}
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
String key = buildKey(point, rateLimit);
int permits = rateLimit.permits() > 0 ?
rateLimit.permits() : properties.getDefaultPermits();
int window = rateLimit.window() > 0 ?
rateLimit.window() : properties.getDefaultWindow();
if (!rateLimiter.tryAcquire(key, permits, window)) {
throw new RateLimitExceededException(
"Rate limit exceeded for key: " + key);
}
return point.proceed();
}
private String buildKey(ProceedingJoinPoint point, RateLimit rateLimit) {
String prefix = rateLimit.key().isEmpty() ?
point.getSignature().toShortString() : rateLimit.key();
return switch (rateLimit.type()) {
case IP -> prefix + ":" + getClientIp();
case USER -> prefix + ":" + getCurrentUser();
case GLOBAL -> prefix + ":global";
};
}
}
14.6 自动配置类
@AutoConfiguration
@ConditionalOnClass(Aspect.class)
@EnableConfigurationProperties(RateLimitProperties.class)
@ConditionalOnProperty(prefix = "ratelimit", name = "enabled",
havingValue = "true", matchIfMissing = true)
public class RateLimitAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RateLimiter.class)
public RateLimiter rateLimiter(RateLimitProperties properties) {
return new SlidingWindowRateLimiter();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(RateLimiter.class)
public RateLimitAspect rateLimitAspect(RateLimiter rateLimiter,
RateLimitProperties properties) {
return new RateLimitAspect(rateLimiter, properties);
}
@Bean
@ConditionalOnMissingBean
public RateLimitExceptionHandler rateLimitExceptionHandler() {
return new RateLimitExceptionHandler();
}
}
14.7 使用方式
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
@RateLimit(permits = 50, window = 60, type = RateLimitType.IP)
public List<Order> listOrders() {
return orderService.findAll();
}
@PostMapping
@RateLimit(key = "create-order", permits = 10, window = 60, type = RateLimitType.USER)
public Order createOrder(@RequestBody OrderRequest request) {
return orderService.create(request);
}
}
# application.yml
ratelimit:
enabled: true
default-permits: 100
default-window: 60
十五、最佳实践
15.1 项目结构规范
com.example.myapp/
├── Application.java # 启动类
├── config/ # 配置类
│ ├── WebConfig.java
│ ├── SecurityConfig.java
│ └── CacheConfig.java
├── controller/ # 控制层
├── service/ # 业务层
│ └── impl/
├── repository/ # 数据层
├── model/ # 实体/DTO
│ ├── entity/
│ ├── dto/
│ └── vo/
├── common/ # 公共组件
│ ├── exception/
│ ├── result/
│ └── util/
└── infrastructure/ # 基础设施
├── mq/
├── cache/
└── external/
15.2 配置管理最佳实践
// 1. 使用 @ConfigurationProperties 而非大量 @Value
// 2. 敏感配置外部化
// 3. 提供合理默认值
// 4. 使用 spring-configuration-processor 生成元数据
@ConfigurationProperties(prefix = "app.payment")
public class PaymentProperties {
private String apiKey; // 从环境变量注入
private Duration timeout = Duration.ofSeconds(30); // 合理默认值
private RetryConfig retry = new RetryConfig(); // 嵌套配置
public record RetryConfig(int maxAttempts, Duration backoff) {
public RetryConfig() { this(3, Duration.ofSeconds(1)); }
}
}
15.3 优雅停机
server:
shutdown: graceful # 等待请求处理完毕
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最长等待时间
15.4 健康检查与探针(Kubernetes)
management:
endpoint:
health:
probes:
enabled: true # 启用 /actuator/health/liveness 和 /readiness
group:
readiness:
include: db, redis, diskSpace
# Kubernetes Deployment
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
15.5 测试最佳实践
// 集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class OrderControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private OrderService orderService;
@Test
void shouldCreateOrder() throws Exception {
when(orderService.create(any())).thenReturn(new Order(1L, "test"));
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"item\":\"test\",\"quantity\":1}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1));
}
}
// 切片测试(仅加载 Web 层)
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private OrderService orderService;
@Test
void shouldReturn404WhenNotFound() throws Exception {
when(orderService.findById(999L))
.thenThrow(new ResourceNotFoundException("Order 999 not found"));
mockMvc.perform(get("/api/orders/999"))
.andExpect(status().isNotFound());
}
}
// Testcontainers 集成
@SpringBootTest
@Testcontainers
class RepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private OrderRepository orderRepository;
@Test
void shouldSaveAndFindOrder() {
Order saved = orderRepository.save(new Order(null, "item1"));
assertThat(orderRepository.findById(saved.getId())).isPresent();
}
}
15.6 容器化部署
# 多阶段构建
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
# 非 root 用户
RUN useradd -r -s /bin/false appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
或使用 Buildpacks(无需 Dockerfile):
mvn spring-boot:build-image -Dspring-boot.build-image.imageName=myapp:1.0
15.7 性能优化清单
# 1. 懒加载(开发环境加速启动)
spring:
main:
lazy-initialization: true # 仅开发环境!
# 2. 减少不必要的自动配置
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
- org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
# 3. JVM 参数优化
# -XX:+UseZGC -XX:+UseStringDeduplication
# -Dspring.jmx.enabled=false(关闭 JMX)
# -Dspring.config.location=file:/opt/config/(减少配置扫描)
15.8 安全建议
# Actuator 安全配置
management:
endpoints:
web:
exposure:
include: health,info # 最小暴露
endpoint:
health:
show-details: when_authorized
# 配合 Spring Security
spring:
security:
user:
name: ${ADMIN_USER}
password: ${ADMIN_PASSWORD}
@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/actuator/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.anyRequest().hasRole("ADMIN"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
总结
Spring Boot 的核心价值在于通过自动配置和 Starter 机制大幅降低了 Spring 应用的搭建成本。理解其底层原理(条件装配、ImportSelector、事件机制)有助于:
- 排查问题:通过
--debug查看条件评估报告,定位自动配置失败原因 - 自定义扩展:编写自己的 Starter 和 AutoConfiguration
- 性能调优:排除无用自动配置、合理使用懒加载
- 版本升级:理解 2.x → 3.x 的破坏性变更(Jakarta EE、配置文件格式)
掌握启动流程和自动配置原理,是从"会用"到"精通" Spring Boot 的关键一步。

转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_49076592/article/details/163504512



