Spring Boot相关
Spring Boot 框架 完整系统学习大纲
一、Spring Boot 概述
1.1 Spring Boot 简介
Spring Boot 是一个基于 Spring 框架的快速开发框架,旨在简化 Spring 应用的初始搭建和开发过程。它通过提供默认配置、开箱即用的特性,让开发者能够快速创建独立运行的、生产级别的 Spring 应用程序,而无需进行繁琐的 XML 配置。
1.2 Spring Boot 核心特性
- 自动配置:根据项目依赖自动配置 Spring 应用
- 起步依赖:提供预配置的依赖管理,简化构建配置
- 内嵌容器:内置 Tomcat、Jetty 或 Undertow 服务器
- 生产就绪:提供健康检查、指标监控等生产级功能
- 无需 XML 配置:基于 Java 配置和约定优于配置原则
- 命令行接口:提供 Spring Boot CLI 工具
- 丰富的功能:集成数据访问、安全、消息、批处理等
1.3 Spring Boot 与 Spring 的关系
- Spring Framework:基础框架,提供核心功能(IoC、AOP、事务等)
- Spring Boot:基于 Spring Framework 的快速开发框架
- 关系:Spring Boot 不是 Spring 的替代品,而是简化 Spring 应用开发的工具
| 特性 | Spring Framework | Spring Boot |
|---|---|---|
| 配置方式 | XML 或 Java Config | 自动配置,最小化配置 |
| 服务器 | 需要外部服务器 | 内置服务器 |
| 依赖管理 | 手动管理依赖 | 起步依赖自动管理 |
| 部署 | 需要打包为 WAR 部署 | 可执行 JAR,独立运行 |
| 开发速度 | 较慢,配置繁琐 | 快速,开箱即用 |
| 学习曲线 | 较陡峭 | 相对平缓 |
1.4 Spring Boot 版本发展
- 2013年:Spring Boot 开始开发
- 2014年4月:Spring Boot 1.0 发布
- 2018年3月:Spring Boot 2.0 发布(重大更新)
- 2022年11月:Spring Boot 3.0 发布(基于 Spring 6,Java 17+)
- 最新版本:Spring Boot 3.x 系列,支持 Java 17+ 和 Jakarta EE 9+
1.5 Spring Boot 生态系统
- 核心框架:Spring Boot
- 数据访问:Spring Data JPA、MyBatis、JDBC
- 安全控制:Spring Security
- 微服务:Spring Cloud
- 消息队列:Spring AMQP、Spring Kafka
- 批处理:Spring Batch
- 集成工具:Spring Integration
- 测试框架:Spring Boot Test
- 监控管理:Spring Boot Actuator
- 开发工具:Spring Boot DevTools
1.6 Spring Boot 3 新特性
- Java 17+ 支持:最低要求 Java 17
- Jakarta EE 9+:从 javax 迁移到 jakarta 命名空间
- 记录(Records)支持:更好的 DTO 支持
- GraalVM 原生镜像:改进的原生编译支持
- 问题详情(ProblemDetail):标准化的错误响应
- HTTP 接口客户端:声明式 HTTP 客户端
- 可观测性:集成了 Micrometer 和 Micrometer Tracing
- 改进的 AOT 处理:更好的提前编译支持
二、快速开始
2.1 系统要求
- Java:JDK 17 或更高版本(Spring Boot 3.x)
- 构建工具:Maven 3.5+ 或 Gradle 7.x+
- 开发工具:IDEA、Eclipse、VS Code
- Spring Boot 版本:3.0.0+
2.2 创建第一个 Spring Boot 应用
-
2.2.1 使用 Spring Initializr Spring Initializr 是创建 Spring Boot 项目的最简单方式,提供 Web 界面和多种构建工具支持。
创建步骤:
- 选择构建工具(Maven/Gradle)
- 选择语言(Java/Kotlin/Groovy)
- 选择 Spring Boot 版本
- 填写项目元数据
- 添加依赖
- 生成项目
-
2.2.2 使用命令行
# 使用 curl curl https://start.spring.io/starter.zip -d dependencies=web,data-jpa \ -d type=maven-project -d javaVersion=17 -d groupId=com.example \ -d artifactId=demo -o demo.zip # 使用 HTTPie http https://start.spring.io/starter.zip dependencies==web,data-jpa \ type==maven-project javaVersion==17 groupId==com.example \ artifactId==demo -o demo.zip -
2.2.3 手动创建项目结构
demo/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── demo/ │ │ │ └── DemoApplication.java │ │ └── resources/ │ │ ├── application.properties │ │ └── static/ │ │ └── templates/ │ └── test/ │ └── java/ │ └── com/ │ └── example/ │ └── demo/ │ └── DemoApplicationTests.java ├── pom.xml └── README.md
2.3 第一个 Spring Boot 应用代码
// DemoApplication.java
package com.example.demo;
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
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
# application.properties
server.port=8080
spring.application.name=demo
2.4 运行应用
2.4.1 使用 IDE 运行
- 在 IDEA 中直接运行 main 方法
- 在 Eclipse 中作为 Spring Boot 应用运行
2.4.2 使用命令行运行
# 使用 Maven
mvn spring-boot:run
# 使用 Gradle
gradle bootRun
# 打包后运行
mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar
2.5 访问应用
打开浏览器,访问 http://localhost:8080/hello,将会看到 “Hello, Spring Boot!” 响应。
三、核心概念
3.1 自动配置(Auto-configuration)
自动配置是 Spring Boot 的核心特性之一,它根据项目类路径中的依赖自动配置 Spring 应用程序。其工作原理如下:
- 条件注解:基于条件判断是否启用配置
- 自动配置类:在
spring-boot-autoconfigure包中定义 - spring.factories:列出所有自动配置类
- @EnableAutoConfiguration:启用自动配置
条件注解示例:
@Configuration
@ConditionalOnClass(DataSource.class) // 当 DataSource 类存在时
@ConditionalOnProperty(name = "spring.datasource.url") // 当配置了数据源 URL 时
@AutoConfigureAfter(DataSourceAutoConfiguration.class) // 在数据源自动配置后执行
public class MyAutoConfiguration {
// 自动配置 Bean
}
常用条件注解:
| 注解 | 描述 |
|---|---|
| @ConditionalOnClass | 当类路径中存在指定类时生效 |
| @ConditionalOnMissingClass | 当类路径中不存在指定类时生效 |
| @ConditionalOnBean | 当容器中存在指定 Bean 时生效 |
| @ConditionalOnMissingBean | 当容器中不存在指定 Bean 时生效 |
| @ConditionalOnProperty | 当配置属性满足条件时生效 |
| @ConditionalOnResource | 当资源文件存在时生效 |
| @ConditionalOnWebApplication | 当是 Web 应用时生效 |
| @ConditionalOnNotWebApplication | 当不是 Web 应用时生效 |
| @ConditionalOnExpression | 当 SpEL 表达式为 true 时生效 |
| @ConditionalOnJava | 当 Java 版本满足条件时生效 |
3.2 起步依赖(Starters)
起步依赖是预定义的依赖描述符,它包含了一组相关的依赖,简化了项目的依赖管理。
常用起步依赖:
| Starter | 描述 |
|---|---|
| spring-boot-starter-web | Web 应用开发 |
| spring-boot-starter-data-jpa | JPA 数据访问 |
| spring-boot-starter-security | 安全框架 |
| spring-boot-starter-test | 测试框架 |
| spring-boot-starter-actuator | 应用监控 |
| spring-boot-starter-thymeleaf | Thymeleaf 模板引擎 |
| spring-boot-starter-aop | AOP 支持 |
| spring-boot-starter-cache | 缓存支持 |
| spring-boot-starter-validation | 数据校验 |
| spring-boot-starter-amqp | RabbitMQ 消息队列 |
起步依赖的优势:
- 提供功能相关的依赖集合
- 解决依赖版本冲突
- 自动传递依赖管理
- 简化构建配置
3.3 外部化配置
Spring Boot 支持多种外部化配置方式,优先级从高到低:
- 命令行参数:
java -jar app.jar --server.port=8081 - 系统属性:
-Dspring.profiles.active=dev - 环境变量:
SPRING_PROFILES_ACTIVE=dev - 配置文件:
application-{profile}.properties/yml - 默认配置:
application.properties/yml
配置文件示例:
# application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
myapp:
feature:
enabled: true
max-size: 100
@ConfigurationProperties 使用:
@Component
@ConfigurationProperties(prefix = "myapp.feature")
public class FeatureProperties {
private boolean enabled = false;
private int maxSize = 50;
// getter 和 setter
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public int getMaxSize() { return maxSize; }
public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
}
3.4 启动过程分析
Spring Boot 应用的启动过程可以分为以下几个阶段:
- 启动类执行:调用
SpringApplication.run() - 创建应用上下文:根据应用类型创建合适的 ApplicationContext
- 准备环境:加载配置文件、命令行参数、环境变量
- 创建 Bean 工厂:创建 BeanDefinitionRegistry
- 执行 Runner:执行 ApplicationRunner 和 CommandLineRunner
- 启动内嵌服务器:启动 Tomcat/Jetty/Undertow
- 发布事件:发布 ApplicationReadyEvent 事件
启动过程关键源码:
public class SpringApplication {
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String... args) {
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
// 1. 创建并启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 创建引导上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 3. 创建并准备应用上下文
ConfigurableApplicationContext context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(context, bootstrapContext, this.applicationArguments);
// 4. 刷新上下文
refreshContext(context);
// 5. 启动完成
afterRefresh(context, this.applicationArguments);
stopWatch.stop();
return context;
}
}
3.5 应用事件
Spring Boot 在启动和关闭过程中会发布一系列事件,开发者可以监听这些事件执行特定操作。
生命周期事件(按发布顺序):
| 事件 | 描述 |
|---|---|
| ApplicationStartingEvent | 应用启动时立即发布 |
| ApplicationEnvironmentPreparedEvent | 环境准备好时发布 |
| ApplicationContextInitializedEvent | 应用上下文初始化完成时发布 |
| ApplicationPreparedEvent | 应用准备完成,Bean 定义加载完成时发布 |
| ApplicationStartedEvent | 应用启动完成时发布 |
| ApplicationReadyEvent | 应用完全就绪,可以处理请求时发布 |
| ApplicationFailedEvent | 应用启动失败时发布 |
监听事件示例:
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("应用启动完成,可以处理请求");
}
}
// 或使用注解方式
@Component
public class MyApplicationEventListener {
@EventListener
public void handleApplicationReady(ApplicationReadyEvent event) {
System.out.println("应用启动完成");
}
}
四、核心配置
4.1 配置文件
Spring Boot 支持多种配置文件格式,主要有 properties 和 yaml 两种。
配置文件对比:
| 特性 | Properties 格式 | YAML 格式 |
|---|---|---|
| 格式 | 键值对 | 层次结构 |
| 可读性 | 一般 | 较好 |
| 复杂配置 | 繁琐 | 简洁 |
| 多环境 | 需要多个文件 | 支持文档分隔符 |
| 特殊字符 | 需要转义 | 支持多种格式 |
| 数组/列表 | 使用逗号分隔 | 使用短横线表示 |
YAML 配置示例:
# 服务器配置
server:
port: 8080
servlet:
context-path: /api
tomcat:
max-threads: 200
connection-timeout: 5000
# 数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
maximum-pool-size: 20
minimum-idle: 5
# JPA 配置
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
# 日志配置
logging:
level:
com.example.demo: DEBUG
org.springframework: INFO
file:
name: logs/app.log
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
4.2 多环境配置
Spring Boot 支持多环境配置,通过 profiles 实现。
创建不同环境的配置文件:
resources/
├── application.yml # 主配置文件
├── application-dev.yml # 开发环境
├── application-test.yml # 测试环境
└── application-prod.yml # 生产环境
配置文件内容:
# application.yml
spring:
profiles:
active: @activatedProperties@ # 使用 Maven/Gradle 变量
myapp:
name: demo-app
version: 1.0.0
# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
logging:
level:
root: DEBUG
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/mydb
username: prod_user
password: ${DB_PASSWORD:defaultPassword}
jpa:
hibernate:
ddl-auto: validate
server:
port: 8080
compression:
enabled: true
logging:
file:
name: /var/log/app/app.log
level:
root: WARN
激活 Profile 的方式:
- 配置文件指定:
spring.profiles.active=dev - 命令行参数:
java -jar app.jar --spring.profiles.active=dev - 环境变量:
SPRING_PROFILES_ACTIVE=dev - 虚拟机参数:
-Dspring.profiles.active=dev - 测试注解:
@ActiveProfiles("test")
Profile 分组:
spring:
profiles:
active: production,audit
group:
production: db,mq,cache
development: db,debug
# 当激活 production 时,会自动激活 db、mq、cache
4.3 自定义配置
@Value 注解:
@Component
public class MyComponent {
@Value("${myapp.name:defaultName}")
private String appName;
@Value("${myapp.timeout:30}")
private int timeout;
@Value("#{${myapp.map}}")
private Map<String, String> map;
@Value("classpath:data.json")
private Resource dataFile;
@Value("${server.port}")
private String serverPort;
}
@ConfigurationProperties 注解:
@Component
@ConfigurationProperties(prefix = "myapp.config")
@Validated
public class AppConfig {
@NotNull
@NotEmpty
private String name;
@Min(1)
@Max(100)
private int maxSize;
private List<String> allowedHosts = new ArrayList<>();
private Map<String, String> settings = new HashMap<>();
private NestedConfig nested = new NestedConfig();
// 静态内部类
public static class NestedConfig {
private boolean enabled;
private String path;
// getter 和 setter
}
// getter 和 setter
}
配置属性元数据:
// META-INF/spring-configuration-metadata.json
{
"properties": [
{
"name": "myapp.config.name",
"type": "java.lang.String",
"description": "应用程序名称",
"sourceType": "com.example.demo.config.AppConfig"
},
{
"name": "myapp.config.max-size",
"type": "java.lang.Integer",
"description": "最大尺寸",
"sourceType": "com.example.demo.config.AppConfig",
"defaultValue": 50
}
]
}
4.4 配置加密
Jasypt 加密配置:
-
添加依赖
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency> -
配置加密
# application.yml jasypt: encryptor: password: ${JASYPT_PASSWORD:mySecretKey} # 从环境变量获取密钥 spring: datasource: password: ENC(加密后的密码) -
加密工具类
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; public class JasyptUtil { public static void main(String[] args) { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword("mySecretKey"); String encrypted = encryptor.encrypt("myPassword"); System.out.println("加密: " + encrypted); String decrypted = encryptor.decrypt(encrypted); System.out.println("解密: " + decrypted); } }
4.5 配置验证
配置属性验证:
@Component
@ConfigurationProperties(prefix = "myapp")
@Validated
public class AppProperties {
@NotNull
@NotEmpty
private String name;
@Min(1)
@Max(65535)
private int port;
@Pattern(regexp = "^https?://.*")
private String url;
@Valid
private Security security = new Security();
public static class Security {
@NotEmpty
private String username;
@Size(min = 8, max = 20)
private String password;
// getter 和 setter
}
// getter 和 setter
}
自定义验证器:
@Component
@ConfigurationProperties(prefix = "myapp")
@Validated
public class CustomValidatedProperties {
@ValidEndpoint
private String endpoint;
// 自定义验证注解
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EndpointValidator.class)
public @interface ValidEndpoint {
String message() default "Invalid endpoint";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 验证器实现
public static class EndpointValidator implements ConstraintValidator<ValidEndpoint, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && (value.startsWith("http://") || value.startsWith("https://"));
}
}
}
五、Web 开发
5.1 Spring MVC
Spring MVC 架构:
客户端请求 → DispatcherServlet → HandlerMapping → Controller → 视图解析器 → 视图渲染 → 响应
基本控制器:
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@Valid @RequestBody User user) {
return userService.save(user);
}
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@Valid @RequestBody User user) {
if (!userService.existsById(id)) {
return ResponseEntity.notFound().build();
}
user.setId(id);
return ResponseEntity.ok(userService.save(user));
}
@DeleteMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.deleteById(id);
}
}
请求映射注解:
| 注解 | HTTP 方法 | 描述 |
|---|---|---|
| @GetMapping | GET | 获取资源 |
| @PostMapping | POST | 创建资源 |
| @PutMapping | PUT | 更新资源 |
| @DeleteMapping | DELETE | 删除资源 |
| @PatchMapping | PATCH | 部分更新资源 |
| @RequestMapping | 任意 | 通用映射 |
方法参数注解:
| 注解 | 描述 |
|---|---|
| @PathVariable | 获取 URL 路径变量 |
| @RequestParam | 获取查询参数 |
| @RequestBody | 获取请求体(JSON/XML) |
| @RequestHeader | 获取请求头 |
| @CookieValue | 获取 Cookie 值 |
| @ModelAttribute | 获取模型属性 |
| @RequestPart | 获取文件上传部分 |
| @MatrixVariable | 获取矩阵变量 |
响应处理:
@RestController
public class ResponseController {
// 返回对象(自动转为 JSON)
@GetMapping("/user")
public User getUser() {
return new User(1L, "张三", "zhangsan@example.com");
}
// 返回 ResponseEntity
@GetMapping("/custom")
public ResponseEntity<Map<String, Object>> customResponse() {
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("data", new User(1L, "张三", "zhangsan@example.com"));
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.ok()
.header("X-Custom-Header", "value")
.body(response);
}
// 返回 ResponseEntity 带状态码
@GetMapping("/notfound")
public ResponseEntity<User> notFound() {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("X-Reason", "user-not-found")
.build();
}
// 返回 Stream
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new User(i, "User" + i, "user" + i + "@example.com"));
}
}
5.2 REST API 设计
RESTful API 最佳实践:
- 资源命名
- 使用名词复数形式:
/users而不是/getUsers - 层级关系:
/users/{userId}/orders - 避免动词:使用 HTTP 方法表示操作
- 使用名词复数形式:
- HTTP 方法使用
- GET:获取资源
- POST:创建资源
- PUT:更新整个资源
- PATCH:部分更新资源
- DELETE:删除资源
- 状态码使用
- 200:请求成功
- 201:创建成功
- 204:无内容
- 400:请求错误
- 401:未认证
- 403:无权限
- 404:资源不存在
- 500:服务器错误
- 版本控制
- URL 路径:
/api/v1/users - 请求头:
Accept: application/vnd.company.app-v1+json
- URL 路径:
- 分页、排序、过滤
- 分页:
?page=1&size=20 - 排序:
?sort=name,desc&sort=age,asc - 过滤:
?name=张三&age_gt=18
- 分页:
分页实现:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public ResponseEntity<Page<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id,desc") String[] sort) {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Order.desc("id")));
Page<User> userPage = userService.findAll(pageable);
return ResponseEntity.ok()
.header("X-Total-Count", String.valueOf(userPage.getTotalElements()))
.header("X-Total-Pages", String.valueOf(userPage.getTotalPages()))
.body(userPage);
}
}
全局异常处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex, WebRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.NOT_FOUND.value())
.error("Not Found")
.message(ex.getMessage())
.path(((ServletWebRequest) request).getRequest().getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Validation Failed")
.message("输入验证失败")
.errors(errors)
.build();
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex, WebRequest request) {
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("Internal Server Error")
.message(ex.getMessage())
.path(((ServletWebRequest) request).getRequest().getRequestURI())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
统一响应格式:
public class ApiResponse<T> {
private boolean success;
private String code;
private String message;
private T data;
private Long timestamp;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "200", "成功", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<>(false, code, message, null, System.currentTimeMillis());
}
// 构造方法、getter、setter
}
5.3 静态资源处理
静态资源配置:
# application.yml
spring:
web:
resources:
static-locations: classpath:/static/,classpath:/public/,file:./uploads/
cache:
period: 3600
cachecontrol:
max-age: 3600
must-revalidate: true
mvc:
static-path-pattern: /static/**
自定义资源处理器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 处理 /uploads/** 映射到文件系统
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:uploads/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
// 处理 /static/** 映射到 classpath
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
// 欢迎页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/login").setViewName("login");
}
}
5.4 文件上传下载
文件上传:
@RestController
@RequestMapping("/api/files")
public class FileController {
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "description", required = false) String description) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("文件不能为空");
}
try {
// 保存文件
String filename = System.currentTimeMillis() + "_" + file.getOriginalFilename();
Path filePath = Paths.get("uploads").resolve(filename);
Files.createDirectories(filePath.getParent());
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// 保存文件信息到数据库
FileInfo fileInfo = new FileInfo();
fileInfo.setFilename(filename);
fileInfo.setOriginalFilename(file.getOriginalFilename());
fileInfo.setContentType(file.getContentType());
fileInfo.setSize(file.getSize());
fileInfo.setDescription(description);
fileRepository.save(fileInfo);
return ResponseEntity.ok("文件上传成功: " + filename);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("文件上传失败: " + e.getMessage());
}
}
// 多文件上传
@PostMapping("/upload-multiple")
public ResponseEntity<List<String>> uploadMultipleFiles(
@RequestParam("files") MultipartFile[] files) {
List<String> filenames = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String filename = saveFile(file);
filenames.add(filename);
}
}
return ResponseEntity.ok(filenames);
}
}
文件下载:
@GetMapping("/download/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
Path filePath = Paths.get("uploads").resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
文件配置:
# application.yml
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB
file-size-threshold: 2KB
location: ${java.io.tmpdir}
web:
resources:
static-locations: file:uploads/
5.5 跨域处理
全局跨域配置:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("http://localhost:3000", "https://*.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Authorization", "X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
方法级跨域配置:
@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(
origins = "http://localhost:3000",
methods = {RequestMethod.GET, RequestMethod.POST},
allowedHeaders = "*",
maxAge = 3600
)
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
Filter 方式跨域:
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
response.setHeader("Access-Control-Expose-Headers", "xsrf-token");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
六、数据访问
6.1 Spring Data JPA
JPA 核心概念:
- 实体(Entity):映射数据库表的 Java 类
- 实体管理器(EntityManager):管理实体生命周期的接口
- 持久化上下文(Persistence Context):实体实例的集合
- 仓库(Repository):数据访问的抽象接口
- 查询(Query):通过 JPQL 或原生 SQL 查询数据
实体定义:
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false)
private String password;
@Column(name = "email", nullable = false, unique = true)
@Email
private String email;
@Column(name = "phone")
@Pattern(regexp = "^1[3-9]\\d{9}$")
private String phone;
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 20)
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// 一对一关系
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserProfile profile;
// 一对多关系
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonManagedReference
private List<Order> orders = new ArrayList<>();
// 多对多关系
@ManyToMany
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
// 枚举类型
public enum UserStatus {
ACTIVE, INACTIVE, LOCKED, DELETED
}
}
仓库接口:
@Repository
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User>,
QuerydslPredicateExecutor<User> {
// 方法名查询
List<User> findByUsername(String username);
List<User> findByEmailContaining(String email);
List<User> findByStatusOrderByCreatedAtDesc(UserStatus status);
// 分页查询
Page<User> findByStatus(UserStatus status, Pageable pageable);
// @Query 注解查询
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
// 原生 SQL 查询
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findUsersAfterDate(@Param("date") LocalDateTime date);
// 修改查询
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
@Transactional
int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
// 投影查询
@Query("SELECT u.username as username, u.email as email FROM User u")
List<UserProjection> findUserProjections();
// 实体图
@EntityGraph(attributePaths = {"roles", "profile"})
@Query("SELECT u FROM User u WHERE u.id = :id")
Optional<User> findByIdWithDetails(@Param("id") Long id);
}
服务层实现:
@Service
@Transactional(readOnly = true)
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Page<User> getUsers(UserQuery query, Pageable pageable) {
Specification<User> spec = Specification.where(null);
if (query.getUsername() != null) {
spec = spec.and((root, query, cb) ->
cb.like(root.get("username"), "%" + query.getUsername() + "%"));
}
if (query.getStatus() != null) {
spec = spec.and((root, query, cb) ->
cb.equal(root.get("status"), query.getStatus()));
}
if (query.getStartDate() != null) {
spec = spec.and((root, query, cb) ->
cb.greaterThanOrEqualTo(root.get("createdAt"), query.getStartDate()));
}
return userRepository.findAll(spec, pageable);
}
@Transactional
public User createUser(CreateUserRequest request) {
if (userRepository.existsByUsername(request.getUsername())) {
throw new BusinessException("用户名已存在");
}
if (userRepository.existsByEmail(request.getEmail())) {
throw new BusinessException("邮箱已存在");
}
User user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.email(request.getEmail())
.phone(request.getPhone())
.status(UserStatus.ACTIVE)
.build();
return userRepository.save(user);
}
@Transactional
public void deleteUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在"));
user.setStatus(UserStatus.DELETED);
userRepository.save(user);
}
// 批量操作
@Transactional
public int batchUpdateStatus(List<Long> ids, UserStatus status) {
return userRepository.batchUpdateStatus(ids, status);
}
}
JPA 配置:
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
show-sql: true
open-in-view: false
# 多数据源配置
datasource:
primary:
url: jdbc:mysql://localhost:3306/primary
username: root
password: 123456
secondary:
url: jdbc:mysql://localhost:3306/secondary
username: root
password: 123456
多数据源配置:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource())
.packages("com.example.primary")
.persistenceUnit("primary")
.build();
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
6.2 Spring Data JDBC
JDBC 配置:
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc:
template:
fetch-size: 100
max-rows: 1000
query-timeout: 30
JdbcTemplate 使用:
@Repository
public class UserJdbcRepository {
private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public UserJdbcRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
}
public List<User> findAll() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new UserRowMapper());
}
public Optional<User> findById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
try {
User user = jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
return Optional.ofNullable(user);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}
public int create(User user) {
String sql = "INSERT INTO users(username, email, password) VALUES(?, ?, ?)";
return jdbcTemplate.update(sql,
user.getUsername(),
user.getEmail(),
user.getPassword());
}
public int update(User user) {
String sql = "UPDATE users SET username = ?, email = ?, password = ? WHERE id = ?";
return jdbcTemplate.update(sql,
user.getUsername(),
user.getEmail(),
user.getPassword(),
user.getId());
}
public int delete(Long id) {
String sql = "DELETE FROM users WHERE id = ?";
return jdbcTemplate.update(sql, id);
}
// 批量操作
public int[] batchInsert(List<User> users) {
String sql = "INSERT INTO users(username, email, password) VALUES(?, ?, ?)";
List<Object[]> batchArgs = users.stream()
.map(user -> new Object[]{user.getUsername(), user.getEmail(), user.getPassword()})
.collect(Collectors.toList());
return jdbcTemplate.batchUpdate(sql, batchArgs);
}
// 使用 NamedParameterJdbcTemplate
public List<User> findByStatus(String status) {
String sql = "SELECT * FROM users WHERE status = :status";
Map<String, Object> params = new HashMap<>();
params.put("status", status);
return namedParameterJdbcTemplate.query(sql, params, new UserRowMapper());
}
// 行映射器
private static class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
return user;
}
}
}
事务管理:
@Service
public class OrderService {
private final JdbcTemplate jdbcTemplate;
public OrderService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional
public void placeOrder(Order order) {
// 1. 创建订单
String orderSql = "INSERT INTO orders(user_id, total_amount) VALUES(?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(orderSql, new String[]{"id"});
ps.setLong(1, order.getUserId());
ps.setBigDecimal(2, order.getTotalAmount());
return ps;
}, keyHolder);
Long orderId = keyHolder.getKey().longValue();
// 2. 创建订单项
String itemSql = "INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(?, ?, ?, ?)";
List<Object[]> batchArgs = order.getItems().stream()
.map(item -> new Object[]{orderId, item.getProductId(), item.getQuantity(), item.getPrice()})
.collect(Collectors.toList());
jdbcTemplate.batchUpdate(itemSql, batchArgs);
// 3. 扣减库存
for (OrderItem item : order.getItems()) {
String updateSql = "UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?";
int updated = jdbcTemplate.update(updateSql, item.getQuantity(), item.getProductId(), item.getQuantity());
if (updated == 0) {
throw new InsufficientStockException("库存不足");
}
}
// 4. 扣减用户余额
String balanceSql = "UPDATE users SET balance = balance - ? WHERE id = ? AND balance >= ?";
int updated = jdbcTemplate.update(balanceSql, order.getTotalAmount(), order.getUserId(), order.getTotalAmount());
if (updated == 0) {
throw new InsufficientBalanceException("余额不足");
}
}
}
6.3 MyBatis
MyBatis 配置:
# application.yml
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.model
configuration:
map-underscore-to-camel-case: true
use-generated-keys: true
default-fetch-size: 100
default-statement-timeout: 30
executor-type: simple
Mapper 接口:
@Mapper
public interface UserMapper {
// 注解方式
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);
@Select("SELECT * FROM users WHERE username = #{username}")
User findByUsername(@Param("username") String username);
@Insert("INSERT INTO users(username, email, password) VALUES(#{username}, #{email}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int delete(Long id);
// 动态 SQL
@SelectProvider(type = UserSqlProvider.class, method = "findByCondition")
List<User> findByCondition(UserCondition condition);
// 一对多查询
@Select("SELECT * FROM users WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "orders", column = "id",
many = @Many(select = "com.example.demo.mapper.OrderMapper.findByUserId"))
})
User findByIdWithOrders(Long id);
}
XML 映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<result property="password" column="password"/>
<result property="createdAt" column="created_at"/>
<result property="updatedAt" column="updated_at"/>
</resultMap>
<!-- 基础查询 -->
<select id="findAll" resultMap="userMap">
SELECT * FROM users
</select>
<!-- 条件查询 -->
<select id="findByCondition" resultMap="userMap" parameterType="UserCondition">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="startDate != null">
AND created_at >= #{startDate}
</if>
<if test="endDate != null">
AND created_at <= #{endDate}
</if>
</where>
ORDER BY created_at DESC
</select>
<!-- 分页查询 -->
<select id="findByPage" resultMap="userMap">
SELECT * FROM users
ORDER BY id DESC
LIMIT #{offset}, #{pageSize}
</select>
<!-- 批量插入 -->
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, email, password) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.password})
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdate">
<foreach collection="list" item="user" separator=";">
UPDATE users
SET username = #{user.username},
email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
<!-- 动态更新 -->
<update id="updateSelective" parameterType="User">
UPDATE users
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="password != null">password = #{password},</if>
</set>
WHERE id = #{id}
</update>
<!-- 关联查询 -->
<resultMap id="userWithOrdersMap" type="User" extends="userMap">
<collection property="orders" ofType="Order" columnPrefix="order_">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<result property="totalAmount" column="total_amount"/>
</collection>
</resultMap>
<select id="findUserWithOrders" resultMap="userWithOrdersMap">
SELECT
u.*,
o.id as order_id,
o.order_no as order_order_no,
o.total_amount as order_total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
</mapper>
PageHelper 分页插件:
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public PageInfo<User> getUsers(int pageNum, int pageSize, UserCondition condition) {
// 使用 PageHelper
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.findByCondition(condition);
return new PageInfo<>(users);
}
public PageInfo<User> getUsersWithSort(int pageNum, int pageSize, String orderBy) {
// 排序和分页
PageHelper.startPage(pageNum, pageSize, orderBy);
List<User> users = userMapper.findAll();
return new PageInfo<>(users);
}
}
6.4 事务管理
声明式事务:
@Service
@Transactional
public class OrderService {
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("订单不存在"));
}
@Transactional(rollbackFor = Exception.class)
public Order createOrder(CreateOrderRequest request) {
// 创建订单逻辑
Order order = new Order();
// ... 设置属性
return orderRepository.save(order);
}
@Transactional(rollbackFor = {BusinessException.class, InsufficientStockException.class})
public void processOrder(Long orderId) {
try {
// 处理订单
} catch (Exception e) {
throw new BusinessException("订单处理失败", e);
}
}
// 传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 如果当前没有事务,就新建一个事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 总是新建一个事务
}
@Transactional(propagation = Propagation.NESTED)
public void methodC() {
// 如果当前存在事务,则嵌套事务执行
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodD() {
// 以非事务方式执行
}
@Transactional(propagation = Propagation.NEVER)
public void methodE() {
// 以非事务方式执行,如果当前存在事务则抛出异常
}
}
编程式事务:
@Service
public class OrderService {
private final PlatformTransactionManager transactionManager;
private final TransactionTemplate transactionTemplate;
public OrderService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void processOrder(Long orderId) {
// 使用 TransactionTemplate
transactionTemplate.execute(status -> {
try {
// 业务逻辑
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
public void processWithManualTransaction(Long orderId) {
// 手动管理事务
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
definition.setTimeout(30);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 业务逻辑
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
事务隔离级别:
@Service
public class AccountService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 转账逻辑
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
public BigDecimal getBalance(Long accountId) {
// 查询余额
}
@Transactional(isolation = Isolation.SERIALIZABLE)
public void batchUpdate(List<Account> accounts) {
// 批量更新
}
}
| 隔离级别 | 描述 | 可能的问题 |
|---|---|---|
| READ_UNCOMMITTED | 读取未提交的数据 | 脏读、不可重复读、幻读 |
| READ_COMMITTED | 读取已提交的数据 | 不可重复读、幻读 |
| REPEATABLE_READ | 可重复读 | 幻读 |
| SERIALIZABLE | 串行化 | 无 |
七、安全控制
7.1 Spring Security 进阶配置
安全配置详解: Spring Security 是 Spring 生态中负责安全认证和授权的框架。在 Spring Boot 中,通过自动配置简化了安全设置,但仍需根据业务需求进行定制。
常用安全配置选项:
| 配置项 | 说明 | 示例值 |
|---|---|---|
| 密码编码器 | 密码存储策略 | BCryptPasswordEncoder |
| 会话管理 | 会话创建策略 | stateless(无状态) |
| CSRF 防护 | 跨站请求伪造防护 | 禁用(API场景) |
| CORS 配置 | 跨域资源共享 | 允许特定源 |
| 安全头 | 安全相关HTTP头 | 内容安全策略 |
| 认证提供者 | 认证逻辑 | DaoAuthenticationProvider |
| 访问决策 | 授权决策逻辑 | 基于角色/权限 |
多认证源配置:
@Configuration
@EnableWebSecurity
public class MultiAuthSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/user/**").hasRole("USER")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
)
.rememberMe(remember -> remember
.tokenValiditySeconds(7 * 24 * 60 * 60) // 7天
.key("myRememberKey")
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID")
);
return http.build();
}
// 内存用户配置(示例)
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password("{bcrypt}$2a$10$...")
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password("{bcrypt}$2a$10$...")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
7.2 OAuth2 与单点登录
OAuth2 授权服务器配置:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;
public AuthorizationServerConfig(AuthenticationManager authenticationManager,
DataSource dataSource) {
this.authenticationManager = authenticationManager;
this.dataSource = dataSource;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("mySecretKey");
return converter;
}
}
OAuth2 资源服务器配置:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("api-resource");
}
}
7.3 方法级安全控制
使用注解进行细粒度控制:
@RestController
@RequestMapping("/api/products")
public class ProductController {
@PreAuthorize("hasRole('ADMIN') or hasPermission(#id, 'Product', 'read')")
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
@PreAuthorize("@securityService.canCreateProduct(authentication)")
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.create(product);
}
@PostAuthorize("returnObject.owner == authentication.name")
@GetMapping("/secret/{id}")
public SecretProduct getSecretProduct(@PathVariable Long id) {
return productService.findSecretById(id);
}
@PreFilter("filterObject.owner == authentication.name")
@PostMapping("/batch")
public List<Product> createProducts(@RequestBody List<Product> products) {
return productService.batchCreate(products);
}
@PostFilter("filterObject.price > 100")
@GetMapping("/expensive")
public List<Product> getExpensiveProducts() {
return productService.findAll();
}
@Secured({"ROLE_ADMIN", "ROLE_SUPER_ADMIN"})
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.delete(id);
}
@RolesAllowed({"USER", "ADMIN"})
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
return productService.update(id, product);
}
}
自定义权限评估器:
@Component("securityService")
public class SecurityService {
public boolean canCreateProduct(Authentication authentication) {
// 自定义权限逻辑
return authentication.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("CREATE_PRODUCT"));
}
public boolean isProductOwner(Long productId, String username) {
// 检查用户是否是产品所有者
return productService.isOwner(productId, username);
}
}
7.4 安全审计
审计功能配置:
@Configuration
@EnableJpaAuditing
public class AuditConfig {
@Bean
public AuditorAware<String> auditorAware() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.of("system");
}
return Optional.of(authentication.getName());
};
}
}
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
@CreatedBy
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
安全事件监听:
@Component
public class SecurityEventListener {
private static final Logger logger = LoggerFactory.getLogger(SecurityEventListener.class);
@EventListener
public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
logger.info("用户 {} 登录成功", event.getAuthentication().getName());
// 记录登录日志
LoginLog log = new LoginLog();
log.setUsername(event.getAuthentication().getName());
log.setLoginTime(LocalDateTime.now());
log.setStatus("SUCCESS");
loginLogRepository.save(log);
}
@EventListener
public void handleAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
logger.warn("用户 {} 登录失败: {}",
event.getAuthentication().getName(),
event.getException().getMessage());
}
@EventListener
public void handleAuthorizationFailure(AccessDeniedException event) {
logger.warn("授权失败: {}", event.getMessage());
}
}
7.5 安全头配置
安全头配置示例:
@Configuration
public class SecurityHeadersConfig {
@Bean
public SecurityFilterChain securityHeadersFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'")
)
.frameOptions(FrameOptionsConfig::sameOrigin)
.xssProtection(xss -> xss
.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK)
)
.contentTypeOptions(ContentTypeOptionsConfig::deny)
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN)
)
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000) // 1年
)
);
return http.build();
}
}
八、消息队列
8.1 RabbitMQ 集成
RabbitMQ 配置:
# application.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
connection-timeout: 30000
listener:
simple:
prefetch: 10
concurrency: 5
max-concurrency: 10
acknowledge-mode: auto
队列配置:
@Configuration
public class RabbitMQConfig {
// 订单队列
@Bean
public Queue orderQueue() {
return new Queue("order.queue", true); // 持久化
}
// 死信队列
@Bean
public Queue orderDeadLetterQueue() {
return new Queue("order.dead.letter.queue", true);
}
// 交换器
@Bean
public DirectExchange orderExchange() {
return new DirectExchange("order.exchange");
}
// 绑定
@Bean
public Binding orderBinding() {
return BindingBuilder
.bind(orderQueue())
.to(orderExchange())
.with("order.routing.key");
}
// JSON消息转换器
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
消息生产者:
@Service
public class OrderMessageProducer {
private final RabbitTemplate rabbitTemplate;
public OrderMessageProducer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendOrderCreated(Order order) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.routing.key",
order,
message -> {
message.getMessageProperties().setPriority(5);
message.getMessageProperties().setExpiration("60000"); // 60秒过期
return message;
}
);
}
// 发送延迟消息
public void sendDelayedMessage(Order order, long delay) {
rabbitTemplate.convertAndSend(
"delayed.exchange",
"delayed.routing.key",
order,
message -> {
message.getMessageProperties().setDelay(Math.toIntExact(delay));
return message;
}
);
}
// 发送事务消息
@Transactional
public void sendTransactionalMessage(Order order) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.routing.key",
order
);
// 如果后续操作失败,消息会回滚
}
}
消息消费者:
@Component
public class OrderMessageConsumer {
@RabbitListener(queues = "order.queue")
public void processOrder(Order order) {
// 处理订单
orderService.process(order);
}
// 手动确认
@RabbitListener(queues = "order.queue")
public void processOrderManualAck(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
orderService.process(order);
channel.basicAck(tag, false); // 确认消息
} catch (Exception e) {
channel.basicNack(tag, false, true); // 拒绝并重新入队
}
}
// 批量消费
@RabbitListener(queues = "order.queue", containerFactory = "batchFactory")
public void processOrderBatch(List<Message> messages, Channel channel) {
for (Message message : messages) {
Order order = (Order) rabbitTemplate.getMessageConverter().fromMessage(message);
orderService.process(order);
}
}
}
8.2 Kafka 集成
Kafka 配置:
# application.yml
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: order-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: "com.example.demo.model"
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
listener:
ack-mode: batch
Kafka 生产者:
@Service
public class KafkaProducer {
private final KafkaTemplate<String, Object> kafkaTemplate;
public KafkaProducer(KafkaTemplate<String, Object> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendOrderEvent(String topic, OrderEvent event) {
kafkaTemplate.send(topic, event.getOrderId().toString(), event);
}
public void sendWithCallback(String topic, OrderEvent event) {
ListenableFuture<SendResult<String, Object>> future =
kafkaTemplate.send(topic, event.getOrderId().toString(), event);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onSuccess(SendResult<String, Object> result) {
log.info("消息发送成功: {}", result.getRecordMetadata().toString());
}
@Override
public void onFailure(Throwable ex) {
log.error("消息发送失败: {}", ex.getMessage());
}
});
}
// 事务消息
@Transactional
public void sendTransactionalMessage(String topic, OrderEvent event) {
kafkaTemplate.executeInTransaction(t -> {
t.send(topic, event.getOrderId().toString(), event);
// 其他操作
return true;
});
}
}
Kafka 消费者:
@Component
public class KafkaConsumer {
@KafkaListener(topics = "order-topic", groupId = "order-group")
public void consumeOrder(OrderEvent event) {
log.info("收到订单事件: {}", event);
orderService.processOrderEvent(event);
}
// 批量消费
@KafkaListener(topics = "order-topic", groupId = "order-group",
containerFactory = "batchFactory")
public void consumeOrderBatch(List<ConsumerRecord<String, OrderEvent>> records) {
for (ConsumerRecord<String, OrderEvent> record : records) {
orderService.processOrderEvent(record.value());
}
}
// 手动提交
@KafkaListener(topics = "order-topic", groupId = "order-group",
containerFactory = "manualAckFactory")
public void consumeOrderManual(OrderEvent event,
Acknowledgment acknowledgment) {
try {
orderService.processOrderEvent(event);
acknowledgment.acknowledge();
} catch (Exception e) {
// 处理失败,不确认
}
}
}
8.3 消息可靠性保证
消息可靠性模式:
- At Most Once:最多一次,可能丢失消息
- At Least Once:至少一次,可能重复消费
- Exactly Once:恰好一次,需要额外支持
消息重试机制:
@Configuration
public class RetryConfig {
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 重试配置
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
factory.setAdviceChain(retryTemplate);
return factory;
}
}
死信队列处理:
@Configuration
public class DeadLetterConfig {
@Bean
public Queue orderQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "order.dlx.exchange");
args.put("x-dead-letter-routing-key", "order.dead.letter");
args.put("x-message-ttl", 60000); // 60秒过期
return new Queue("order.queue", true, false, false, args);
}
@Bean
public Queue deadLetterQueue() {
return new Queue("order.dead.letter.queue");
}
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange("order.dlx.exchange");
}
@Bean
public Binding deadLetterBinding() {
return BindingBuilder
.bind(deadLetterQueue())
.to(deadLetterExchange())
.with("order.dead.letter");
}
}
九、缓存
9.1 Spring Cache
缓存配置:
# application.yml
spring:
cache:
type: redis
redis:
time-to-live: 60000
cache-null-values: false
key-prefix: "cache:"
use-key-prefix: true
启用缓存:
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
缓存使用示例:
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
}
@Cacheable(value = "products", key = "'list'", condition = "#category != null")
public List<Product> getProductsByCategory(String category) {
return productRepository.findByCategory(category);
}
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
@CacheEvict(value = "products", allEntries = true)
public void clearAllProducts() {
// 清除所有产品缓存
}
@Caching(
evict = {
@CacheEvict(value = "products", key = "#product.id"),
@CacheEvict(value = "productList", allEntries = true)
}
)
public Product saveProduct(Product product) {
return productRepository.save(product);
}
}
自定义缓存管理器:
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(getCacheConfigurations())
.transactionAware()
.build();
}
private Map<String, RedisCacheConfiguration> getCacheConfigurations() {
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("products", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new JdkSerializationRedisSerializer())));
configMap.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)));
return configMap;
}
}
9.2 Redis 缓存
Redis 配置:
# application.yml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 2000
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1
Redis 模板使用:
@Service
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
private final StringRedisTemplate stringRedisTemplate;
public RedisService(RedisTemplate<String, Object> redisTemplate,
StringRedisTemplate stringRedisTemplate) {
this.redisTemplate = redisTemplate;
this.stringRedisTemplate = stringRedisTemplate;
}
public void setValue(String key, Object value) {
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(10));
}
public Object getValue(String key) {
return redisTemplate.opsForValue().get(key);
}
public void setHashValue(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public Object getHashValue(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
public void addToList(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
}
public List<Object> getList(String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
public void addToSet(String key, Object value) {
redisTemplate.opsForSet().add(key, value);
}
public Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}
public void increment(String key) {
redisTemplate.opsForValue().increment(key);
}
public Boolean setIfAbsent(String key, Object value, Duration timeout) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout);
}
}
分布式锁实现:
@Component
public class DistributedLock {
private final RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
public DistributedLock(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean tryLock(String lockKey, String requestId, long expireTime) {
String key = LOCK_PREFIX + lockKey;
return Boolean.TRUE.equals(redisTemplate.opsForValue()
.setIfAbsent(key, requestId, Duration.ofMillis(expireTime)));
}
public boolean unlock(String lockKey, String requestId) {
String key = LOCK_PREFIX + lockKey;
String value = redisTemplate.opsForValue().get(key);
if (requestId.equals(value)) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
return false;
}
// Redisson 分布式锁
public void withRedissonLock(String lockKey, Runnable task) {
RLock lock = redissonClient.getLock(lockKey);
try {
boolean locked = lock.tryLock(10, 60, TimeUnit.SECONDS);
if (locked) {
task.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
9.3 多级缓存
多级缓存配置:
@Configuration
public class MultiLevelCacheConfig {
@Primary
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(caffeineCacheBuilder());
// 创建多级缓存管理器
List<CacheManager> cacheManagers = new ArrayList<>();
cacheManagers.add(caffeineCacheManager);
cacheManagers.add(redisCacheManager());
return new CompositeCacheManager(cacheManagers);
}
@Bean
public Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats();
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
缓存穿透/击穿/雪崩解决方案:
@Service
public class CacheSafeService {
// 缓存空值防止缓存穿透
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProductSafe(Long id) {
Product product = productRepository.findById(id).orElse(null);
if (product == null) {
// 缓存空值,短时间过期
redisTemplate.opsForValue().set("product:null:" + id, "", 1, TimeUnit.MINUTES);
}
return product;
}
// 互斥锁防止缓存击穿
public Product getProductWithLock(Long id) {
String cacheKey = "product:" + id;
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product == null) {
String lockKey = "lock:product:" + id;
try {
// 尝试获取锁
if (tryLock(lockKey)) {
// 双重检查
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product == null) {
product = productRepository.findById(id).orElse(null);
if (product != null) {
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
}
}
} else {
// 等待重试
Thread.sleep(100);
return getProductWithLock(id);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
unlock(lockKey);
}
}
return product;
}
// 随机过期时间防止缓存雪崩
public void setWithRandomExpire(String key, Object value) {
Random random = new Random();
int expire = 1800 + random.nextInt(600); // 30-40分钟随机过期
redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}
}
十、定时任务
10.1 @Scheduled 注解
基本定时任务:
@Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
// 每5秒执行一次
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
logger.info("Fixed Rate Task: {}", System.currentTimeMillis());
}
// 每5秒执行一次,上次结束5秒后开始
@Scheduled(fixedDelay = 5000)
public void fixedDelayTask() {
logger.info("Fixed Delay Task: {}", System.currentTimeMillis());
}
// 初始延迟3秒,每5秒执行一次
@Scheduled(initialDelay = 3000, fixedRate = 5000)
public void initialDelayTask() {
logger.info("Initial Delay Task: {}", System.currentTimeMillis());
}
// Cron表达式:每秒执行
@Scheduled(cron = "*/1 * * * * *")
public void cronTask() {
logger.info("Cron Task: {}", System.currentTimeMillis());
}
// Cron表达式:工作日上午10点执行
@Scheduled(cron = "0 0 10 * * MON-FRI")
public void workdayTask() {
logger.info("Workday Task: {}", LocalDateTime.now());
}
}
定时任务配置:
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean(destroyMethod = "shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(10);
}
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
}
10.2 动态定时任务
动态定时任务实现:
@Component
public class DynamicScheduledTask {
private final ThreadPoolTaskScheduler taskScheduler;
private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
public DynamicScheduledTask(ThreadPoolTaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public void addTask(String taskId, Runnable task, String cronExpression) {
ScheduledFuture<?> future = taskScheduler.schedule(
task,
triggerContext -> {
CronTrigger trigger = new CronTrigger(cronExpression);
return trigger.nextExecutionTime(triggerContext);
}
);
scheduledTasks.put(taskId, future);
}
public void updateTask(String taskId, Runnable task, String cronExpression) {
cancelTask(taskId);
addTask(taskId, task, cronExpression);
}
public void cancelTask(String taskId) {
ScheduledFuture<?> future = scheduledTasks.get(taskId);
if (future != null) {
future.cancel(true);
scheduledTasks.remove(taskId);
}
}
public boolean isTaskRunning(String taskId) {
ScheduledFuture<?> future = scheduledTasks.get(taskId);
return future != null && !future.isDone();
}
public List<String> getRunningTasks() {
return scheduledTasks.entrySet().stream()
.filter(entry -> !entry.getValue().isDone())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}
10.3 分布式定时任务
基于数据库的分布式锁:
@Component
public class DistributedScheduledTask {
@Scheduled(cron = "0 */5 * * * *")
@SchedulerLock(name = "syncDataTask", lockAtMostFor = "5m", lockAtLeastFor = "1m")
public void syncData() {
// 使用数据库锁确保只有一个实例执行
if (tryAcquireLock("syncDataTask", 5)) {
try {
// 执行任务
dataSyncService.sync();
} finally {
releaseLock("syncDataTask");
}
}
}
private boolean tryAcquireLock(String lockName, int expireSeconds) {
String sql = "INSERT INTO distributed_lock(lock_name, locked_by, locked_at, expire_at) " +
"VALUES(?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND)) " +
"ON DUPLICATE KEY UPDATE " +
"locked_by = IF(expire_at < NOW(), VALUES(locked_by), locked_by), " +
"locked_at = IF(expire_at < NOW(), VALUES(locked_at), locked_at), " +
"expire_at = IF(expire_at < NOW(), VALUES(expire_at), expire_at)";
int updated = jdbcTemplate.update(sql, lockName, getInstanceId(), expireSeconds);
return updated > 0;
}
}
使用 Quartz 集群:
# application.yml
spring:
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: never
properties:
org.quartz.scheduler.instanceId: AUTO
org.quartz.scheduler.instanceName: clusteredScheduler
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix: QRTZ_
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 20000
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
十一、监控管理
11.1 Spring Boot Actuator
Actuator 配置:
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
jmx:
exposure:
include: health,info
endpoint:
health:
show-details: when_authorized
probes:
enabled: true
metrics:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
tracing:
sampling:
probability: 1.0
健康检查端点:
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
private final RedisTemplate<String, String> redisTemplate;
public CustomHealthIndicator(DataSource dataSource,
RedisTemplate<String, String> redisTemplate) {
this.dataSource = dataSource;
this.redisTemplate = redisTemplate;
}
@Override
public Health health() {
// 检查数据库连接
boolean dbHealthy = checkDatabase();
// 检查Redis连接
boolean redisHealthy = checkRedis();
// 检查外部服务
boolean externalServiceHealthy = checkExternalService();
Map<String, Object> details = new HashMap<>();
details.put("database", dbHealthy ? "connected" : "disconnected");
details.put("redis", redisHealthy ? "connected" : "disconnected");
details.put("externalService", externalServiceHealthy ? "available" : "unavailable");
if (dbHealthy && redisHealthy && externalServiceHealthy) {
return Health.up().withDetails(details).build();
} else {
return Health.down().withDetails(details).build();
}
}
// 数据库就绪探针
@ReadinessProbe
public HealthCheckResponse databaseReady() {
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(1000)) {
return HealthCheckResponse.up("database");
}
} catch (SQLException e) {
return HealthCheckResponse.down("database").withDetail("error", e.getMessage());
}
return HealthCheckResponse.down("database");
}
// 应用活跃探针
@LivenessProbe
public HealthCheckResponse applicationAlive() {
return HealthCheckResponse.up("application");
}
}
11.2 自定义指标
自定义业务指标:
@Component
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
private final Counter orderCounter;
private final Timer orderProcessingTimer;
private final DistributionSummary orderAmountSummary;
public BusinessMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 订单计数器
this.orderCounter = Counter.builder("orders.total")
.description("Total number of orders")
.tag("type", "business")
.register(meterRegistry);
// 订单处理时间计时器
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Order processing time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
// 订单金额分布
this.orderAmountSummary = DistributionSummary.builder("orders.amount")
.description("Order amount distribution")
.baseUnit("CNY")
.publishPercentiles(0.5, 0.95, 0.99)
.register(meterRegistry);
}
public void incrementOrderCount() {
orderCounter.increment();
}
public void recordOrderProcessingTime(Runnable task) {
orderProcessingTimer.record(task);
}
public void recordOrderAmount(BigDecimal amount) {
orderAmountSummary.record(amount.doubleValue());
}
// 测量方法执行时间
@Timed(value = "user.service", description = "Time taken to process user")
@Counted(value = "user.service", description = "Number of user service calls")
public User processUser(User user) {
// 处理用户
return user;
}
// 自定义仪表盘
public void createCustomGauge() {
List<String> cacheKeys = Arrays.asList("key1", "key2", "key3");
Gauge.builder("cache.keys.count", cacheKeys, List::size)
.description("Number of keys in cache")
.register(meterRegistry);
}
}
日志监控配置:
@Configuration
public class LoggingConfig {
@Bean
public MeterRegistryLogbackConfigurer meterRegistryLogbackConfigurer(MeterRegistry registry) {
return new MeterRegistryLogbackConfigurer(registry);
}
// 日志级别动态调整
@RestController
@RequestMapping("/actuator/loggers")
public class LoggersController {
@PostMapping("/{name}")
public void configureLogLevel(@PathVariable String name,
@RequestBody Map<String, String> configuration) {
String level = configuration.get("configuredLevel");
if (level != null) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.getLogger(name).setLevel(Level.valueOf(level));
}
}
}
}
11.3 应用监控
慢查询监控:
@Aspect
@Component
@Slf4j
public class SlowQueryAspect {
@Value("${app.slow-query.threshold:1000}")
private long slowQueryThreshold;
@Autowired
private MeterRegistry meterRegistry;
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public Object monitorRequest(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - startTime;
// 记录指标
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(meterRegistry.timer("http.request.duration"));
// 记录慢查询
if (duration > slowQueryThreshold) {
String methodName = joinPoint.getSignature().toShortString();
log.warn("慢查询: {} 耗时 {}ms", methodName, duration);
// 发送告警
alertSlowQuery(methodName, duration);
}
}
}
}
告警配置:
# application.yml
management:
metrics:
export:
prometheus:
enabled: true
endpoint:
prometheus:
enabled: true
# 告警规则
alerting:
rules:
- alert: HighErrorRate
expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) > 0.1
for: 1m
labels:
severity: critical
annotations:
summary: "高错误率"
description: "5分钟内错误率超过10%"
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1
for: 2m
labels:
severity: warning
annotations:
summary: "高响应时间"
description: "95%分位响应时间超过1秒"
十二、测试
12.1 单元测试
基本单元测试:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
@Test
void shouldCreateUserSuccessfully() {
// 准备
CreateUserRequest request = new CreateUserRequest(
"testuser", "password123", "test@example.com");
User expectedUser = User.builder()
.username("testuser")
.password("encodedPassword")
.email("test@example.com")
.build();
when(passwordEncoder.encode("password123")).thenReturn("encodedPassword");
when(userRepository.save(any(User.class))).thenReturn(expectedUser);
// 执行
User result = userService.createUser(request);
// 验证
assertThat(result.getUsername()).isEqualTo("testuser");
assertThat(result.getEmail()).isEqualTo("test@example.com");
verify(userRepository).save(any(User.class));
verify(passwordEncoder).encode("password123");
}
@Test
void shouldThrowExceptionWhenUsernameExists() {
// 准备
CreateUserRequest request = new CreateUserRequest(
"existinguser", "password123", "test@example.com");
when(userRepository.existsByUsername("existinguser")).thenReturn(true);
// 执行和验证
assertThatThrownBy(() -> userService.createUser(request))
.isInstanceOf(BusinessException.class)
.hasMessage("用户名已存在");
verify(userRepository, never()).save(any(User.class));
}
@ParameterizedTest
@ValueSource(strings = {"user1", "user2", "user3"})
void shouldFindUserByUsername(String username) {
// 参数化测试
User user = User.builder().username(username).build();
when(userRepository.findByUsername(username)).thenReturn(Optional.of(user));
Optional<User> result = userService.findByUsername(username);
assertThat(result).isPresent();
assertThat(result.get().getUsername()).isEqualTo(username);
}
}
12.2 集成测试
Spring Boot 集成测试:
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldCreateUser() throws Exception {
CreateUserRequest request = new CreateUserRequest(
"testuser", "password123", "test@example.com");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
assertThat(userRepository.count()).isEqualTo(1);
}
@Test
void shouldReturnUserList() throws Exception {
// 准备数据
User user1 = User.builder().username("user1").email("user1@example.com").build();
User user2 = User.builder().username("user2").email("user2@example.com").build();
userRepository.saveAll(Arrays.asList(user1, user2));
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(2))
.andExpect(jsonPath("$[0].username").value("user1"))
.andExpect(jsonPath("$[1].username").value("user2"));
}
@Test
@WithMockUser(username = "admin", roles = {"ADMIN"})
void shouldAccessAdminEndpointWithAdminRole() throws Exception {
mockMvc.perform(get("/api/admin/dashboard"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "user", roles = {"USER"})
void shouldDenyAccessAdminEndpointWithoutAdminRole() throws Exception {
mockMvc.perform(get("/api/admin/dashboard"))
.andExpect(status().isForbidden());
}
}
测试切片:
// 只测试Web层
@WebMvcTest(UserController.class)
class UserControllerWebTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldGetUser() throws Exception {
User user = User.builder().id(1L).username("testuser").build();
when(userService.getUser(1L)).thenReturn(Optional.of(user));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("testuser"));
}
}
// 只测试JPA
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindByUsername() {
User user = User.builder().username("testuser").build();
entityManager.persist(user);
Optional<User> found = userRepository.findByUsername("testuser");
assertThat(found).isPresent();
assertThat(found.get().getUsername()).isEqualTo("testuser");
}
}
// 只测试JSON序列化
@JsonTest
class UserJsonTest {
@Autowired
private JacksonTester<User> json;
@Test
void shouldSerializeUser() throws Exception {
User user = User.builder()
.id(1L)
.username("testuser")
.email("test@example.com")
.build();
assertThat(json.write(user)).isEqualToJson("user.json");
assertThat(json.write(user)).hasJsonPathStringValue("@.username");
}
}
12.3 测试配置
测试配置文件:
# src/test/resources/application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
show-sql: false
redis:
host: localhost
port: 6379
lettuce:
pool:
max-active: 5
kafka:
bootstrap-servers: ${spring.embedded.kafka.brokers}
test:
double:
precision: 0.001
测试工具类:
@SpringBootTest
@Testcontainers
class IntegrationTestWithContainers {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Container
static RedisContainer<?> redis = new RedisContainer<>("redis:6-alpine")
.withExposedPorts(6379);
@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);
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", redis::getFirstMappedPort);
}
@Test
void shouldWorkWithContainers() {
// 使用真实容器进行测试
}
}
十三、部署
13.1 打包部署
打包配置:
<!-- Maven 打包配置 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
Docker 部署:
# Dockerfile
# 第一阶段:构建
FROM maven:3.8.4-openjdk-17-slim AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行
FROM openjdk:17-jdk-slim
WORKDIR /app
# 创建非root用户
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring
# 复制JAR文件
COPY --from=builder /app/target/*.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 环境变量
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
Docker Compose 部署:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/db
- SPRING_DATASOURCE_USERNAME=user
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_REDIS_HOST=redis
depends_on:
- postgres
- redis
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
postgres:
image: postgres:13-alpine
environment:
- POSTGRES_DB=db
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:6-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- backend
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
backend:
driver: bridge
13.2 部署策略
蓝绿部署:
# Kubernetes 蓝绿部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-v1
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: v1
template:
metadata:
labels:
app: myapp
version: v1
spec:
containers:
name: app
image: myapp:v1
ports:
containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
ports:
port: 80
targetPort: 8080
滚动更新:
# 滚动更新策略
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
minReadySeconds: 30
13.3 配置管理
外部化配置:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.properties(
"spring.config.name:application,config",
"spring.config.location:optional:classpath:/,optional:classpath:/config/,optional:file:./,optional:file:./config/",
"spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}"
)
.run(args);
}
}
配置服务器:
# bootstrap.yml
spring:
application:
name: myapp
cloud:
config:
uri: http://config-server:8888
fail-fast: true
retry:
max-attempts: 6
max-interval: 10000
request-read-timeout: 5000
request-connect-timeout: 2000
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
十四、微服务
14.1 Spring Cloud
微服务架构组件:
| 组件 | 功能 | 替代方案 |
|---|---|---|
| Eureka/Nacos | 服务注册与发现 | Consul, Zookeeper |
| Ribbon/Spring Cloud LoadBalancer | 客户端负载均衡 | Nginx |
| Feign/OpenFeign | 声明式HTTP客户端 | RestTemplate, WebClient |
| Hystrix/Resilience4j | 熔断降级 | Sentinel |
| Zuul/Spring Cloud Gateway | API网关 | Kong, Nginx |
| Config Server | 配置中心 | Apollo, Nacos Config |
| Sleuth/Zipkin | 分布式追踪 | SkyWalking, Jaeger |
服务注册与发现:
// 服务提供者
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
// 服务消费者
@Service
public class OrderService {
private final RestTemplate restTemplate;
private final LoadBalancerClient loadBalancer;
public OrderService(RestTemplate restTemplate,
LoadBalancerClient loadBalancer) {
this.restTemplate = restTemplate;
this.loadBalancer = loadBalancer;
}
public User getUser(Long userId) {
// 使用 RestTemplate
return restTemplate.getForObject(
"http://user-service/api/users/{id}",
User.class,
userId
);
}
public User getUserWithFeign(Long userId) {
return userServiceClient.getUser(userId);
}
}
// Feign 客户端
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable("id") Long id);
@PostMapping("/api/users")
User createUser(@RequestBody User user);
}
14.2 API 网关
Spring Cloud Gateway 配置:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_service", r -> r
.path("/api/users/**")
.filters(f -> f
.addRequestHeader("X-Request-Id", UUID.randomUUID().toString())
.circuitBreaker(config -> config
.setName("userService")
.setFallbackUri("forward:/fallback/user"))
.retry(config -> config
.setRetries(3)
.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR))
)
.uri("lb://user-service"))
.route("order_service", r -> r
.path("/api/orders/**")
.filters(f -> f
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter()))
.modifyRequestBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))
)
.uri("lb://order-service"))
.build();
}
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20, 1);
}
}
网关过滤器:
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 检查认证头
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 验证令牌
String token = authHeader.substring(7);
if (!validateToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 添加用户信息到请求头
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", extractUserId(token))
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
@Override
public int getOrder() {
return -1;
}
}
14.3 服务容错
Resilience4j 配置:
@Configuration
public class ResilienceConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(60))
.slidingWindowSize(10)
.permittedNumberOfCallsInHalfOpenState(3)
.build();
return CircuitBreakerRegistry.of(config);
}
@Bean
public RateLimiterRegistry rateLimiterRegistry() {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(10)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMillis(100))
.build();
return RateLimiterRegistry.of(config);
}
@Bean
public BulkheadRegistry bulkheadRegistry() {
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(5)
.maxWaitDuration(Duration.ofMillis(100))
.build();
return BulkheadRegistry.of(config);
}
}
使用容错保护:
@Service
public class UserService {
private final CircuitBreaker circuitBreaker;
private final RateLimiter rateLimiter;
private final Bulkhead bulkhead;
public UserService(CircuitBreakerRegistry circuitBreakerRegistry,
RateLimiterRegistry rateLimiterRegistry,
BulkheadRegistry bulkheadRegistry) {
this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("userService");
this.rateLimiter = rateLimiterRegistry.rateLimiter("userService");
this.bulkhead = bulkheadRegistry.bulkhead("userService");
}
@TimeLimiter(name = "userService", fallbackMethod = "fallback")
public CompletableFuture<User> getUserWithResilience(Long userId) {
return CompletableFuture.supplyAsync(() -> {
// 受保护的服务调用
return circuitBreaker.executeSupplier(() ->
rateLimiter.executeSupplier(() ->
bulkhead.executeSupplier(() ->
userRepository.findById(userId)
.orElseThrow(ResourceNotFoundException::new)
)
)
);
});
}
public User fallback(Long userId, TimeoutException e) {
return User.builder()
.id(userId)
.username("fallback")
.build();
}
}
十五、性能优化
15.1 JVM 优化
JVM 参数配置:
# application.yml
spring:
application:
name: myapp
config:
activate:
on-profile: prod
# JVM 参数
jvm:
options: >
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=35
-Xlog:gc*:file=logs/gc.log:time,uptime,level,tags:filecount=5,filesize=10m
-XX:NativeMemoryTracking=summary
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=logs/
-XX:ErrorFile=logs/hs_err_pid%p.log
监控JVM状态:
@Component
public class JvmMonitor {
@Scheduled(fixedRate = 60000)
public void monitorJvm() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
double memoryUsage = (double) usedMemory / maxMemory * 100;
if (memoryUsage > 80) {
log.warn("JVM内存使用率过高: {}%", String.format("%.2f", memoryUsage));
}
// 监控线程状态
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
int threadCount = threadBean.getThreadCount();
int peakThreadCount = threadBean.getPeakThreadCount();
if (threadCount > 200) {
log.warn("线程数过多: {}", threadCount);
}
// 监控GC
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
long count = gcBean.getCollectionCount();
long time = gcBean.getCollectionTime();
log.info("GC {}: 次数={}, 时间={}ms",
gcBean.getName(), count, time);
}
}
}
15.2 数据库优化
连接池优化:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
pool-name: HikariPool
auto-commit: false
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
useLocalSessionState: true
rewriteBatchedStatements: true
cacheResultSetMetadata: true
cacheServerConfiguration: true
elideSetAutoCommits: true
maintainTimeStats: false
查询优化:
@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findUsersWithOptimization(UserQuery query, Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
// 只选择需要的字段
cq.multiselect(
root.get("id"),
root.get("username"),
root.get("email")
);
// 添加查询条件
List<Predicate> predicates = new ArrayList<>();
if (query.getUsername() != null) {
predicates.add(cb.like(root.get("username"), "%" + query.getUsername() + "%"));
}
if (query.getStatus() != null) {
predicates.add(cb.equal(root.get("status"), query.getStatus()));
}
cq.where(predicates.toArray(new Predicate[0]));
cq.orderBy(cb.desc(root.get("createdAt")));
TypedQuery<User> typedQuery = entityManager.createQuery(cq);
// 分页
typedQuery.setFirstResult((int) pageable.getOffset());
typedQuery.setMaxResults(pageable.getPageSize());
// 使用只读查询
typedQuery.setHint(QueryHints.READ_ONLY, true);
return typedQuery.getResultList();
}
// 批量操作优化
@Transactional
public int batchInsert(List<User> users) {
Session session = entityManager.unwrap(Session.class);
session.setJdbcBatchSize(50);
for (int i = 0; i < users.size(); i++) {
entityManager.persist(users.get(i));
// 每50条刷新一次
if (i % 50 == 0 && i > 0) {
entityManager.flush();
entityManager.clear();
}
}
return users.size();
}
}
15.3 应用优化
启动优化:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.lazyInitialization(true) // 延迟初始化
.logStartupInfo(false) // 减少启动日志
.run(args);
}
}
// 使用懒加载
@Component
@Lazy
public class HeavyService {
// 延迟初始化的重服务
}
// 使用条件装配
@Configuration
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureConfig {
// 按需加载配置
}
缓存优化策略:
@Service
public class CacheOptimizationService {
// 多级缓存策略
public Product getProductWithMultiLevelCache(Long id) {
// 1. 本地缓存
Product product = localCache.get(id);
if (product != null) {
return product;
}
// 2. Redis缓存
product = redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
localCache.put(id, product);
return product;
}
// 3. 数据库
product = productRepository.findById(id).orElse(null);
if (product != null) {
// 写入缓存
redisTemplate.opsForValue().set("product:" + id, product, 30, TimeUnit.MINUTES);
localCache.put(id, product);
}
return product;
}
// 缓存预热
@PostConstruct
public void warmUpCache() {
List<Product> hotProducts = productRepository.findHotProducts();
for (Product product : hotProducts) {
redisTemplate.opsForValue().set(
"product:" + product.getId(),
product,
60,
TimeUnit.MINUTES
);
}
}
}
十六、学习资源
16.1 官方资源
- Spring Boot 官方文档:https://spring.io/projects/spring-boot
- Spring Framework 官方文档:https://spring.io/projects/spring-framework
- Spring Cloud 官方文档:https://spring.io/projects/spring-cloud
- Spring Data 官方文档:https://spring.io/projects/spring-data
- Spring Security 官方文档:https://spring.io/projects/spring-security
- Spring Initializr:https://start.spring.io
- Spring Boot GitHub:https://github.com/spring-projects/spring-boot
- Spring Guides:https://spring.io/guides
16.2 推荐书籍
- 入门书籍
- 《Spring Boot 实战》- Craig Walls
- 《Spring Boot 编程思想》- 小马哥
- 《Spring Boot 揭秘》- 汪云飞
- 进阶书籍
- 《Spring 源码深度解析》- 郝佳
- 《Spring Boot 企业级应用开发实战》
- 《Spring Cloud 微服务实战》
16.3 在线课程
- Spring 官方教程:Spring Guides 提供的循序渐进的实践教程
- Udemy:多位讲师提供的完整 Spring Boot 系列课程
- Coursera:知名大学的 Java 和 Spring 相关课程
- Bilibili/YouTube:众多免费的视频教程和项目实战
- 慕课网/极客时间:国内的技术课程平台
16.4 社区与论坛
- Stack Overflow:问题解答,标签
spring-boot、spring下有大量讨论 - GitHub Issues:各 Spring 项目的官方问题追踪和讨论区
- Reddit:r/java、r/SpringBoot 子版块
- 知乎/掘金/思否:中文技术社区,有许多经验分享和问题讨论
- Spring 官方论坛:社区讨论和官方支持
16.5 练习与实战
- Spring Initializr:快速创建项目脚手架进行练习
- LeetCode:算法练习,可结合 Spring Boot 实现 Web 接口
- GitHub 开源项目:阅读和模仿优秀的 Spring Boot 项目源码
- 官方样例代码:Spring Boot 源码仓库中的
spring-boot-samples模块 - 构建个人项目:从博客系统、管理系统等小型项目开始实践
16.6 学习建议
- 循序渐进:从 Spring Boot 基础开始,再到 Web 开发、数据访问、安全等模块。
- 重视官方文档:Spring 官方文档质量极高,是解决问题的最佳首选。
- 动手实践:编码是学习框架的最佳途径,不要只停留在阅读。
- 理解而非记忆:理解 Spring 的核心思想(如 IoC、AOP)和 Spring Boot 的自动配置原理。
- 关注社区:通过社区了解最佳实践、常见问题解决方案和最新动态。
- 版本选择:初学者建议从当前主流稳定版(如 Spring Boot 3.x)开始,避免过早接触快照或候选版本。