全部笔记All notes

Spring MVC RESTful API 开发完整指南

阅读 7m 45s7m 45s read

概述

RESTful API 是现代 Web 应用开发的核心技术,基于 REST(Representational State Transfer)架构风格设计。Spring MVC 提供了强大的 RESTful API 开发支持,通过注解驱动的方式简化了 API 的构建过程。

核心特性

  • 统一接口: 使用标准 HTTP 方法(GET、POST、PUT、DELETE)
  • 无状态: 每个请求都包含处理所需的所有信息
  • 可缓存: 响应可以被缓存以提高性能
  • 分层系统: 支持代理、网关等中间层
  • 按需代码: 可选的客户端代码下载

应用场景

场景描述示例
微服务架构服务间通信接口用户服务、订单服务、支付服务
移动端API移动应用后端接口iOS、Android 应用API
前后端分离Web 前端数据接口Vue、React、Angular 应用
第三方集成对外开放的数据接口开放平台API、数据同步接口

💡 提示: RESTful API 是现代分布式系统的基础,掌握其设计和实现对于 Java Web 开发至关重要。

RESTful 架构基础

1. REST 设计原则

资源标识

# 良好的资源标识
GET    /api/users           # 获取用户列表
GET    /api/users/123       # 获取特定用户
POST   /api/users           # 创建新用户
PUT    /api/users/123       # 更新用户
DELETE /api/users/123       # 删除用户

# 嵌套资源
GET    /api/users/123/posts # 获取用户的文章
POST   /api/users/123/posts # 为用户创建文章

HTTP 状态码规范

状态码含义使用场景
2xx 成功
200OK成功返回数据
201Created资源创建成功
204No Content成功但无返回内容
4xx 客户端错误
400Bad Request请求参数错误
401Unauthorized未授权访问
403Forbidden禁止访问
404Not Found资源不存在
409Conflict资源冲突
5xx 服务器错误
500Internal Server Error服务器内部错误
503Service Unavailable服务不可用

2. 环境准备

项目依赖配置

<dependencies>
    <!-- Spring MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.21</version>
    </dependency>
    
    <!-- JSON 处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.3</version>
    </dependency>
    
    <!-- 数据验证 -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.2.3.Final</version>
    </dependency>
    
    <!-- Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.21</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Spring MVC 配置

web.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <display-name>RESTful API Application</display-name>
    
    <!-- 字符编码过滤器 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Spring MVC 前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
    
</web-app>

spring-mvc.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    <!-- 组件扫描 -->
    <context:component-scan base-package="com.example.api"/>
    
    <!-- 启用注解驱动 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <!-- JSON 消息转换器 -->
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                        <value>text/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    
    <!-- CORS 全局配置 -->
    <mvc:cors>
        <mvc:mapping path="/api/**"
                     allowed-origins="*"
                     allowed-methods="GET,POST,PUT,DELETE,OPTIONS"
                     allowed-headers="*"
                     allow-credentials="true"
                     max-age="3600"/>
    </mvc:cors>
    
    <!-- 静态资源处理 -->
    <mvc:resources mapping="/static/**" location="/static/"/>
    
</beans>

Java 配置方式:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.api")
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 配置 JSON 转换器
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        jsonConverter.setObjectMapper(objectMapper);
        converters.add(jsonConverter);
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

JSON 数据处理

1. JSON 基础概念

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,具有以下特点:

  • 易于阅读和编写
  • 语言无关性
  • 结构简单

JSON 数据结构

// 对象结构
{
    "id": 1,
    "username": "张三",
    "email": "zhangsan@example.com",
    "age": 25,
    "active": true
}

// 数组结构
[
    {
        "id": 1,
        "name": "用户1"
    },
    {
        "id": 2,
        "name": "用户2"
    }
]

// 嵌套结构
{
    "user": {
        "id": 1,
        "profile": {
            "name": "张三",
            "address": {
                "city": "北京",
                "district": "朝阳区"
            }
        },
        "posts": [
            {
                "id": 1,
                "title": "第一篇文章"
            }
        ]
    }
}

2. Java 实体类设计

package com.example.api.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.validation.constraints.*;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 用户实体类
 */
public class User {
    
    @JsonProperty("id")
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
    @JsonProperty("username")
    private String username;
    
    @Email(message = "邮箱格式不正确")
    @JsonProperty("email")
    private String email;
    
    @JsonIgnore  // 密码字段不参与JSON序列化
    private String password;
    
    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 100, message = "年龄不能大于100岁")
    @JsonProperty("age")
    private Integer age;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonProperty("createTime")
    private LocalDateTime createTime;
    
    @JsonProperty("active")
    private Boolean active;
    
    @JsonProperty("roles")
    private List<String> roles;
    
    @JsonProperty("profile")
    private UserProfile profile;
    
    // 构造函数
    public User() {
        this.createTime = LocalDateTime.now();
        this.active = true;
    }
    
    // getter 和 setter 方法
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    // ... 其他 getter 和 setter
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                ", createTime=" + createTime +
                ", active=" + active +
                '}';
    }
}

/**
 * 用户档案实体类
 */
public class UserProfile {
    
    @JsonProperty("realName")
    private String realName;
    
    @JsonProperty("phone")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    
    @JsonProperty("address")
    private String address;
    
    // getter 和 setter
}

3. JSON 序列化配置

Jackson 配置

@Configuration
public class JacksonConfig {
    
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        // 配置日期格式
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        
        // 忽略未知属性
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        
        // 忽略空值字段
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        
        // 配置驼峰命名转换
        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
        
        // 处理时间
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        return mapper;
    }
}

自定义序列化器

// 自定义日期序列化器
public class CustomDateSerializer extends JsonSerializer<Date> {
    
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    @Override
    public void serialize(Date date, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(dateFormat.format(date));
    }
}

// 使用自定义序列化器
public class User {
    
    @JsonSerialize(using = CustomDateSerializer.class)
    private Date createTime;
    
    // ...
}

REST 控制器开发

1. 基础 REST 控制器

package com.example.api.controller;

import com.example.api.entity.User;
import com.example.api.service.UserService;
import com.example.api.dto.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

/**
 * 用户 REST 控制器
 */
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    /**
     * 获取所有用户
     */
    @GetMapping
    public ResponseEntity<ApiResponse<List<User>>> getAllUsers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        
        List<User> users = userService.findAll(page, size, keyword);
        long total = userService.count(keyword);
        
        ApiResponse<List<User>> response = ApiResponse.success(users);
        response.setTotal(total);
        response.setPage(page);
        response.setSize(size);
        
        return ResponseEntity.ok(response);
    }
    
    /**
     * 根据ID获取用户
     */
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(ApiResponse.success(user));
    }
    
    /**
     * 创建新用户
     */
    @PostMapping
    public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody User user) {
        User createdUser = userService.create(user);
        return ResponseEntity.status(HttpStatus.CREATED)
                           .body(ApiResponse.success(createdUser));
    }
    
    /**
     * 更新用户信息
     */
    @PutMapping("/{id}")
    public ResponseEntity<ApiResponse<User>> updateUser(
            @PathVariable Long id, 
            @Valid @RequestBody User user) {
        
        user.setId(id);
        User updatedUser = userService.update(user);
        return ResponseEntity.ok(ApiResponse.success(updatedUser));
    }
    
    /**
     * 删除用户
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.ok(ApiResponse.success(null, "用户删除成功"));
    }
    
    /**
     * 批量删除用户
     */
    @DeleteMapping
    public ResponseEntity<ApiResponse<Void>> deleteUsers(@RequestBody List<Long> ids) {
        userService.deleteByIds(ids);
        return ResponseEntity.ok(ApiResponse.success(null, "批量删除成功"));
    }
    
    /**
     * 用户搜索
     */
    @GetMapping("/search")
    public ResponseEntity<ApiResponse<List<User>>> searchUsers(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "username") String field) {
        
        List<User> users = userService.search(keyword, field);
        return ResponseEntity.ok(ApiResponse.success(users));
    }
}

2. 统一响应格式

package com.example.api.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * 统一API响应格式
 */
public class ApiResponse<T> {
    
    @JsonProperty("code")
    private int code;
    
    @JsonProperty("message")
    private String message;
    
    @JsonProperty("data")
    private T data;
    
    @JsonProperty("timestamp")
    private long timestamp;
    
    @JsonProperty("total")
    private Long total;
    
    @JsonProperty("page")
    private Integer page;
    
    @JsonProperty("size")
    private Integer size;
    
    public ApiResponse() {
        this.timestamp = System.currentTimeMillis();
    }
    
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage("成功");
        response.setData(data);
        return response;
    }
    
    public static <T> ApiResponse<T> success(T data, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage(message);
        response.setData(data);
        return response;
    }
    
    public static <T> ApiResponse<T> error(int code, String message) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(code);
        response.setMessage(message);
        return response;
    }
    
    // getter 和 setter 方法
    public int getCode() {
        return code;
    }
    
    public void setCode(int code) {
        this.code = code;
    }
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public T getData() {
        return data;
    }
    
    public void setData(T data) {
        this.data = data;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
    
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    
    public Long getTotal() {
        return total;
    }
    
    public void setTotal(Long total) {
        this.total = total;
    }
    
    public Integer getPage() {
        return page;
    }
    
    public void setPage(Integer page) {
        this.page = page;
    }
    
    public Integer getSize() {
        return size;
    }
    
    public void setSize(Integer size) {
        this.size = size;
    }
}

HTTP 方法映射

1. 标准 HTTP 方法

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    // GET - 获取资源
    @GetMapping
    public ResponseEntity<List<Product>> getAllProducts() {
        // 获取所有产品
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        // 获取单个产品
    }
    
    // POST - 创建资源
    @PostMapping
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        // 创建新产品
    }
    
    // PUT - 完整更新资源
    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(
            @PathVariable Long id, 
            @RequestBody Product product) {
        // 完整更新产品
    }
    
    // PATCH - 部分更新资源
    @PatchMapping("/{id}")
    public ResponseEntity<Product> patchProduct(
            @PathVariable Long id,
            @RequestBody Map<String, Object> updates) {
        // 部分更新产品
    }
    
    // DELETE - 删除资源
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        // 删除产品
    }
    
    // HEAD - 获取资源头信息
    @RequestMapping(value = "/{id}", method = RequestMethod.HEAD)
    public ResponseEntity<Void> checkProduct(@PathVariable Long id) {
        // 检查产品是否存在
    }
    
    // OPTIONS - 获取支持的方法
    @RequestMapping(method = RequestMethod.OPTIONS)
    public ResponseEntity<Void> options() {
        return ResponseEntity.ok()
                .allow(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, 
                       HttpMethod.DELETE, HttpMethod.PATCH)
                .build();
    }
}

2. 内容协商

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    // 根据 Accept 头返回不同格式
    @GetMapping(value = "/{id}", produces = {
        MediaType.APPLICATION_JSON_VALUE,
        MediaType.APPLICATION_XML_VALUE
    })
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    
    // 根据 Content-Type 头接收不同格式
    @PostMapping(consumes = {
        MediaType.APPLICATION_JSON_VALUE,
        MediaType.APPLICATION_XML_VALUE
    })
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.create(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    // 文件上传
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<ApiResponse<String>> uploadFile(
            @RequestParam("file") MultipartFile file) {
        
        String fileName = fileService.upload(file);
        return ResponseEntity.ok(ApiResponse.success(fileName, "文件上传成功"));
    }
}

请求响应处理

1. 请求参数处理

@RestController
@RequestMapping("/api/search")
public class SearchController {
    
    // 查询参数
    @GetMapping("/users")
    public ResponseEntity<List<User>> searchUsers(
            @RequestParam(required = false) String keyword,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id") String sortBy,
            @RequestParam(defaultValue = "asc") String sortDir) {
        
        // 搜索逻辑
    }
    
    // 路径参数
    @GetMapping("/users/{userId}/posts/{postId}")
    public ResponseEntity<Post> getUserPost(
            @PathVariable Long userId,
            @PathVariable Long postId) {
        
        // 获取用户的特定文章
    }
    
    // 请求头参数
    @GetMapping("/profile")
    public ResponseEntity<User> getProfile(
            @RequestHeader("Authorization") String token,
            @RequestHeader(value = "User-Agent", required = false) String userAgent) {
        
        // 根据 token 获取用户信息
    }
    
    // Cookie 参数
    @GetMapping("/preferences")
    public ResponseEntity<Map<String, Object>> getPreferences(
            @CookieValue(value = "sessionId", required = false) String sessionId) {
        
        // 获取用户偏好设置
    }
}

2. 复杂对象绑定

// DTO 类
public class UserSearchRequest {
    
    private String keyword;
    private Integer minAge;
    private Integer maxAge;
    private List<String> roles;
    private Boolean active;
    private String city;
    
    // getter 和 setter
}

@RestController
public class UserSearchController {
    
    // 复杂查询对象
    @PostMapping("/api/users/search")
    public ResponseEntity<List<User>> searchUsers(@RequestBody UserSearchRequest request) {
        List<User> users = userService.search(request);
        return ResponseEntity.ok(users);
    }
}

数据验证与转换

1. Bean Validation

// 用户创建请求 DTO
public class CreateUserRequest {
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
    @JsonProperty("username")
    private String username;
    
    @Email(message = "邮箱格式不正确")
    @NotBlank(message = "邮箱不能为空")
    @JsonProperty("email")
    private String email;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, max = 20, message = "密码长度必须在8-20之间")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$", 
             message = "密码必须包含大小写字母和数字")
    @JsonProperty("password")
    private String password;
    
    @Range(min = 18, max = 100, message = "年龄必须在18-100之间")
    @JsonProperty("age")
    private Integer age;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    @JsonProperty("phone")
    private String phone;
    
    // getter 和 setter
}

// 控制器中使用验证
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<ApiResponse<User>> createUser(
            @Valid @RequestBody CreateUserRequest request) {
        
        User user = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED)
                           .body(ApiResponse.success(user));
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<ApiResponse<User>> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        
        User user = userService.update(id, request);
        return ResponseEntity.ok(ApiResponse.success(user));
    }
}

2. 自定义验证器

// 自定义验证注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface ValidUsername {
    String message() default "用户名格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 验证器实现
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
    
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        if (username == null || username.trim().isEmpty()) {
            return false;
        }
        
        // 用户名验证规则:3-20位字母数字下划线,不能以数字开头
        String regex = "^[a-zA-Z_][a-zA-Z0-9_]{2,19}$";
        return username.matches(regex);
    }
}

// 使用自定义验证
public class User {
    
    @ValidUsername
    @JsonProperty("username")
    private String username;
    
    // ...
}

异常处理机制

1. 全局异常处理器

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    /**
     * 处理参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Map<String, String>> handleValidationException(
            MethodArgumentNotValidException ex) {
        
        logger.warn("参数验证失败: {}", ex.getMessage());
        
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        return ApiResponse.error(400, "参数验证失败", errors);
    }
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleBusinessException(BusinessException ex) {
        
        logger.warn("业务异常: {}", ex.getMessage());
        
        return ApiResponse.error(ex.getCode(), ex.getMessage());
    }
    
    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiResponse<Void> handleGenericException(Exception ex) {
        
        logger.error("系统异常", ex);
        
        return ApiResponse.error(500, "系统内部错误,请稍后重试");
    }
}

CORS 跨域配置

1. 全局 CORS 配置

@Configuration
public class CorsConfig {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        
        return source;
    }
}

最佳实践

1. API 设计原则

  • RESTful 风格: 使用标准 HTTP 方法和状态码
  • 资源导向: URL 应该表示资源而不是动作
  • 版本控制: 为 API 提供版本控制策略
  • 一致性: 保持命名和响应格式的一致性

2. 安全最佳实践

// 输入验证示例
@RestController
public class SecurityController {
    
    @PostMapping("/api/search")
    public ResponseEntity<List<User>> search(@RequestBody SearchRequest request) {
        
        // 输入长度限制
        if (request.getKeyword().length() > 100) {
            throw new IllegalArgumentException("搜索关键字过长");
        }
        
        // 特殊字符过滤
        String sanitizedKeyword = request.getKeyword()
                .replaceAll("[<>\"'%;()&+]", "");
        
        // 业务逻辑
        List<User> users = searchService.search(sanitizedKeyword);
        return ResponseEntity.ok(users);
    }
}

常见问题

问题1:JSON 数据绑定失败

现象: @RequestBody 注解的参数接收不到数据 原因:

  • 缺少 Jackson 依赖
  • Content-Type 不正确
  • JSON 格式错误

解决方案:

<!-- 确保包含 Jackson 依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

问题2:CORS 跨域问题

现象: 浏览器控制台出现跨域错误 解决方案:

@Configuration
public class CorsConfig {
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

⚠️ 注意: 在生产环境中,务必配置适当的安全策略、监控和日志记录。

相关文章

前后章节导航

Spring 核心技术

MyBatis 与数据访问

相关实操

总结

Spring MVC RESTful API 开发是现代 Java Web 开发的核心技能,本指南全面覆盖了:

🎯 核心内容

  • RESTful 架构: 标准的 REST 设计原则和最佳实践
  • JSON 处理: 完整的 JSON 序列化和反序列化解决方案
  • 数据验证: Bean Validation 和自定义验证器
  • 异常处理: 全局异常处理和错误响应统一化

🛠️ 技术特性

  • 标准化设计: 遵循 RESTful 设计规范
  • 企业级特性: 包含安全、性能、监控等企业级考虑
  • 完整工具链: 从开发到测试的完整解决方案

📚 实际应用

  • 微服务架构: 服务间通信接口设计
  • 前后端分离: Web 应用后端 API 开发
  • 移动端支持: iOS/Android 应用后端接口
  • 第三方集成: 开放平台 API 设计