一、核心概念

REST(Representational State Transfer)是一种基于 HTTP 协议的软件架构风格,用于构建可伸缩、松耦合的 Web 服务。Spring Boot 通过 Spring Web MVC 模块提供了强大的支持来构建 RESTful APIs。

核心概念

  1. 资源 (Resource)

    • REST 的核心是“资源”。任何可以被命名的东西都可以是一个资源,如用户 (User)、订单 (Order)、产品 (Product)。
    • 资源通过 URI (Uniform Resource Identifier) 来唯一标识,例如 /api/users, /api/users/123
  2. HTTP 方法 (HTTP Methods / Verbs)

    • 使用标准的 HTTP 方法来对资源执行 CRUD (Create, Read, Update, Delete) 操作:
      • GET: 读取/获取资源。安全且幂等
      • POST: 创建新资源。非幂等(每次调用可能创建新资源)。
      • PUT: 完全更新一个已知资源(或创建)。幂等(多次执行效果相同)。
      • PATCH: 部分更新一个已知资源。非幂等(但通常设计为幂等)。
      • DELETE: 删除一个资源。幂等
  3. 状态码 (HTTP Status Codes)

    • 服务器通过 HTTP 状态码告知客户端请求的结果。
    • 常用状态码:
      • 200 OK: 请求成功(GET, PUT, PATCH, DELETE)。
      • 201 Created: 资源创建成功(POST),响应体通常包含新资源。
      • 204 No Content: 请求成功,但响应体无内容(常见于 DELETE, PUT)。
      • 400 Bad Request: 客户端请求语法错误或参数无效。
      • 401 Unauthorized: 未认证(缺少或无效凭据)。
      • 403 Forbidden: 已认证,但无权限访问。
      • 404 Not Found: 请求的资源不存在。
      • 405 Method Not Allowed: 请求方法不被允许。
      • 409 Conflict: 请求与当前资源状态冲突(如创建已存在的资源)。
      • 422 Unprocessable Entity: 语义错误(如验证失败)。
      • 500 Internal Server Error: 服务器内部错误。
  4. 请求/响应体 (Request/Response Body)

    • 通常使用 JSON 格式传输数据。Spring Boot 默认使用 Jackson 库进行序列化/反序列化。
    • 请求头 Content-Type 指定请求体格式(如 application/json)。
    • 响应头 Content-Type 指定响应体格式。
  5. 无状态 (Stateless)

    • REST 要求服务器不保存客户端状态。每个请求必须包含处理该请求所需的所有信息(如通过 Authorization 头传递令牌)。
  6. HATEOAS (Hypermedia as the Engine of Application State)

    • 可选原则。在响应中包含指向相关资源的链接(_links),使客户端能发现 API 的导航路径。

二、操作步骤(非常详细)

我们将构建一个管理 User 资源的 REST API。

步骤 1:创建 Spring Boot 项目

  1. 访问 Spring Initializr
  2. 选择:
    • Project: Maven / Gradle
    • Language: Java
    • Spring Boot: 最新稳定版 (如 3.x)
    • Project Metadata:
      • Group: com.example
      • Artifact: rest-demo
      • Name: rest-demo
      • Package name: com.example.restdemo
      • Packaging: Jar
      • Java: 17 (或 11, 17+ 推荐)
  3. Dependencies 中添加:
    • Spring Web (核心 Web 支持)
    • Spring Data JPA (持久化,可选,用于演示数据库交互)
    • H2 Database (内存数据库,用于演示,可选)
    • Spring Boot DevTools (开发工具,可选)
  4. 点击 "Generate" 下载项目,解压并用 IDE (如 IntelliJ IDEA) 打开。

步骤 2:项目结构概览

rest-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com.example.restdemo/
│   │   │       ├── RestDemoApplication.java  // 主启动类
│   │   │       ├── controller/              // Controller 层
│   │   │       ├── model/                   // 实体/数据模型
│   │   │       ├── repository/             // 数据访问层 (JPA)
│   │   │       └── service/                // 业务逻辑层
│   │   └── resources/
│   │       ├── application.properties      // 配置文件
│   │       └── static/                     // 静态资源
│   │       └── templates/                  // 模板
└── pom.xml / build.gradle

步骤 3:定义数据模型 (Model)

创建 User 实体类。

// src/main/java/com/example/restdemo/model/User.java
package com.example.restdemo.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;

@Entity // JPA 注解,标记为数据库实体
@Table(name = "users") // 指定数据库表名
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增
    private Long id;

    @NotBlank(message = "First name is mandatory")
    @Size(max = 50)
    @Column(nullable = false, length = 50)
    private String firstName;

    @NotBlank(message = "Last name is mandatory")
    @Size(max = 50)
    @Column(nullable = false, length = 50)
    private String lastName;

    @NotBlank(message = "Email is mandatory")
    @Email(message = "Email should be valid")
    @Size(max = 100)
    @Column(nullable = false, unique = true, length = 100)
    private String email;

    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;

    // Constructors
    public User() {
    }

    public User(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(LocalDateTime updatedAt) {
        this.updatedAt = updatedAt;
    }

    // JPA Callbacks for timestamps
    @PrePersist
    protected void onCreate() {
        LocalDateTime now = LocalDateTime.now();
        createdAt = now;
        updatedAt = now;
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }

    // toString, equals, hashCode (可选,用于调试)
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", createdAt=" + createdAt +
                ", updatedAt=" + updatedAt +
                '}';
    }
}

步骤 4:定义数据访问层 (Repository)

创建 UserRepository 接口。

// src/main/java/com/example/restdemo/repository/UserRepository.java
package com.example.restdemo.repository;

import com.example.restdemo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository // 标记为 Spring Bean (可选,JpaRepository 已包含)
public interface UserRepository extends JpaRepository<User, Long> {
    // Spring Data JPA 自动提供 findById, save, deleteById, findAll 等方法

    // 自定义查询方法:根据 email 查找用户
    Optional<User> findByEmail(String email);
}

步骤 5:定义业务逻辑层 (Service)

创建 UserService 接口和实现类。

// src/main/java/com/example/restdemo/service/UserService.java
package com.example.restdemo.service;

import com.example.restdemo.model.User;

import java.util.List;
import java.util.Optional;

public interface UserService {
    List<User> getAllUsers();
    Optional<User> getUserById(Long id);
    User createUser(User user);
    User updateUser(Long id, User userDetails);
    void deleteUser(Long id);
}
// src/main/java/com/example/restdemo/service/UserServiceImpl.java
package com.example.restdemo.service;

import com.example.restdemo.exception.ResourceNotFoundException;
import com.example.restdemo.model.User;
import com.example.restdemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service // 标记为 Spring Service Bean
@Transactional // 声明式事务管理
public class UserServiceImpl implements UserService {

    @Autowired // 或使用构造函数注入 (推荐)
    private UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }

    @Override
    public User createUser(User user) {
        // 检查 email 是否已存在
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new RuntimeException("Email already exists");
        }
        return userRepository.save(user);
    }

    @Override
    public User updateUser(Long id, User userDetails) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));

        // 更新字段
        user.setFirstName(userDetails.getFirstName());
        user.setLastName(userDetails.getLastName());
        user.setEmail(userDetails.getEmail());
        // 注意:createdAt 不应被更新

        return userRepository.save(user);
    }

    @Override
    public void deleteUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
        userRepository.delete(user);
    }
}

步骤 6:创建异常类 (Exception)

创建自定义异常 ResourceNotFoundException

// src/main/java/com/example/restdemo/exception/ResourceNotFoundException.java
package com.example.restdemo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND) // 自动设置 HTTP 状态码
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

步骤 7:创建全局异常处理器 (Global Exception Handler)

创建 GlobalExceptionHandler 来统一处理异常。

// src/main/java/com/example/restdemo/exception/GlobalExceptionHandler.java
package com.example.restdemo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice // 应用于所有 @Controller 或 @RestController
public class GlobalExceptionHandler {

    // 处理自定义的 ResourceNotFoundException
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, Object>> handleResourceNotFoundException(ResourceNotFoundException ex) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", System.currentTimeMillis());
        errorResponse.put("status", HttpStatus.NOT_FOUND.value());
        errorResponse.put("error", "Not Found");
        errorResponse.put("message", ex.getMessage());
        errorResponse.put("path", "/api/users"); // 可以更精确地获取请求路径

        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    // 处理 @Valid 校验失败的异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", System.currentTimeMillis());
        errorResponse.put("status", HttpStatus.UNPROCESSABLE_ENTITY.value());
        errorResponse.put("error", "Validation Failed");
        errorResponse.put("message", "Input validation failed");

        // 收集所有字段错误
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
                errors.put(error.getField(), error.getDefaultMessage())
        );
        errorResponse.put("details", errors);

        return new ResponseEntity<>(errorResponse, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    // 处理其他未预期的异常 (生产环境应更谨慎)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", System.currentTimeMillis());
        errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        errorResponse.put("error", "Internal Server Error");
        errorResponse.put("message", "An unexpected error occurred");
        // 生产环境不要暴露 ex.getMessage() 或堆栈
        // errorResponse.put("details", ex.getMessage());

        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

步骤 8:创建 REST Controller

创建 UserController

// src/main/java/com/example/restdemo/controller/UserController.java
package com.example.restdemo.controller;

import com.example.restdemo.model.User;
import com.example.restdemo.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController // 组合注解:@Controller + @ResponseBody
@RequestMapping("/api/users") // 基础路径
public class UserController {

    @Autowired // 或使用构造函数注入 (推荐)
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // GET /api/users - 获取所有用户
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return new ResponseEntity<>(users, HttpStatus.OK);
    }

    // GET /api/users/{id} - 根据 ID 获取单个用户
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        Optional<User> user = userService.getUserById(id);
        return user.map(value -> new ResponseEntity<>(value, HttpStatus.OK))
                .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
        // 或者直接抛出异常,由 GlobalExceptionHandler 处理
        // User user = userService.getUserById(id)
        //     .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
        // return ResponseEntity.ok(user);
    }

    // POST /api/users - 创建新用户
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED) // 直接设置状态码
    public User createUser(@Valid @RequestBody User user) {
        // @Valid 触发 Bean Validation
        return userService.createUser(user);
    }

    // PUT /api/users/{id} - 完全更新用户
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }

    // PATCH /api/users/{id} - 部分更新用户 (示例:只更新 email)
    @PatchMapping("/{id}")
    public ResponseEntity<User> partialUpdateUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
        // 注意:这里简化处理,实际项目中可能需要更精细的逻辑或使用专门的 DTO
        Optional<User> userOpt = userService.getUserById(id);
        if (userOpt.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        User user = userOpt.get();
        // 示例:只允许更新 email
        if (updates.containsKey("email")) {
            String newEmail = (String) updates.get("email");
            // 简单验证 (实际应用中应更严格)
            if (newEmail != null && !newEmail.trim().isEmpty()) {
                user.setEmail(newEmail);
            }
        }
        // ... 可以添加其他字段的更新逻辑

        User savedUser = userService.createUser(user); // 复用 createService 的 save 方法
        return ResponseEntity.ok(savedUser);
    }

    // DELETE /api/users/{id} - 删除用户
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

步骤 9:配置数据库 (application.properties)

# src/main/resources/application.properties

# --- H2 Database (for demo) ---
# 启用 H2 控制台 (开发环境)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# --- JPA / Hibernate ---
# 自动创建/更新表结构
spring.jpa.hibernate.ddl-auto=update
# 显示 SQL
spring.jpa.show-sql=true
# 格式化 SQL
spring.jpa.properties.hibernate.format_sql=true
# 方言
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# --- Server ---
# 服务器端口
server.port=8080

步骤 10:启动应用并测试

  1. 运行 RestDemoApplication.javamain 方法。
  2. 应用启动后,访问 http://localhost:8080/h2-console (如果使用 H2),连接到 jdbc:h2:mem:testdb
  3. 使用 curl, Postman, 或 httpie 测试 API:
# 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"firstName":"John", "lastName":"Doe", "email":"john.doe@example.com"}'

# 获取所有用户
curl -X GET http://localhost:8080/api/users

# 获取单个用户
curl -X GET http://localhost:8080/api/users/1

# 更新用户 (PUT)
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Jane", "lastName":"Doe", "email":"jane.doe@example.com"}'

# 部分更新用户 (PATCH)
curl -X PATCH http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"email":"new.email@example.com"}'

# 删除用户
curl -X DELETE http://localhost:8080/api/users/1

三、常见错误与解决方案

错误 原因 解决方案
404 Not Found URL 路径错误、@RequestMapping 路径拼写错误、Controller 未被扫描到(缺少 @ComponentScan 或包路径不对)。 1. 检查 URL 路径和 @RequestMapping/@GetMapping 等注解。2. 确保 Controller 类在主应用类的包或子包下。3. 检查 @RestController 注解。
405 Method Not Allowed 请求方法(GET/POST/PUT/DELETE)与 Controller 方法不匹配。 检查 @GetMapping, @PostMapping 等注解是否正确。
400 Bad Request 请求体 JSON 格式错误、缺少必需字段、数据类型不匹配。 1. 检查 JSON 语法(逗号、引号)。2. 确保必需字段存在。3. 检查数据类型(字符串用引号,数字不用)。
422 Unprocessable Entity @Valid 校验失败(如 @NotBlank, @Email 不满足)。 1. 检查请求体数据是否符合实体类的校验注解要求。2. 查看响应体中的 details 字段获取具体错误信息。
500 Internal Server Error 代码中发生未处理的异常(如空指针、数据库错误)。 1. 查看服务器日志获取详细错误堆栈。2. 确保有适当的异常处理(@ExceptionHandler, @RestControllerAdvice)。
HTTP Status 200 with empty body @ResponseBody 缺失(但 @RestController 已包含)、方法返回 void 但期望有响应体。 1. 确认使用 @RestController@ResponseBody。2. 对于 POST 创建资源,应返回 201 Created 和资源本身。
Jackson Serialization Error 实体类有循环引用(如 UserList<Order>OrderUser)、缺少无参构造函数、final 字段。 1. 使用 @JsonIgnore@JsonManagedReference/@JsonBackReference 处理循环引用。2. 确保实体类有 public 无参构造函数。3. 使用 @JsonProperty 处理 final 字段。

四、注意事项

  1. @RestController vs @Controller@RestController@Controller + @ResponseBody 的组合,所有方法返回值都会直接写入响应体。如果使用 @Controller,需要在每个需要返回 JSON 的方法上加 @ResponseBody
  2. 路径变量 (@PathVariable):用于获取 URL 路径中的动态部分(如 /users/{id} 中的 id)。
  3. 请求体 (@RequestBody):用于将 HTTP 请求体中的 JSON 数据反序列化为 Java 对象。
  4. 请求参数 (@RequestParam):用于获取 URL 查询参数(如 /users?name=john)。
  5. @Valid 和 Bean Validation:在 @RequestBody 参数前加 @Valid 可触发 JSR-380 (Hibernate Validator) 校验。确保添加了 jakarta.validation 依赖(Spring Boot Web 通常已包含)。
  6. ResponseEntity:提供了对 HTTP 响应的完全控制(状态码、头、体)。@ResponseStatus 可以直接在方法上设置状态码。
  7. @Transactional:在 Service 层使用,确保数据库操作在事务中执行。
  8. API 版本控制:建议在 URL 路径中加入版本号,如 /api/v1/users,便于未来 API 演进。
  9. 安全性:生产环境必须集成 Spring Security 进行认证和授权。
  10. CORS (跨域):如果前端和后端在不同域名/端口,需要配置 CORS。可使用 @CrossOrigin 注解或全局配置。

五、使用技巧

  1. @Data (Lombok):使用 Lombok 的 @Data 注解可以自动生成 getter, setter, toString, equals, hashCode,减少样板代码。
  2. @NoArgsConstructor, @AllArgsConstructor (Lombok):生成无参和全参构造函数。
  3. @Builder (Lombok):生成构建者模式。
  4. @Slf4j (Lombok):生成日志记录器 log
  5. @RequestMapping(produces = "application/json"):指定 Controller 或方法只产生 JSON 响应。
  6. @RequestMapping(consumes = "application/json"):指定 Controller 或方法只消费 JSON 请求。
  7. @RequestParam 默认值@RequestParam(defaultValue = "0") int page
  8. @RequestHeader:获取 HTTP 请求头。
  9. @CookieValue:获取 Cookie 值。
  10. @MatrixVariable:处理矩阵变量(较少用)。

六、最佳实践

  1. 分层架构:清晰分离 Controller (处理 HTTP)、Service (业务逻辑)、Repository (数据访问)。
  2. 使用 DTO (Data Transfer Object)
    • 避免直接暴露实体:不要直接将 JPA 实体作为 API 的请求/响应体返回,尤其是在有敏感字段(如密码)或关联关系时。
    • 创建专门的 DTO:为每个 API 操作创建输入 DTO (UserCreateRequest, UserUpdateRequest) 和输出 DTO (UserResponse)。
    • 使用 MapStruct 或 ModelMapper:简化 DTO 与实体之间的转换。
  3. 统一错误响应格式:使用 @RestControllerAdvice@ExceptionHandler 定义统一的错误响应结构(如前面 GlobalExceptionHandler 示例)。
  4. API 文档:集成 Springdoc OpenAPI (Swagger) 自动生成 API 文档。
    • 依赖:springdoc-openapi-starter-webmvc-ui
    • 访问:http://localhost:8080/swagger-ui.html
  5. 输入验证:使用 @Valid 和 Bean Validation 注解进行声明式校验。
  6. 幂等性:确保 PUTDELETE 操作是幂等的。
  7. 资源命名:使用名词(复数形式)表示资源集合,如 /users, /orders。避免使用动词。
  8. HTTP 状态码:正确使用状态码,让客户端能准确理解结果。
  9. 版本控制:在 URL 路径中包含 API 版本(/api/v1/users)。
  10. 日志记录:在关键操作(如创建、更新、删除)中添加日志。
  11. 安全性:始终集成 Spring Security,实现认证(如 JWT)和授权。
  12. HATEOAS (可选):对于成熟的 API,考虑使用 Spring HATEOAS 提供链接。

七、性能优化

  1. 数据库优化
    • N+1 查询问题:使用 @EntityGraph, JOIN FETCH, 或 @Query 明确指定关联加载策略(EAGER 谨慎使用)。
    • 索引:在经常查询的字段上创建数据库索引。
    • 分页:对于返回列表的 API,必须支持分页。使用 PageablePage
      // Controller
      @GetMapping
      public ResponseEntity<Page<UserResponse>> getAllUsers(Pageable pageable) {
          Page<User> usersPage = userService.getAllUsers(pageable);
          Page<UserResponse> responses = usersPage.map(user -> modelMapper.map(user, UserResponse.class));
          return ResponseEntity.ok(responses);
      }
      
  2. 缓存
    • 使用 @Cacheable, @CachePut, @CacheEvict (Spring Cache Abstraction) 缓存频繁读取且不常变的数据(如配置信息、热点数据)。
    • 集成 Redis 作为缓存后端。
  3. 异步处理
    • 对于耗时较长的操作(如发送邮件、生成报告),使用 @Async 将其放入异步线程池执行,立即返回 202 Accepted 状态码。
  4. GZIP 压缩:在 application.properties 中启用响应压缩。
    server.compression.enabled=true
    server.compression.mime-types=application/json,text/html,text/xml,text/plain
    
  5. 连接池优化:配置 HikariCP 等连接池参数(maximumPoolSize, connectionTimeout)。
  6. 避免大对象序列化:避免返回包含大量数据或深层嵌套的对象。使用分页和投影(Projection)。
  7. 使用 ResponseEntityHttpStatus:直接返回 ResponseEntity.status(HttpStatus.CREATED).body(user) 比抛出异常有时更高效(异常处理有开销)。
  8. 监控:集成 Spring Boot Actuator,监控应用健康、指标(如 HTTP 请求延迟)。

总结

构建标准的 Spring Boot REST API 需要遵循 REST 原则,合理使用 Spring MVC 注解,并注重分层、安全、错误处理和性能。

快速上手关键点

  1. @RestController + @RequestMapping 定义控制器。
  2. @GetMapping/@PostMapping/@PutMapping/@DeleteMapping 映射 HTTP 方法。
  3. @PathVariable 获取路径参数,@RequestBody 接收 JSON 请求体。
  4. ResponseEntity@ResponseStatus 控制响应。
  5. @Valid + Bean Validation 进行输入校验。
  6. @RestControllerAdvice + @ExceptionHandler 统一处理异常。
  7. Service 层 处理业务逻辑,Repository 层 访问数据。
  8. DTO 模式 隔离内外模型。
  9. API 文档 (Springdoc OpenAPI)。
  10. 分页缓存 是性能关键。

遵循这些实践,你将能构建出健壮、可维护、高性能的 RESTful 服务。