Spring Boot相关

42 minute read

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 界面和多种构建工具支持。

    访问地址:https://start.spring.io

    创建步骤:

    1. 选择构建工具(Maven/Gradle)
    2. 选择语言(Java/Kotlin/Groovy)
    3. 选择 Spring Boot 版本
    4. 填写项目元数据
    5. 添加依赖
    6. 生成项目
  • 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 运行

  1. 在 IDEA 中直接运行 main 方法
  2. 在 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 应用程序。其工作原理如下:

  1. 条件注解:基于条件判断是否启用配置
  2. 自动配置类:在 spring-boot-autoconfigure 包中定义
  3. spring.factories:列出所有自动配置类
  4. @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 消息队列

起步依赖的优势

  1. 提供功能相关的依赖集合
  2. 解决依赖版本冲突
  3. 自动传递依赖管理
  4. 简化构建配置

3.3 外部化配置

Spring Boot 支持多种外部化配置方式,优先级从高到低:

  1. 命令行参数java -jar app.jar --server.port=8081
  2. 系统属性-Dspring.profiles.active=dev
  3. 环境变量SPRING_PROFILES_ACTIVE=dev
  4. 配置文件application-{profile}.properties/yml
  5. 默认配置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 应用的启动过程可以分为以下几个阶段:

  1. 启动类执行:调用 SpringApplication.run()
  2. 创建应用上下文:根据应用类型创建合适的 ApplicationContext
  3. 准备环境:加载配置文件、命令行参数、环境变量
  4. 创建 Bean 工厂:创建 BeanDefinitionRegistry
  5. 执行 Runner:执行 ApplicationRunner 和 CommandLineRunner
  6. 启动内嵌服务器:启动 Tomcat/Jetty/Undertow
  7. 发布事件:发布 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 的方式

  1. 配置文件指定spring.profiles.active=dev
  2. 命令行参数java -jar app.jar --spring.profiles.active=dev
  3. 环境变量SPRING_PROFILES_ACTIVE=dev
  4. 虚拟机参数-Dspring.profiles.active=dev
  5. 测试注解@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 加密配置

  1. 添加依赖

     <dependency>
         <groupId>com.github.ulisesbocchio</groupId>
         <artifactId>jasypt-spring-boot-starter</artifactId>
         <version>3.0.5</version>
     </dependency>
    
  2. 配置加密

     # application.yml
     jasypt:
     encryptor:
         password: ${JASYPT_PASSWORD:mySecretKey}  # 从环境变量获取密钥
            
     spring:
     datasource:
         password: ENC(加密后的密码)
    
  3. 加密工具类

     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 最佳实践

  1. 资源命名
    • 使用名词复数形式:/users 而不是 /getUsers
    • 层级关系:/users/{userId}/orders
    • 避免动词:使用 HTTP 方法表示操作
  2. HTTP 方法使用
    • GET:获取资源
    • POST:创建资源
    • PUT:更新整个资源
    • PATCH:部分更新资源
    • DELETE:删除资源
  3. 状态码使用
    • 200:请求成功
    • 201:创建成功
    • 204:无内容
    • 400:请求错误
    • 401:未认证
    • 403:无权限
    • 404:资源不存在
    • 500:服务器错误
  4. 版本控制
    • URL 路径:/api/v1/users
    • 请求头:Accept: application/vnd.company.app-v1+json
  5. 分页、排序、过滤
    • 分页:?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 消息可靠性保证

消息可靠性模式

  1. At Most Once:最多一次,可能丢失消息
  2. At Least Once:至少一次,可能重复消费
  3. 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 官方资源

16.2 推荐书籍

  1. 入门书籍
    • 《Spring Boot 实战》- Craig Walls
    • 《Spring Boot 编程思想》- 小马哥
    • 《Spring Boot 揭秘》- 汪云飞
  2. 进阶书籍
    • 《Spring 源码深度解析》- 郝佳
    • 《Spring Boot 企业级应用开发实战》
    • 《Spring Cloud 微服务实战》

16.3 在线课程

  • Spring 官方教程:Spring Guides 提供的循序渐进的实践教程
  • Udemy:多位讲师提供的完整 Spring Boot 系列课程
  • Coursera:知名大学的 Java 和 Spring 相关课程
  • Bilibili/YouTube:众多免费的视频教程和项目实战
  • 慕课网/极客时间:国内的技术课程平台

16.4 社区与论坛

  • Stack Overflow:问题解答,标签 spring-bootspring 下有大量讨论
  • 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 学习建议

  1. 循序渐进:从 Spring Boot 基础开始,再到 Web 开发、数据访问、安全等模块。
  2. 重视官方文档:Spring 官方文档质量极高,是解决问题的最佳首选。
  3. 动手实践:编码是学习框架的最佳途径,不要只停留在阅读。
  4. 理解而非记忆:理解 Spring 的核心思想(如 IoC、AOP)和 Spring Boot 的自动配置原理。
  5. 关注社区:通过社区了解最佳实践、常见问题解决方案和最新动态。
  6. 版本选择:初学者建议从当前主流稳定版(如 Spring Boot 3.x)开始,避免过早接触快照或候选版本。