Spring Boot 3.x 新特性详解:从基础到高级实战

Spring Boot 3.x 新特性详解:从基础到高级实战

精选文章moguli202025-06-05 8:50:414A+A-

1. Spring Boot 3.x 简介与核心特性

1.1 Spring Boot 3.x 新特性概览

Spring Boot 3.x 是建立在 Spring Framework 6.0 基础上的重大版本更新,主要新特性包括:

特性

说明

优势

JDK 17+ 基线

最低要求Java 17

利用现代Java特性如record、文本块等

Jakarta EE 9+

从javax迁移到jakarta命名空间

符合最新企业标准

改进的GraalVM支持

更好的原生镜像支持

更快的启动速度和更低的内存占用

增强的Micrometer集成

改进的指标收集

更好的可观测性

新的Problem Details API

标准化的错误响应格式

更好的API错误处理

自动配置改进

更智能的条件配置

更精简的运行时代码

1.2 Spring Boot 3.x 架构变化

1.3 版本升级注意事项

从Spring Boot 2.x升级到3.x需要考虑:

  1. 包名变更:所有javax.*包名变更为jakarta.*
  2. // 旧版
    import javax.servlet.http.HttpServletRequest;
    // 新版
    import jakarta.servlet.http.HttpServletRequest;
  3. 最低Java版本:必须使用Java 17或更高版本
  4. 依赖变更
  5. Spring Framework 6.0+
  6. Hibernate 6.1+
  7. Tomcat 10+ (支持Jakarta EE 9)

2. 环境准备与项目创建

2.1 开发环境要求

组件

最低版本

推荐版本

JDK

17

17或21

Maven

3.5+

3.8+

Gradle

7.x

8.x

IDE

IntelliJ IDEA 2021.3+

IDEA Ultimate

2.2 项目初始化方式

方式1:使用Spring Initializr网站

  1. 访问 https://start.spring.io
  2. 选择:
  3. Project: Maven/Gradle
  4. Language: Java
  5. Spring Boot: 3.1.0
  6. 添加依赖:根据需要选择
  7. 生成并下载项目

方式2:使用命令行

# 使用curl
curl https://start.spring.io/starter.tgz -d dependencies=web,data-jpa \
  -d javaVersion=17 -d type=maven-project -d bootVersion=3.1.0 \
  -d groupId=com.example -d artifactId=demo -d name=demo | tar -xzvf -

# 或者使用Spring Boot CLI
spring init --build=maven --java-version=17 --dependencies=web,data-jpa demo

2.3 项目结构解析

典型的Spring Boot 3.x项目结构:

src/
├── main/
│   ├── java/
│   │   └── com/example/demo/
│   │       ├── DemoApplication.java       # 主启动类
│   │       ├── config/                    # 配置类目录
│   │       ├── controller/                # 控制器目录
│   │       ├── service/                   # 服务层目录
│   │       ├── repository/                # 数据访问层目录
│   │       └── model/                     # 实体类目录
│   └── resources/
│       ├── static/                        # 静态资源
│       ├── templates/                     # 模板文件
│       ├── application.yml                # 配置文件
│       └── application-dev.yml            # 环境特定配置
└── test/                                  # 测试代码

2.4 第一个Spring Boot 3.x应用

主启动类示例

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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@RestController
class GreetingController {
    
    @GetMapping("/greet")
    public String greet(@RequestParam(value = "name", defaultValue = "World") String name) {
        return String.format("Hello, %s from Spring Boot 3!", name);
    }
}

运行与测试

  1. 运行DemoApplication的main方法
  2. 访问 http://localhost:8080/greet
  3. 访问 http://localhost:8080/greet?name=John

3. 自动配置原理深度解析

3.1 自动配置机制详解

Spring Boot自动配置通过@EnableAutoConfiguration实现,其工作原理:

  1. 检查classpath下的依赖
  2. 根据条件注解(如@ConditionalOnClass)决定是否配置
  3. 应用合理的默认配置

自动配置流程

自动配置Spring Boot开发者自动配置Spring Boot开发者添加starter依赖扫描META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports提供自动配置类列表应用条件过滤最终生效的配置

3.2 常用条件注解

注解

说明

示例

@ConditionalOnClass

类路径存在指定类时生效

@ConditionalOnClass(DataSource.class)

@ConditionalOnMissingBean

容器中不存在指定Bean时生效

@ConditionalOnMissingBean(DataSource.class)

@ConditionalOnProperty

配置属性满足条件时生效

@ConditionalOnProperty(name="cache.enabled", havingValue="true")

@ConditionalOnWebApplication

Web应用环境下生效

@ConditionalOnWebApplication(type=Type.SERVLET)

@ConditionalOnExpression

SpEL表达式为true时生效

@ConditionalOnExpression("${feature.enabled:true}")

3.3 自定义自动配置

步骤1:创建自动配置类

/**
 * 这是一个自动配置类,用于自动配置 MyService 相关的 Bean。
 * 它会在满足特定条件时生效,并根据配置文件中的属性来创建 MyService 实例。
 */
@Configuration // 表明这是一个配置类,Spring 会对其进行处理,相当于传统的 XML 配置文件,该类中可以定义 Bean
@AutoConfiguration  // 开启自动配置功能,Spring Boot 会根据类路径中的依赖自动配置 Spring 应用程序
@ConditionalOnClass(MyService.class) //条件注解,当类路径中存在 MyService 类时,这个配置类才会生效。即只有当项目依赖中包含 MyService 类时,Spring 才会处理这个配置类
@EnableConfigurationProperties(MyProperties.class) //启用配置属性绑定功能,将配置文件中的属性绑定到 MyProperties 类的实例上。这样就可以在配置文件中设置 MyProperties 类中定义的属性值
public class MyAutoConfiguration {
    /**
     * 创建并返回一个 MyService 实例。
     *
     * @param properties 从配置文件中绑定的 MyProperties 实例,包含了 MyService 所需的配置信息
     * @return 返回一个使用了配置属性的 MyService 实例
     */
    @Bean //定义一个名为 myService 的 Bean。该 Bean 的类型为 MyService,用于在 Spring 应用中提供特定的服务。
    @ConditionalOnMissingBean //条件注解,当 Spring 容器中不存在 MyService 类型的 Bean 时,才会创建这个 Bean。避免重复创建相同类型的 Bean
    public MyService myService(MyProperties properties) {
        // 使用从配置文件中获取的属性创建 MyService 实例
        return new MyService(properties);
    }
}

步骤2:定义配置属性类

@ConfigurationProperties("my.service")
public class MyProperties {
    private String prefix = "[INFO]";
    private boolean enabled = true;
    
    // getters and setters
}

步骤3:注册自动配置


src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中添加:

com.example.config.MyAutoConfiguration

3.4 自动配置调试

方法1:启用调试日志

application.yml中添加:

logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG

方法2:使用ConditionEvaluationReport

启动时添加--debug参数,或在应用中:

// 这是一个 Spring Boot 应用的主注解,组合了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 等注解,用于简化 Spring Boot 应用的配置,标识这是一个 Spring Boot 应用的入口类
@SpringBootApplication 
public class DemoApplication {
    // 应用程序的入口点,程序从这里开始执行
    public static void main(String[] args) {
        // 启动 Spring Boot 应用,传入应用主类和命令行参数
        SpringApplication.run(DemoApplication.class, args); 
    }

    // 定义一个 Bean,类型为 CommandLineRunner,在 Spring Boot 应用启动完成后执行
    @Bean 
    public CommandLineRunner conditionReport(ApplicationContext context) {
        return args -> {
            // 获取条件评估报告,通过应用上下文的 Bean 工厂来获取
            ConditionEvaluationReport report = ConditionEvaluationReport.get(
                context.getBeanFactory()); 
            // 遍历条件评估报告中按源分组的条件和结果,并打印输出
            report.getConditionAndOutcomesBySource().forEach((k, v) -> { 
                System.out.println(k + " => " + v);
            });
        };
    }
}

4. Web开发全攻略

4.1 RESTful API开发

基础控制器示例

@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
    
    private final ProductService productService;
    
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
    
    @GetMapping
    public ResponseEntity<List<Product>> getAllProducts() {
        return ResponseEntity.ok(productService.findAll());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        return productService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
        Product saved = productService.save(product);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(saved.getId())
                .toUri();
        return ResponseEntity.created(location).body(saved);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(
            @PathVariable Long id, 
            @Valid @RequestBody Product product) {
        if (!productService.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        product.setId(id);
        return ResponseEntity.ok(productService.save(product));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        productService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}

4.2 异常处理

全局异常处理器

// @RestControllerAdvice 是一个特殊的 @Component 注解,用于定义全局异常处理类。
// 它可以捕获并处理所有 @RestController 注解标注的控制器中抛出的异常,
// 并以统一的方式返回错误响应给客户端。
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理 ProductNotFoundException 异常的方法。
     * 当控制器中抛出 ProductNotFoundException 异常时,会自动调用此方法进行处理。
     *
     * @param ex 捕获到的 ProductNotFoundException 异常实例
     * @return 返回一个 ProblemDetail 对象,包含异常的详细信息,用于返回给客户端
     */
    @ExceptionHandler(ProductNotFoundException.class)
    public ProblemDetail handleProductNotFound(ProductNotFoundException ex) {
        // 创建一个 ProblemDetail 对象,根据 HTTP 状态码(这里是 404 Not Found)和异常消息来初始化。
        // ProblemDetail 是 Spring 提供的用于封装错误信息的类,方便以标准的方式返回错误响应。
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND, ex.getMessage());
        // 设置 ProblemDetail 对象的标题,用于简要描述错误类型
        problemDetail.setTitle("Product Not Found");
        // 为 ProblemDetail 对象添加一个属性,记录当前时间戳,方便追踪错误发生的时间
        problemDetail.setProperty("timestamp", Instant.now());
        // 为 ProblemDetail 对象添加一个属性,记录错误代码,方便客户端识别和处理特定类型的错误
        problemDetail.setProperty("errorCode", "PRODUCT_NOT_FOUND");
        // 返回包含详细错误信息的 ProblemDetail 对象
        return problemDetail;
    }

    /**
     * 处理 MethodArgumentNotValidException 异常的方法。
     * 当控制器中的方法参数验证失败时,会抛出此异常,此方法会对其进行处理。
     *
     * @param ex 捕获到的 MethodArgumentNotValidException 异常实例
     * @return 返回一个 ProblemDetail 对象,包含验证失败的详细信息,用于返回给客户端
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handleValidationException(MethodArgumentNotValidException ex) {
        // 创建一个 ProblemDetail 对象,根据 HTTP 状态码(这里是 400 Bad Request)和验证失败的提示信息来初始化
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
            HttpStatus.BAD_REQUEST, "Validation failed");
        // 设置 ProblemDetail 对象的标题,用于简要描述错误类型
        problemDetail.setTitle("Validation Error");
        // 为 ProblemDetail 对象添加一个属性,记录当前时间戳,方便追踪错误发生的时间
        problemDetail.setProperty("timestamp", Instant.now());

        // 从异常中获取绑定结果,绑定结果中包含了所有验证失败的字段错误信息
        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
        // 使用 Java 8 的 Stream API 对字段错误信息进行处理,将每个字段错误转换为一个包含字段名和错误消息的 Map
        List<Map<String, String>> errors = fieldErrors.stream()
            .map(fieldError -> Map.of(
                "field", fieldError.getField(),
                "message", fieldError.getDefaultMessage()))
            // 将转换后的 Map 收集到一个列表中
            .collect(Collectors.toList());

        // 为 ProblemDetail 对象添加一个属性,记录所有验证失败的字段错误信息,方便客户端查看具体的错误情况
        problemDetail.setProperty("errors", errors);
        // 返回包含详细验证错误信息的 ProblemDetail 对象
        return problemDetail;
    }
}

4.3 静态资源与模板引擎

静态资源配置

spring:
  mvc:
    static-path-pattern: /static/**
  web:
    resources:
      static-locations: 
        - classpath:/static/
        - file:/opt/app/static/

Thymeleaf模板示例

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Product List</title>
</head>
<body>
    <h1>Products</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="product : ${products}">
                <td th:text="${product.id}">1</td>
                <td th:text="${product.name}">Sample</td>
                <td th:text="${#numbers.formatDecimal(product.price, 1, 2)}">0.00</td>
            </tr>
        </tbody>
    </table>
</body>
</html>

4.4 文件上传与下载

文件上传控制器

@RestController
@RequestMapping("/api/v1/files")
public class FileController {
    
    private final FileStorageService storageService;
    
    public FileController(FileStorageService storageService) {
        this.storageService = storageService;
    }
    
    @PostMapping("/upload")
    public ResponseEntity<FileInfo> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            FileInfo fileInfo = storageService.store(file);
            return ResponseEntity.ok(fileInfo);
        } catch (IOException e) {
            throw new FileStorageException("Failed to store file", e);
        }
    }
    
    @GetMapping("/download/{filename:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        Resource resource = storageService.loadAsResource(filename);
        
        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                   "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
    }
}

4.5 跨域配置

全局CORS配置

// @Configuration 注解表明这是一个 Spring 配置类,Spring 会对其进行处理,
// 可以在这个类中定义 Bean 以及进行一些配置操作,类似于传统的 XML 配置文件。
@Configuration
// 实现 WebMvcConfigurer 接口,用于自定义 Spring MVC 的配置,
// 这里主要是为了配置跨域资源共享(CORS)相关的设置。
public class CorsConfig implements WebMvcConfigurer {

    /**
     * 重写 addCorsMappings 方法,用于配置跨域资源共享(CORS)规则。
     * 跨域资源共享允许浏览器在跨源请求时,访问不同域名下的资源。
     *
     * @param registry CorsRegistry 对象,用于注册跨域映射规则
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 添加一个跨域映射规则,匹配所有以 /api/ 开头的请求路径
        registry.addMapping("/api/**")
            // 指定允许访问该资源的源(域名),这里允许 https://example.com 和 https://www.example.com 这两个域名的请求
            .allowedOrigins("https://example.com", "https://www.example.com")
            // 指定允许的 HTTP 请求方法,这里允许 GET、POST、PUT、DELETE 和 OPTIONS 方法
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            // 指定允许的请求头,* 表示允许所有请求头
            .allowedHeaders("*")
            // 指定允许客户端访问的响应头,这里允许客户端访问 Authorization 和 Content-Disposition 这两个响应头
            .exposedHeaders("Authorization", "Content-Disposition")
            // 允许请求携带凭证(如 cookies、HTTP 认证等),设置为 true 表示允许
            .allowCredentials(true)
            // 设置预检请求(OPTIONS 请求)的缓存时间,单位为秒,这里设置为 3600 秒(即 1 小时),
            // 表示在 1 小时内,浏览器可以使用缓存的预检请求结果,而不需要再次发送预检请求
            .maxAge(3600);
    }
}

方法级CORS配置

// @RestController 是 Spring 框架中的一个注解,它是 @Controller 和 @ResponseBody 的组合。
// 表明该类是一个 RESTful 风格的控制器,用于处理 HTTP 请求并返回 JSON 或 XML 等格式的数据。
@RestController
// @RequestMapping 注解用于映射 HTTP 请求到控制器的处理方法。
// 这里将所有以 /api/v1/users 开头的请求映射到这个控制器类中。
@RequestMapping("/api/v1/users")
public class UserController {

    /**
     * @CrossOrigin 注解用于解决跨域资源共享(CORS)问题。
     * 跨域请求是指浏览器从一个域名的网页去请求另一个域名的资源时,由于浏览器的同源策略会受到限制。
     * 通过该注解可以指定允许跨域访问的相关信息。
     *
     * origins 属性指定允许访问该资源的源(域名),这里只允许 https://client.example.com 这个域名的请求进行跨域访问。
     * methods 属性指定允许的 HTTP 请求方法,这里只允许 GET 和 POST 请求跨域。
     * allowedHeaders 属性指定允许的请求头,* 表示允许所有请求头。
     */
    @CrossOrigin(origins = "https://client.example.com",
            methods = {RequestMethod.GET, RequestMethod.POST},
            allowedHeaders = "*")
    // @GetMapping 是 @RequestMapping(method = RequestMethod.GET) 的缩写,
    // 用于映射 HTTP GET 请求到该方法,即当客户端发送一个 GET 请求到 /api/v1/users 时,会调用此方法。
    @GetMapping
    /**
     * 获取所有用户的信息。
     * 该方法返回一个包含 User 对象的列表,用于表示所有用户的信息。
     * 方法体中的 // ... 表示具体的业务逻辑代码,例如从数据库中查询所有用户信息等,这里省略了具体实现。
     *
     * @return 包含所有用户信息的列表
     */
    public List<User> getAllUsers() {
        // ... 这里应该实现具体的业务逻辑,例如从数据库中查询所有用户信息
        return null;
    }
}

5. 数据访问与事务管理

5.1 JDBC与JPA配置

application.yml配置示例

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10
      connection-timeout: 30000
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect

5.2 JPA实体与Repository

实体类示例

@Entity
@Table(name = "products")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 100)
    private String name;
    
    @Column(nullable = false, precision = 10, scale = 2)
    private BigDecimal price;
    
    @Column(length = 500)
    private String description;
    
    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private ProductStatus status;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
    
    @Version
    private Integer version;
}

public enum ProductStatus {
    ACTIVE, DISCONTINUED, OUT_OF_STOCK
}

Repository接口

public interface ProductRepository extends JpaRepository<Product, Long> {
    
    List<Product> findByStatus(ProductStatus status);
    
    @Query("SELECT p FROM Product p WHERE p.price BETWEEN :min AND :max")
    List<Product> findByPriceRange(@Param("min") BigDecimal min, 
                                 @Param("max") BigDecimal max);
    
    @Query(value = "SELECT * FROM products WHERE name LIKE %:keyword%", 
          nativeQuery = true)
    Page<Product> searchByName(@Param("keyword") String keyword, Pageable pageable);
    
    @Modifying
    @Query("UPDATE Product p SET p.price = p.price * :factor WHERE p.status = :status")
    int updatePriceByStatus(@Param("status") ProductStatus status, 
                          @Param("factor") BigDecimal factor);
}

5.3 事务管理

声明式事务

// @Service 注解表明这是一个 Spring 服务类,通常用于处理业务逻辑。
// Spring 会自动扫描并将其注册为一个 Bean,以便在其他组件中进行依赖注入。
@Service
// @RequiredArgsConstructor 是 Lombok 提供的注解,
// 它会自动生成一个包含所有 final 字段的构造函数,方便进行依赖注入。
@RequiredArgsConstructor
// @Transactional 注解将整个类的方法都纳入事务管理,
// 意味着该类中的所有公共方法都会在事务中执行。
@Transactional
public class OrderService {

    // 注入订单仓库,用于与订单数据进行交互,如保存、查询订单等操作。
    private final OrderRepository orderRepository;
    // 注入产品仓库,用于与产品数据进行交互,如查询产品信息等操作。
    private final ProductRepository productRepository;
    // 注入库存服务,用于处理库存相关的业务逻辑,如检查库存、扣减库存等操作。
    private final InventoryService inventoryService;

    /**
     * 创建订单的方法。
     * 
     * @Transactional(rollbackFor = {InventoryException.class, PaymentException.class}) 
     * 该注解表明这个方法在事务中执行,并且当抛出 InventoryException 或 PaymentException 异常时会进行回滚操作。
     * 
     * @param request 包含订单请求信息的对象
     * @return 创建好的订单对象
     */
    @Transactional(rollbackFor = {InventoryException.class, PaymentException.class})
    public Order createOrder(OrderRequest request) {
        // 调用库存服务的检查库存方法,检查订单请求中的商品是否有足够的库存。
        inventoryService.checkInventory(request.getItems());

        // 调用 createOrderFromRequest 方法,根据订单请求信息创建订单对象。
        Order order = createOrderFromRequest(request);
        // 将创建好的订单对象保存到数据库中。
        order = orderRepository.save(order);

        // 调用库存服务的扣减库存方法,扣减订单中商品的库存。
        inventoryService.reduceInventory(request.getItems());

        // 假设存在 paymentService 来处理支付逻辑,调用其处理支付方法完成订单支付。
        // 这里代码中未定义 paymentService,可能是代码省略或者有其他依赖注入。
        paymentService.processPayment(order);

        // 返回创建好的订单对象。
        return order;
    }

    /**
     * 根据订单 ID 获取包含详细信息的订单。
     * 
     * @Transactional(readOnly = true) 该注解表明这个方法在只读事务中执行,
     * 可以提高性能,因为只读事务不需要进行锁操作。
     * 
     * @param orderId 订单的 ID
     * @return 包含详细信息的订单对象,如果未找到则抛出 OrderNotFoundException 异常
     */
    @Transactional(readOnly = true)
    public Order getOrderWithDetails(Long orderId) {
        // 调用订单仓库的 findByIdWithDetails 方法,根据订单 ID 查询包含详细信息的订单。
        // 如果未找到订单,则抛出 OrderNotFoundException 异常。
        return orderRepository.findByIdWithDetails(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
    }

    /**
     * 取消订单的方法。
     * 
     * @Transactional(propagation = Propagation.REQUIRES_NEW) 该注解表明这个方法会开启一个新的事务,
     * 无论外部是否存在事务,都会独立执行。
     * 
     * @param orderId 要取消的订单的 ID
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void cancelOrder(Long orderId) {
        // 调用订单仓库的 findById 方法,根据订单 ID 查询订单。
        // 如果未找到订单,则抛出 OrderNotFoundException 异常。
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));

        // 检查订单的状态,如果订单状态不是已取消状态,则进行取消操作。
        if (order.getStatus() != OrderStatus.CANCELLED) {
            // 将订单状态设置为已取消。
            order.setStatus(OrderStatus.CANCELLED);
            // 将更新后的订单保存到数据库中。
            orderRepository.save(order);
            // 调用库存服务的恢复库存方法,恢复订单中商品的库存。
            inventoryService.restoreInventory(order.getItems());
        }
    }
}

5.4 多数据源配置

主数据源配置

// @Configuration 注解表明这是一个 Spring 配置类,Spring 会对其进行处理,
// 可以在这个类中定义 Bean 以及进行一些配置操作,类似于传统的 XML 配置文件。
@Configuration
// @EnableTransactionManagement 注解用于启用 Spring 的声明式事务管理功能。
// 它允许在服务层或其他组件中使用 @Transactional 注解来管理事务。
@EnableTransactionManagement
// @EnableJpaRepositories 注解用于启用 Spring Data JPA 仓库的自动扫描和注册功能。
// 它会自动创建仓库接口的实现类,并将其注册为 Spring Bean。
@EnableJpaRepositories(
    // 指定要扫描的仓库接口所在的基础包路径,这里是 com.example.repository.primary 包及其子包。
    basePackages = "com.example.repository.primary",
    // 指定要使用的 EntityManagerFactory 的引用名称,这里使用 primaryEntityManagerFactory。
    entityManagerFactoryRef = "primaryEntityManagerFactory",
    // 指定要使用的事务管理器的引用名称,这里使用 primaryTransactionManager。
    transactionManagerRef = "primaryTransactionManager"
)
/**
 * 主数据源配置类,用于配置主数据源、实体管理器工厂和事务管理器。
 */
public class PrimaryDataSourceConfig {

    /**
     * 创建主数据源的 Bean。
     * 
     * @Bean 注解将该方法返回的对象注册为 Spring Bean。
     * @Primary 注解表示当有多个相同类型的 Bean 时,优先使用这个 Bean。
     * @ConfigurationProperties 注解将配置文件中以 spring.datasource.primary 开头的属性绑定到数据源对象上。
     * 
     * @return 返回一个根据配置文件创建的数据源对象。
     */
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 使用 DataSourceBuilder 创建数据源对象,并根据配置文件中的属性进行初始化。
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建主实体管理器工厂的 Bean。
     * 
     * @Bean 注解将该方法返回的对象注册为 Spring Bean。
     * @Primary 注解表示当有多个相同类型的 Bean 时,优先使用这个 Bean。
     * 
     * @param builder 用于构建 EntityManagerFactory 的构建器对象。
     * @param dataSource 主数据源对象,通过 @Qualifier 注解指定使用名为 primaryDataSource 的 Bean。
     * @return 返回一个配置好的 LocalContainerEntityManagerFactoryBean 对象。
     */
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("primaryDataSource") DataSource dataSource) {
        return builder
            // 设置数据源为前面创建的主数据源。
            .dataSource(dataSource)
            // 指定要扫描的实体类所在的包路径,这里是 com.example.model.primary 包及其子包。
            .packages("com.example.model.primary")
            // 设置持久化单元的名称为 primary。
            .persistenceUnit("primary")
            // 设置 Hibernate 的相关属性,如 hbm2ddl.auto 用于自动更新数据库表结构,dialect 用于指定数据库方言。
            .properties(Map.of(
                "hibernate.hbm2ddl.auto", "update",
                "hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"
            ))
            // 构建并返回 LocalContainerEntityManagerFactoryBean 对象。
            .build();
    }

    /**
     * 创建主事务管理器的 Bean。
     * 
     * @Bean 注解将该方法返回的对象注册为 Spring Bean。
     * @Primary 注解表示当有多个相同类型的 Bean 时,优先使用这个 Bean。
     * 
     * @param entityManagerFactory 主实体管理器工厂对象,通过 @Qualifier 注解指定使用名为 primaryEntityManagerFactory 的 Bean。
     * @return 返回一个 JpaTransactionManager 对象,用于管理事务。
     */
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        // 创建 JpaTransactionManager 对象,并将主实体管理器工厂作为参数传入,用于管理事务。
        return new JpaTransactionManager(entityManagerFactory);
    }
}

从数据源配置

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = "com.example.repository.secondary",
    entityManagerFactoryRef = "secondaryEntityManagerFactory",
    transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDataSourceConfig {
    
    @Bean
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return builder
            .dataSource(dataSource)
            .packages("com.example.model.secondary")
            .persistenceUnit("secondary")
            .properties(Map.of(
                "hibernate.hbm2ddl.auto", "update",
                "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"
            ))
            .build();
    }
    
    @Bean
    public PlatformTransactionManager secondaryTransactionManager(
            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

6. 安全控制与认证授权

6.1 Spring Security基础配置

依赖添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

基础安全配置

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final UserDetailsService userDetailsService;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authenticationProvider(authenticationProvider())
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .accessDeniedHandler(new JwtAccessDeniedHandler())
            )
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable);
        
        return http.build();
    }
    
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

6.2 JWT认证实现

JWT工具类

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
    
    private final JwtProperties jwtProperties;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList()));
        
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpirationTime()))
            .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecretKey())
            .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
            .setSigningKey(jwtProperties.getSecretKey())
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtProperties.getSecretKey()).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            log.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            log.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            log.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            log.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            log.error("JWT claims string is empty");
        }
        return false;
    }
}

JWT过滤器

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);
            
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String username = tokenProvider.getUsernameFromToken(jwt);
                
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = 
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            log.error("Could not set user authentication in security context", ex);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

6.3 OAuth2集成

OAuth2客户端配置

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: your-google-client-id
            client-secret: your-google-client-secret
            scope: email, profile
          github:
            client-id: your-github-client-id
            client-secret: your-github-client-secret
            scope: user:email
        provider:
          google:
            issuer-uri: https://accounts.google.com
          github:
            issuer-uri: https://github.com
            user-name-attribute: name

OAuth2登录配置

@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login**", "/error**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService)
                )
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler)
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/").permitAll()
            );
        
        return http.build();
    }
    
    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
        DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
        
        return request -> {
            OAuth2User oAuth2User = delegate.loadUser(request);
            
            Map<String, Object> attributes = oAuth2User.getAttributes();
            String registrationId = request.getClientRegistration().getRegistrationId();
            
            // 根据不同提供商处理用户信息
            OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(
                registrationId, attributes);
            
            // 保存或更新用户信息到数据库
            User user = saveOrUpdateUser(userInfo);
            
            return new CustomOAuth2User(user, attributes);
        };
    }
}

7. 响应式编程支持

7.1 WebFlux基础

响应式控制器

@RestController
@RequestMapping("/api/reactive/products")
public class ReactiveProductController {
    
    private final ReactiveProductService productService;
    
    public ReactiveProductController(ReactiveProductService productService) {
        this.productService = productService;
    }
    
    @GetMapping
    public Flux<Product> getAllProducts() {
        return productService.findAll();
    }
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<Product>> getProductById(@PathVariable String id) {
        return productService.findById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<Product> createProduct(@Valid @RequestBody Mono<Product> productMono) {
        return productMono.flatMap(productService::save);
    }
    
    @PutMapping("/{id}")
    public Mono<ResponseEntity<Product>> updateProduct(
            @PathVariable String id, 
            @Valid @RequestBody Mono<Product> productMono) {
        return productMono
            .flatMap(product -> productService.update(id, product))
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteProduct(@PathVariable String id) {
        return productService.deleteById(id)
            .then(Mono.just(ResponseEntity.noContent().build()));
    }
    
    @GetMapping("/stream")
    @Produces(MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Product> streamProducts() {
        return productService.streamAll();
    }
}

7.2 响应式数据访问

响应式Repository

public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, String> {
    
    Flux<Product> findByStatus(ProductStatus status);
    
    @Query("{ 'price': { $gte: ?0, $lte: ?1 } }")
    Flux<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
    
    Flux<Product> findByNameContaining(String name);
    
    @Tailable
    Flux<Product> findWithTailableCursorBy();
}

响应式服务层

@Service
@RequiredArgsConstructor
public class ReactiveProductService {
    
    private final ReactiveProductRepository productRepository;
    
    public Flux<Product> findAll() {
        return productRepository.findAll()
            .delayElements(Duration.ofMillis(100)); // 模拟延迟
    }
    
    public Mono<Product> findById(String id) {
        return productRepository.findById(id)
            .switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
    }
    
    public Mono<Product> save(Product product) {
        return productRepository.save(product);
    }
    
    public Mono<Product> update(String id, Product product) {
        return productRepository.existsById(id)
            .flatMap(exists -> exists ? 
                Mono.just(product).flatMap(productRepository::save) : 
                Mono.error(new ProductNotFoundException(id)));
    }
    
    public Mono<Void> deleteById(String id) {
        return productRepository.deleteById(id);
    }
    
    public Flux<Product> streamAll() {
        return productRepository.findWithTailableCursorBy();
    }
}

7.3 WebClient使用

WebClient配置

@Configuration
public class WebClientConfig {
    
    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder
            .baseUrl("https://api.example.com")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .filter(logRequest())
            .filter(logResponse())
            .build();
    }
    
    private ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers().forEach((name, values) -> 
                values.forEach(value -> log.info("{}={}", name, value)));
            return Mono.just(clientRequest);
        });
    }
    
    private ExchangeFilterFunction logResponse() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            log.info("Response status: {}", clientResponse.statusCode());
            clientResponse.headers().asHttpHeaders().forEach((name, values) -> 
                values.forEach(value -> log.info("{}={}", name, value)));
            return Mono.just(clientResponse);
        });
    }
}

WebClient使用示例

@Service
@RequiredArgsConstructor
public class ExternalApiService {
    
    private final WebClient webClient;
    
    public Mono<Product> fetchProduct(String productId) {
        return webClient.get()
            .uri("/products/{id}", productId)
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, response -> 
                Mono.error(new ProductNotFoundException("Product not found with id: " + productId)))
            .onStatus(HttpStatus::is5xxServerError, response -> 
                Mono.error(new ServiceUnavailableException("External service unavailable")))
            .bodyToMono(Product.class)
            .timeout(Duration.ofSeconds(5))
            .retryWhen(Retry.backoff(3, Duration.ofMillis(100))
            .doOnError(e -> log.error("Error fetching product", e));
    }
    
    public Flux<Product> searchProducts(String keyword) {
        return webClient.get()
            .uri(uriBuilder -> uriBuilder
                .path("/products/search")
                .queryParam("q", keyword)
                .build())
            .retrieve()
            .bodyToFlux(Product.class);
    }
    
    public Mono<Product> createProduct(Product product) {
        return webClient.post()
            .uri("/products")
            .bodyValue(product)
            .retrieve()
            .bodyToMono(Product.class);
    }
}

8. 监控与管理端点

8.1 Actuator配置

基础配置

management:
  endpoint:
    health:
      show-details: always
      show-components: always
    info:
      enabled: true
    metrics:
      enabled: true
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus
      base-path: /manage
  health:
    db:
      enabled: true
    diskspace:
      enabled: true
    redis:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: ${spring.application.name}

自定义健康指标

@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    private final ExternalServiceClient serviceClient;
    
    public CustomHealthIndicator(ExternalServiceClient serviceClient) {
        this.serviceClient = serviceClient;
    }
    
    @Override
    public Health health() {
        try {
            String status = serviceClient.getStatus();
            if ("UP".equals(status)) {
                return Health.up()
                    .withDetail("responseTime", serviceClient.getResponseTime())
                    .build();
            } else {
                return Health.down()
                    .withDetail("reason", "Service returned status: " + status)
                    .build();
            }
        } catch (Exception e) {
            return Health.down(e)
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

8.2 自定义指标

计数器示例

@Service
public class OrderService {
    
    private final Counter orderCounter;
    private final DistributionSummary orderAmountSummary;
    private final Timer orderProcessingTimer;
    
    public OrderService(MeterRegistry meterRegistry) {
        this.orderCounter = meterRegistry.counter("orders.count");
        this.orderAmountSummary = DistributionSummary
            .builder("orders.amount")
            .description("Order amount distribution")
            .baseUnit("USD")
            .register(meterRegistry);
        this.orderProcessingTimer = Timer
            .builder("orders.processing.time")
            .description("Time taken to process orders")
            .register(meterRegistry);
    }
    
    @Transactional
    public Order createOrder(OrderRequest request) {
        return orderProcessingTimer.record(() -> {
            Order order = processOrder(request);
            
            // 增加计数器
            orderCounter.increment();
            
            // 记录订单金额
            orderAmountSummary.record(order.getTotalAmount().doubleValue());
            
            return order;
        });
    }
}

自定义指标端点

@Endpoint(id = "custommetrics")
@Component
public class CustomMetricsEndpoint {
    
    private final Map<String, Double> metrics = new ConcurrentHashMap<>();
    
    @ReadOperation
    public Map<String, Double> metrics() {
        return metrics;
    }
    
    @WriteOperation
    public void updateMetric(@Selector String name, double value) {
        metrics.put(name, value);
    }
    
    @DeleteOperation
    public void removeMetric(@Selector String name) {
        metrics.remove(name);
    }
}

8.3 链路追踪

Sleuth与Zipkin配置

spring:
  sleuth:
    sampler:
      probability: 1.0 # 采样率
    propagation:
      type: B3,W3C # 支持多种追踪头
  zipkin:
    base-url: http://localhost:9411
    sender:
      type: web
    service:
      name: order-service

自定义Span

@Service
@RequiredArgsConstructor
public class PaymentService {
    
    private final Tracer tracer;
    
    public PaymentResult processPayment(Order order) {
        // 创建自定义Span
        Span paymentSpan = tracer.nextSpan().name("process-payment").start();
        
        try (SpanInScope ws = tracer.withSpan(paymentSpan)) {
            paymentSpan.tag("order.id", order.getId().toString());
            paymentSpan.tag("payment.amount", order.getTotalAmount().toString());
            
            // 业务逻辑
            PaymentResult result = doPayment(order);
            
            paymentSpan.event("Payment processed");
            return result;
        } catch (Exception e) {
            paymentSpan.error(e);
            throw e;
        } finally {
            paymentSpan.end();
        }
    }
}

9. 测试策略与最佳实践

9.1 单元测试

服务层测试

@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
    
    @Mock
    private ProductRepository productRepository;
    
    @InjectMocks
    private ProductService productService;
    
    @Test
    void whenValidId_thenProductShouldBeFound() {
        // Given
        Long productId = 1L;
        Product mockProduct = new Product(productId, "Test Product", BigDecimal.valueOf(10.99));
        when(productRepository.findById(productId)).thenReturn(Optional.of(mockProduct));
        
        // When
        Product found = productService.getProductById(productId);
        
        // Then
        assertThat(found.getId()).isEqualTo(productId);
        assertThat(found.getName()).isEqualTo("Test Product");
    }
    
    @Test
    void whenInvalidId_thenThrowException() {
        // Given
        Long productId = 999L;
        when(productRepository.findById(productId)).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> productService.getProductById(productId))
            .isInstanceOf(ProductNotFoundException.class)
            .hasMessageContaining("Product not found with id: " + productId);
    }
    
    @Test
    void whenCreateProduct_thenProductShouldBeSaved() {
        // Given
        Product newProduct = new Product(null, "New Product", BigDecimal.valueOf(15.99));
        Product savedProduct = new Product(1L, "New Product", BigDecimal.valueOf(15.99));
        
        when(productRepository.save(any(Product.class))).thenReturn(savedProduct);
        
        // When
        Product result = productService.createProduct(newProduct);
        
        // Then
        assertThat(result.getId()).isNotNull();
        verify(productRepository, times(1)).save(any(Product.class));
    }
}

9.2 集成测试

Web层集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Transactional
class ProductControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Test
    void whenGetAllProducts_thenReturnProductList() throws Exception {
        // Given
        Product product1 = new Product(null, "Product 1", BigDecimal.valueOf(10.99));
        Product product2 = new Product(null, "Product 2", BigDecimal.valueOf(20.99));
        productRepository.saveAll(List.of(product1, product2));
        
        // When & Then
        mockMvc.perform(get("/api/products")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("#34;, hasSize(2)))
            .andExpect(jsonPath("$[0].name", is("Product 1")))
            .andExpect(jsonPath("$[1].name", is("Product 2")));
    }
    
    @Test
    void whenValidInput_thenCreateProduct() throws Exception {
        // Given
        Product product = new Product(null, "New Product", BigDecimal.valueOf(15.99));
        
        // When & Then
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(product)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name", is("New Product")));
        
        // Verify database
        List<Product> found = productRepository.findAll();
        assertThat(found).extracting(Product::getName).containsOnly("New Product");
    }
    
    @Test
    void whenInvalidInput_thenReturnBadRequest() throws Exception {
        // Given
        Product invalidProduct = new Product(null, "", BigDecimal.valueOf(-1));
        
        // When & Then
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidProduct)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.title", is("Validation Error")))
            .andExpect(jsonPath("$.errors[0].field", is("name")))
            .andExpect(jsonPath("$.errors[1].field", is("price")));
    }
}

9.3 测试切片

WebMvc测试切片

@WebMvcTest(ProductController.class)
@Import({ProductService.class, SecurityConfig.class})
class ProductControllerSliceTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private ProductRepository productRepository;
    
    @Test
    @WithMockUser(username = "admin", roles = {"ADMIN"})
    void whenAdminAccess_thenShouldReturnOk() throws Exception {
        when(productRepository.findAll()).thenReturn(List.of(
            new Product(1L, "Test", BigDecimal.TEN)));
        
        mockMvc.perform(get("/api/products"))
            .andExpect(status().isOk());
    }
    
    @Test
    @WithMockUser(username = "user", roles = {"USER"})
    void whenUserAccess_thenShouldReturnForbidden() throws Exception {
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"Test\",\"price\":10.99}"))
            .andExpect(status().isForbidden());
    }
}

DataJpa测试切片

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Test
    void whenFindByStatus_thenReturnProducts() {
        // Given
        Product activeProduct = new Product(null, "Active", BigDecimal.TEN, ProductStatus.ACTIVE);
        Product discontinuedProduct = new Product(null, "Discontinued", BigDecimal.ONE, ProductStatus.DISCONTINUED);
        
        entityManager.persist(activeProduct);
        entityManager.persist(discontinuedProduct);
        entityManager.flush();
        
        // When
        List<Product> found = productRepository.findByStatus(ProductStatus.ACTIVE);
        
        // Then
        assertThat(found).hasSize(1);
        assertThat(found.get(0).isEqualTo(activeProduct);
    }
}

10. 部署与性能优化

10.1 打包与部署

构建可执行JAR

mvn clean package
# 生成的jar位于target目录下
java -jar target/demo-0.0.1-SNAPSHOT.jar

Docker化部署

# 使用多阶段构建
FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /workspace/app
COPY . .
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=builder /workspace/app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

构建与运行Docker镜像

docker build -t demo-app .
docker run -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=prod" demo-app

10.2 性能优化

JVM调优参数

java -jar app.jar \
  -XX:+UseG1GC \
  -Xms512m \
  -Xmx1024m \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=35 \
  -XX:+AlwaysPreTouch \
  -Djava.security.egd=file:/dev/./urandom

Spring Boot性能配置

server:
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
    min-response-size: 1024
  tomcat:
    max-threads: 200
    min-spare-threads: 10
    connection-timeout: 5000
    accept-count: 100
spring:
  mvc:
    async:
      request-timeout: 30000
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 30
          fetch_size: 100

10.3 GraalVM原生镜像

原生镜像构建

  1. 添加Native Build Tools插件:
<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <version>0.9.27</version>
        </plugin>
    </plugins>
</build>
  1. 构建原生镜像:
mvn -Pnative native:compile
  1. 运行原生应用:
./target/demo

原生镜像配置提示

创建
src/main/resources/META-INF/native-image
目录,添加配置:

  • reflect-config.json - 反射配置
  • resource-config.json - 资源文件配置
  • proxy-config.json - 动态代理配置

示例reflect-config.json

[
  {
    "name": "com.example.model.Product",
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  }
]

总结

Spring Boot 3.x带来了许多激动人心的新特性和改进,从对现代Java特性的支持到更好的原生镜像兼容性。通过本指南,您应该已经掌握了从基础到高级的Spring Boot 3.x开发知识,包括:

  1. 自动配置原理与自定义
  2. Web开发最佳实践
  3. 数据访问与事务管理
  4. 安全认证与授权
  5. 响应式编程模型
  6. 应用监控与管理
  7. 测试策略
  8. 部署与优化

千万别三连,不然我会以为自己是网红(但你可以试试)。

头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。

点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2