乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

最佳实践

本章是 Spring MVC 教程的收官章节。经过前面七个章节的学习,你已经掌握了 DispatcherServlet 调度、请求映射、参数绑定、视图解析、拦截器、异常处理等全部核心机制。但知道机制不等于能写出好代码。本章将把这些散点知识整合为一套可落地的工程规范,涵盖统一响应格式、全局异常架构、RESTful 设计准则和前后端分离配置要点。遵循这些实践,你的项目才能在团队协作中保持清晰、在运维排查中保持高效、在面试答辩中展现专业深度。


定义与作用

Spring MVC 最佳实践不是某一条代码规则,而是一套覆盖 API 设计、异常处理、响应规范、跨域配置、静态资源管理的系统性工程准则。它的目标是:

  • 对前端:提供一致的响应格式,消除"有时返回 JSON、有时返回 HTML、有时返回空白"的混乱
  • 对后端:建立清晰的分层异常体系,让业务代码只关注业务,错误处理由全局架构接管
  • 对运维:通过标准化的日志和状态码,快速定位问题层级(客户端错误 / 业务错误 / 系统故障)
  • 对团队:用约定代替争论,新成员能在 30 分钟内理解项目的 Web 层规范

核心原理

统一响应格式:给前端一个可预期的契约

前后端分离项目中,前端需要解析每一个响应体。如果不同接口的返回结构各不相同,前端就要写大量防御性代码。统一响应格式是API 契约的底线。

统一响应体实现(Spring Boot 2.x):

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
    
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    private String traceId;
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(
            ResultCode.SUCCESS.getCode(),
            ResultCode.SUCCESS.getMessage(),
            data,
            System.currentTimeMillis(),
            MDC.get("traceId")
        );
    }
    
    public static <T> ApiResponse<T> error(ResultCode resultCode, String message) {
        return new ApiResponse<>(
            resultCode.getCode(),
            message != null ? message : resultCode.getMessage(),
            null,
            System.currentTimeMillis(),
            MDC.get("traceId")
        );
    }
}

public enum ResultCode {
    SUCCESS(200, "成功"),
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "未授权"),
    FORBIDDEN(403, "禁止访问"),
    NOT_FOUND(404, "资源不存在"),
    BUSINESS_ERROR(1000, "业务异常"),
    SYSTEM_ERROR(500, "系统内部错误");
    
    private final int code;
    private final String message;
    
    ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    public int getCode() { return code; }
    public String getMessage() { return message; }
}

关键设计决策:

  • code 使用业务状态码(200/400/1000),与 HTTP 状态码解耦,方便前端做细粒度错误处理
  • traceId 用于分布式链路追踪,李眉在排查生产问题时可以通过一个 ID 串联所有日志
  • timestamp 让前端能判断响应是否过期(如缓存场景)

全局异常处理架构

业务代码中不应该出现 try-catch 块包裹业务逻辑的情况。所有异常应该由 @RestControllerAdvice 统一捕获、分类、封装。

全局异常处理器实现:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // 1. 参数校验异常(JSR-303 + @Valid)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());
        
        log.warn("参数校验失败: {}", errors);
        return ApiResponse.error(ResultCode.BAD_REQUEST, String.join("; ", errors));
    }
    
    // 2. 参数绑定异常(类型转换失败、必填参数缺失)
    @ExceptionHandler({BindException.class, MethodArgumentTypeMismatchException.class})
    public ApiResponse<Void> handleBindException(Exception e) {
        log.warn("参数绑定失败: {}", e.getMessage());
        return ApiResponse.error(ResultCode.BAD_REQUEST, "请求参数格式错误");
    }
    
    // 3. 业务异常(由业务层主动抛出)
    @ExceptionHandler(BusinessException.class)
    public ApiResponse<Void> handleBusinessException(BusinessException e) {
        log.info("业务异常: code={}, message={}", e.getResultCode().getCode(), e.getMessage());
        return ApiResponse.error(e.getResultCode(), e.getMessage());
    }
    
    // 4. 系统异常(数据库连接失败、第三方服务超时等)
    @ExceptionHandler(SystemException.class)
    public ApiResponse<Void> handleSystemException(SystemException e) {
        log.error("系统异常: {}", e.getMessage(), e);
        // 可集成钉钉/企业微信告警
        alertService.sendAlert(e);
        return ApiResponse.error(ResultCode.SYSTEM_ERROR, "系统繁忙,请稍后重试");
    }
    
    // 5. 兜底异常(未预期的任何异常)
    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleException(Exception e) {
        log.error("未预期异常: {}", e.getMessage(), e);
        return ApiResponse.error(ResultCode.SYSTEM_ERROR, "系统内部错误");
    }
}

异常分层定义:

// 业务异常:用户可理解的错误,如"库存不足"、"订单已取消"
public class BusinessException extends RuntimeException {
    private final ResultCode resultCode;
    
    public BusinessException(ResultCode resultCode, String message) {
        super(message);
        this.resultCode = resultCode;
    }
    
    public ResultCode getResultCode() { return resultCode; }
}

// 系统异常:用户不应感知的底层故障,如"数据库连接超时"
public class SystemException extends RuntimeException {
    public SystemException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 参数异常:参数校验失败,通常由 @Valid 自动触发
public class ParamException extends BusinessException {
    public ParamException(String message) {
        super(ResultCode.BAD_REQUEST, message);
    }
}

RESTful API 设计规范

RESTful 不是"用 URL 做动词",而是用 HTTP 协议的原生语义表达资源操作。

检查项规范反例正例
URL 命名名词复数,小写,连字符分隔/getUserInfo /user_list/users /order-items
HTTP 方法GET 查询、POST 创建、PUT 全量更新、PATCH 局部更新、DELETE 删除POST /users/deleteDELETE /users/{id}
状态码2xx 成功、4xx 客户端错误、5xx 服务端错误所有响应都返回 200参数错误返回 400,业务错误返回 200 但 code ≠ 200
路径参数用于资源定位(ID)/users?id=123(GET 用查询参数除外)/users/123
查询参数用于过滤、排序、分页/users/page/1/size/20/users?page=1&size=20
响应体统一格式,包含自描述信息直接返回裸数组 []返回 ApiResponse 包装体
版本控制URL 路径或请求头无版本控制/api/v1/users 或 Accept: application/vnd.feixiang.v1+json

飞翔科技 RESTful 接口示例:

@RestController
@RequestMapping("/api/v1/warehouses")
public class WarehouseController {
    
    // 查询仓库列表(支持过滤和分页)
    @GetMapping
    public ApiResponse<Page<Warehouse>> list(
            @RequestParam(required = false) String city,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return ApiResponse.success(warehouseService.list(city, PageRequest.of(page, size)));
    }
    
    // 查询单个仓库
    @GetMapping("/{warehouseId}")
    public ApiResponse<WarehouseDetail> getById(@PathVariable String warehouseId) {
        return ApiResponse.success(warehouseService.findById(warehouseId));
    }
    
    // 创建仓库
    @PostMapping
    public ApiResponse<Warehouse> create(@Valid @RequestBody WarehouseCreateRequest request) {
        return ApiResponse.success(warehouseService.create(request));
    }
    
    // 全量更新仓库信息
    @PutMapping("/{warehouseId}")
    public ApiResponse<Warehouse> update(
            @PathVariable String warehouseId,
            @Valid @RequestBody WarehouseUpdateRequest request) {
        return ApiResponse.success(warehouseService.update(warehouseId, request));
    }
    
    // 局部更新:仅更新仓库状态
    @PatchMapping("/{warehouseId}/status")
    public ApiResponse<Warehouse> updateStatus(
            @PathVariable String warehouseId,
            @RequestParam WarehouseStatus status) {
        return ApiResponse.success(warehouseService.updateStatus(warehouseId, status));
    }
    
    // 删除仓库
    @DeleteMapping("/{warehouseId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable String warehouseId) {
        warehouseService.delete(warehouseId);
    }
}

前后端分离项目的 MVC 配置要点

前后端分离意味着 Spring MVC 不再负责视图渲染,而是纯粹作为 API 网关。配置策略需要相应调整:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    // 1. CORS 配置:允许前端开发服务器访问
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://localhost:3000", "https://app.feixiang.tech")
            .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
    
    // 2. 静态资源:仅保留 Swagger / Actuator 等必要资源
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
    }
    
    // 3. 拦截器白名单:登录、注册、Swagger 等接口放行
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
            .addPathPatterns("/api/**")
            .excludePathPatterns(
                "/api/v1/auth/login",
                "/api/v1/auth/register",
                "/api/v1/auth/refresh",
                "/swagger-ui/**",
                "/v3/api-docs/**"
            );
    }
}

完整示例

场景

飞翔科技的仓储管理系统即将上线。大翔作为 CTO,在上线前评审中提出三个硬性要求:

  1. 前端黄俪说:"如果后端返回格式不统一,我的错误处理代码要写 20 套"
  2. 运维李眉说:"生产环境出问题,我需要 10 秒内判断是前端传错参数还是数据库挂了"
  3. 架构师白歌说:"所有 Controller 方法里不许出现 try-catch,业务异常直接抛"

小崔负责落实这套规范。

统一响应格式落地

// 所有 Controller 返回 ApiResponse
@RestController
@RequestMapping("/api/v1/inventory")
public class InventoryController {
    
    @GetMapping("/items/{itemId}")
    public ApiResponse<ItemDetail> getItem(@PathVariable String itemId) {
        ItemDetail item = inventoryService.findById(itemId);
        if (item == null) {
            // 直接抛业务异常,由 GlobalExceptionHandler 处理
            throw new BusinessException(ResultCode.NOT_FOUND, "商品不存在: " + itemId);
        }
        return ApiResponse.success(item);
    }
}

全局异常处理落地

@Slf4j
@RestControllerAdvice(basePackages = "com.feixiang.api")
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse<Void>> handleBusiness(BusinessException e) {
        ApiResponse<Void> response = ApiResponse.error(e.getResultCode(), e.getMessage());
        // 业务异常返回 200 OK,但 body 中 code 标识错误
        return ResponseEntity.ok(response);
    }
    
    @ExceptionHandler(SystemException.class)
    public ResponseEntity<ApiResponse<Void>> handleSystem(SystemException e) {
        log.error("系统异常", e);
        ApiResponse<Void> response = ApiResponse.error(
            ResultCode.SYSTEM_ERROR, "系统繁忙,请稍后重试"
        );
        // 系统异常返回 500,触发 Nginx 错误页或告警
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

白歌的架构决策说明:

  • 业务异常返回 HTTP 200:因为请求本身到达了服务端,只是业务条件不满足(如库存不足)。前端不需要重试,只需要提示用户
  • 系统异常返回 HTTP 500:因为服务端自身故障,前端可能需要降级或重试。Nginx 和监控工具也会根据 5xx 状态码触发告警

RESTful 设计检查清单

白歌要求每个新接口在 Code Review 时必须通过以下检查:

检查项通过标准检查人
URL 是否使用名词复数/warehouses 而非 /getWarehouse白歌
HTTP 方法是否符合语义创建用 POST,更新用 PUT/PATCH白歌
是否返回统一响应体所有响应均为 ApiResponse<T>黄俪(前端验收)
异常是否由全局处理器接管Controller 中无 try-catch白歌
参数校验是否使用 @Valid复杂对象必须标注 @Valid小崔
分页参数是否统一page + size,默认 size ≤ 100小崔
敏感操作是否有日志DELETE / PUT 记录操作日志李眉

易错场景与面试考点

误区一:统一响应格式就是"所有情况都返回 200"

错误认知:为了前端方便,所有响应都返回 HTTP 200,错误信息放在 body 的 code 字段里。

纠正:HTTP 状态码和 body 中的 code 承担不同职责。HTTP 状态码描述传输层结果(请求是否成功到达、服务端是否理解请求),body 中的 code 描述业务层结果(业务条件是否满足)。混淆两者会导致:

  • 缓存服务器错误缓存 4xx 响应(因为返回了 200)
  • 负载均衡器无法根据 5xx 自动剔除故障节点
  • 监控告警规则失效
// ❌ 错误:参数错误也返回 200
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationError() {
    return ResponseEntity.ok(ApiResponse.error(ResultCode.BAD_REQUEST, "参数错误"));
}

// ✅ 正确:参数错误返回 400,业务错误返回 200,系统错误返回 500
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationError() {
    return ResponseEntity.badRequest()
        .body(ApiResponse.error(ResultCode.BAD_REQUEST, "参数错误"));
}

误区二:@RestControllerAdvice 不加 basePackages 导致副作用

错误认知:一个 @RestControllerAdvice 就能处理整个应用的异常。

纠正:中大型项目中,可能同时存在 API 模块、Admin 模块、定时任务模块。不加 basePackages 会导致 Admin 后台的异常也被 API 的全局处理器拦截,返回 JSON 而不是 HTML 错误页。

// ❌ 错误:影响所有 Controller
@RestControllerAdvice
public class GlobalExceptionHandler { ... }

// ✅ 正确:限定只处理 API 包下的 Controller
@RestControllerAdvice(basePackages = "com.feixiang.api")
public class ApiExceptionHandler { ... }

@RestControllerAdvice(basePackages = "com.feixiang.admin")
public class AdminExceptionHandler { ... }

面试高频:如何设计一个健壮的 Spring MVC 全局异常处理方案?

标准回答:

  1. 异常分层:定义 BusinessException(用户可理解)、SystemException(用户不应感知)、ParamException(参数校验失败)三层异常体系
  2. 全局拦截:用 @RestControllerAdvice + @ExceptionHandler 按异常类型分类处理,Controller 中不写 try-catch
  3. 响应规范:统一 ApiResponse<T> 结构,包含 code / message / data / timestamp / traceId
  4. 状态码分离:HTTP 状态码描述传输结果(2xx/4xx/5xx),body 中的 code 描述业务结果
  5. 日志策略:参数错误记 WARN(可定位但无需紧急处理),业务错误记 INFO(正常业务分支),系统错误记 ERROR(触发告警)
  6. 模块隔离:通过 basePackages 让不同模块有独立的异常处理器,避免 API 和 Admin 互相干扰

小结

Spring MVC 的最佳实践不是锦上添花,而是工程化开发的底线。统一响应格式消除了前后端的协作摩擦,全局异常架构让业务代码保持纯净,RESTful 规范让 API 具备自描述性,CORS 和拦截器白名单让前后端分离项目安全可控。这些实践共同构成了一套可落地、可检查、可演进的 Web 层工程规范。

本章与全局的关系:本章是教程的终点,也是你实际项目的起点。前面所有章节讲解的 DispatcherServlet、HandlerMapping、WebDataBinder、ViewResolver、Interceptor 等机制,在这里汇聚为一套完整的工程方案。建议你以本章为模板,结合团队实际情况,制定一份属于你们项目的《Spring MVC 开发规范》,并在 Code Review 中强制执行。

上一页
国际化