Java编程高频的“技术点”-01:自定义全局异常处理器

(一)、自定义全局异常处理器

在 Spring Boot 中,自定义校验异常的统一处理通常通过****@RestControllerAdvice(全局异常处理器) 配合 @ExceptionHandler(异常拦截) 来实现。

这样做的好处是,当 @Valid 或 @Validated 校验失败时,我们不需要在每个 Controller 里写重复的 try-catch 或 if-else 逻辑,而是由全局处理器统一捕获,并向前端返回格式一致、语义清晰的 JSON 错误信息。

以下是实现统一异常处理的标准步骤和完整代码示例:

🛠️ 1. 引入必要的依赖

确保你的 pom.xml 中包含了 Spring Boot 的 Web 和 Validation 启动器(Spring Boot 3.x 默认使用 jakarta.validation):

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

🌍 2. 创建全局异常处理器(核心代码)

新建一个类(如 GlobalExceptionHandler),使用 @RestControllerAdvice 注解。我们需要重点捕获两种最常见的校验异常:

  • MethodArgumentNotValidException :处理 Controller 层 @RequestBody 参数校验失败(如 POST/PUT 请求的 JSON 数据)。
  • ConstraintViolationException :处理 GET 请求的 @RequestParam、@PathVariable 或 Service 层方法参数校验失败。
java 复制代码
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理 @RequestBody 参数校验失败 (如 POST/PUT 请求)
     * 异常类型:MethodArgumentNotValidException
     */
  // 返回 400 状态码
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST) 
    public Map<String, Object> handleValidationExceptions(
                            MethodArgumentNotValidException ex) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 400);
        response.put("message", "参数校验失败");

        // 提取所有字段的错误信息,拼接成易读的格式
        String errors = ex.getBindingResult().getFieldErrors().stream()
                .map(fieldError -> fieldError.getField() + ": " + 
                        fieldError.getDefaultMessage())
                .collect(Collectors.joining(", "));
        
        response.put("errors", errors);
        return response;
    }

    /**
     * 处理 @RequestParam / @PathVariable 
     *   或 Service 层方法参数校验失败 (如 GET 请求)
     * 异常类型:ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handleConstraintViolationException(
                                        ConstraintViolationException ex) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 400);
        response.put("message", "参数校验失败");

        // 提取异常中的约束违规信息
        String errors = ex.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(", "));
        
        response.put("errors", errors);
        return response;
    }


    /**
     * 兜底处理:捕获其他未处理的系统异常,防止直接暴露堆栈信息给前端
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, Object> handleSystemException(Exception ex) {

        Map<String, Object> response = new HashMap<>();
        response.put("code", 500);
        response.put("message", "系统内部错误,请联系管理员");

        return response;
    }
}

📝 3. 实际效果演示

假设你有一个接收用户注册的 DTO,并且加上了校验规则:

java 复制代码
public class UserRegisterDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 16, 
             message = "密码长度必须在6到16位之间")
    private String password;
    // getter / setter
}

当前端传入了不合法的 JSON 数据(例如 {"username": "", "password": "123"})时,全局异常处理器会自动拦截并返回如下结构化的 JSON:

java 复制代码
{
  "code": 400,
  "message": "参数校验失败",
  "errors": "username: 用户名不能为空, 
            password: 密码长度必须在6到16位之间"
}

💡 最佳实践建议

  1. 统一返回结构 :在实际的企业级项目中,建议将返回的 Map 替换为一个专门的**ApiResponse<T> 泛型类**(包含 code, message, data 字段),这样能让前后端交互的接口规范更加统一。
  2. 国际化支持 :如果你的项目需要支持多语言,可以在 @NotBlank(message = "{user.username.notblank}") 中使用占位符,并配合 Spring Boot 的国际化(i18n)资源文件(如 messages_zh.properties, messages_en.properties)来动态返回不同语言的错误提示。
  3. 避免暴露敏感信息 :在兜底的 Exception 处理中,千万不要直接把 ex.getMessage() 或堆栈信息返回给前端,以免暴露系统内部逻辑或数据库结构。
相关推荐
武子康几秒前
用户说“算了”,设备指令真的取消了吗?
人工智能·后端·agent
阿里嘎多学长1 分钟前
2026-10-13 GitHub 热点项目精选
开发语言·程序员·github·代码托管
砚底藏山河4 分钟前
量化新手误区:因子计算与行情数据的工程化解耦
java·数据库·python·金融
威哥爱编程6 分钟前
【AI全栈12-07】Spring Boot 用 Agent 自动完成多步运营任务
java·spring boot·后端
巧克力味的桃子9 分钟前
复杂网络能控性鲁棒性研究进展
开发语言·网络·php
未秃头的程序猿10 分钟前
我用 Spring AI + MCP 把公司内部系统接给了 AI,过程没想象中顺利
java·后端
RuoyiOffice17 分钟前
SpringBoot3+Vue3 员工全生命周期:入转调离如何衔接账号、权限、资产与交接
spring boot·vue3·权限管理·员工管理·hrm·ruoyi office·人事变更
m0_5873830023 分钟前
碰碰好评卡功能落地实战 从需求梳理到上线部署全流程实操指南
java·linux·服务器·前端·javascript·vue.js·人工智能
普通人629 分钟前
php处理百度编辑器有误html
开发语言·编辑器·html·php·1024程序员节
沉下心来学鲁班37 分钟前
具身智能入门:用 Python 让 MicroDuck 机器狗学会走路
开发语言·人工智能·python·机器人