首页 > 数据库 > 国内数据库 > Spring之SpringBoot
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 启动参数
AI写代码
yaml
1
2
启动时输出:
============================
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
AI写代码
text
1
2
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"]
dockerfile
或使用 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博主「萧瑟余晖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_49076592/article/details/163504512
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/29568843/viewspace-3183014/ ,如需转载,请注明出处,否则将追究法律责任。