开发者

SpringBoot使用@ControllerAdvice进行统一处理异常详解

开发者 https://www.devze.com 2026-01-07 10:25 出处:网络 作者: 程序员大华
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录一、什么是全局异常处理?常见场景如果不做处理,会发生什么有了全局异常处理,就能二、如何做异常处理传统做法(不推荐)全局异常处理(推荐)三、SpringBoot 中如何实现全局异常处理?第一步:定义统一的错误响
目录
  • 一、什么是全局异常处理?
    • 常见场景
    • 如果不做处理,会发生什么
    • 有了全局异常处理,就能
  • 二、如何做异常处理
    • 传统做法(不推荐)
    • 全局异常处理(推荐)
  • 三、SpringBoot 中如何实现全局异常处理?
    • 第一步:定义统一的错误响应格式
    • 第二步:创建全局异常处理器类
    • 第三步:自定义业务异常(可选但推荐)
  • 四、更智能的异常处理
    • 1. 区分 Controller 和 REST 接口
    • 2. 处理参数校验异常(如 @Valid)
    • 3. 记录日志(非常重要!)
  • 五、深入理解@ControllerAdvice
    • 最佳实践建议
      • 1. 定义清晰的异常体系
      • 2. 使用枚举管理错误码
      • 3. 日志记录要恰当
    • 完整示例代码
      • 总结

        一、什么是全局异常处理?

        在实际开发中,程序运行时难免会遇到各种意外。这些意外在 Java 中就表现为异常(Exception)。如果不做统一处理,用户可能会看到一堆错误信息。

        常见场景

        • NullPointerException(空指针异常):前端传了一个编程用户 ID,但后端没做判空,直接调用 user.getName(),结果 user 是 null 导致程序崩溃!
        • SQLException(数据库连接失败):数据库宕机或网络中断,导致查询失败,直接抛出 SQL 异常。
        • NumberFormatException(参数格式错误):前javascript端传了个字符串 "abc",后端试图用 Integer.parseInt() 转成数字失败!

        如果不做处理,会发生什么

        SpringBoot 默认会把异常的完整堆栈信息原样返回给前端,比如:

        {
          "timestamp": "2025-11-26T12:00:00",
          "status": php500,
          "error": "Internal Server Error",
          "message": "/ by zero",
          "path": "/api/calculate"
        }
        

        甚至在开发环境下,还可能返回几十行 Java 堆栈,这些会导致以下以下问题:

        • 用户看不懂,
        • 前端无法统一处理,
        • 更严重的是:可能泄露类名、方法名、服务器路径等敏感信息!

        有了全局异常处理,就能

        • 拦截所有异常,统一返回结构化错误信息
        • 区分不同异常类型,返回不同的状态码和提示语
        • 自动记录日志,方便排查问题
        • 避免敏感信息外泄,提升系统安全性

        最终返回给前端的,可能是这样一段干净、友好的 jsON:

        {
          "code": 400,
          "message": "用户ID不能为空",
          "data": null
        }
        

        或者:

        {
          "code": 200,
          "message": "订单创建成功",
          "data": true
        }
        

        是不是清爽又专业多了

        二、如何做异常处理

        传统做法(不推荐)

        每个 Controller 方法里都写 try-catch

        @GetMapping("/user/{id}")
        public ResponseEntity getUser(@PathVariable Long id) {
            try {
                User user = userService.findById(id);
                return ResponseEntity.ok(user);
            } catch (UserNotFoundException e) {
                return ResponseEntity.status(404).body("用户不存在");
            } catch (Exception e) {
                return ResponseEntity.status(500).body("系统错误");
            }
        }
        

        问题很明显:

        • 代码重复
        • 难维护
        • 容易漏掉

        全局异常处理(推荐)

        只写一次,全局生效!

        下面主要写 SpringBoot 的处理。

        三、SpringBoot 中如何实现全局异常处理?

        核心就靠一个注解:@ControllerAdvice

        第一步:定义统一的错误响应格式

        // 统一返回结构,前端好解析
        pandroidublic class ErrorResponse {
            private int code;
            private String message;
            private Object data; // 一般为 null,但保留扩展性
        
            public ErrorResponse(int code, String message) {
                this.code = code;
                this.message = message;
            }
        
            // getter / setter 省略(实际项目中建议用 Lombok)
        }
        

        第二步:创建全局异常处理器类

        // 1. 创建一个类,加上 @ControllerAdvice 注解
        @ControllerAdvice
        public class GlobalExceptionHandler {
        
            // 2. 用 @ExceptionHandler 捕获特定异常
            @ExceptionHandler(UserNotFoundException.class)
            public ResponseEntity handleUserNotFound(UserNotFoundException e) {
                ErrorResponse error = new ErrorResponse(404, e.getMessage());
                return ResponseEntity.status(404).body(error);
            }
        
            // 3. 捕获所有其他异常(兜底)
            @ExceptionHandler(Exception.class)
            public ResponseEntity handleGenericException(Exception e) {
                ErrorResponse error = new ErrorResponse(500, "服务器内部错误,请联系管理员");
                return ResponseEntity.status(500).body(error);
            }
        }
        

        第三步:自定义业务异常(可选但推荐)

        // 自定义业务异常
        public class BusinessException extends RuntimeException {
            private Integer code;
            
            public BusinessException(Integer code, String message) {
                super(message);
                this.code = code;
            }
            
            // getter省略
        }
        

        然后在 Service 层这样用:

        public User findById(Long id) {
            if (id == null || id <= 0) {
                throw new BusinessException(&#34;用户ID无效&#34;);
            }
            // 查询数据库...
        }
        

        可以说配置全局异常是比较简单的,可能因为简单,或者是项目里本来就配置的有,所以很多朋友直接忽略掉了这些问题。

        四、更智能的异常处理

        1. 区分 Controller 和 REST 接口

        如果你既有页面(返回 html),又有 API(返回 JSON),可以用:

        @ControllerAdvice(annotations = RestController.class)
        public class RestGlobalExceptionHandler {
            // 只处理 @RestController 的异常
        }
        

        或者指定包路径:

        @ControllerAdvice(basePackages = &#34;com.example.api&#34;)
        public class ApiExceptionHandler { ... }
        

        2. 处理参数校验异常(如 @Valid)

        SpringBoot 自带校验框架,校验失败会抛 MethodArgumentNotValidException

        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity handleValidation(MethodArgumentNotValidException e) {
            String msg = e.getBindingResult().getFieldError().getDefaultMessage();
            return ResponseEntity.badRequest().body(new ErrorResponse(400, msg));
        }
        

        3. 记录日志(非常重要!)

        handleGenericException 中加入日志:

        private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
        
        @ExceptionHandler(Exception.class)
        public ResponseEntity handleGenericException(Exception e) {
            log.error(&#34;系统发生未预期异常&#34;, e); // 记录完整堆栈
            return ResponseEntity.status(500).body(new ErrorResponse(500, &#34;服务器开小差啦~&#34;));
        }
        

        五、深入理解@ControllerAdvice

        很多同学可能会好奇:为什么加了@ControllerAdvice后,异常就能被自动捕获呢?

        其实原理很简单:

        • Spring MVC 在处理请求时,如果 Controller 方法抛出异常
        • Spring 会查找有没有对应的异常处理器(@ExceptionHandler
        • 查找顺序是:先找当前 Controller 中的 @ExceptionHandler 方法
        • 如果当前 Controller 没有,就找 @ControllerAdvice 标注的类中的 @ExceptionHandler 方法
        • 找到匹配的异常处理器后,调用对应的方法处理异常

        最佳实践建议

        1. 定义清晰的异常体系

        // 基础异常类
        public class BaseException extends RuntimeException {
            private Integer code;
            
            public BaseException(Integer code, String message) {
                super(message);
                this.code = code;
            }
        }
        
        // 各种具体异常
        public class BusinessException extends BaseException {
            public BusinessException(Integer code, String message) {
                super(code, message);
            }
        }
        
        public class SystemException extends BaseException {
            public SystemException(Integer code, String message) {
                super(code, message);
            }
        }
        

        2. 使用枚举管理错误码

        public enum ErrorCode {
            SUCCESS(200, &#34;成功&#34;),
            PARAM_ERROR(400, &#34;参数错误&#34;),
            UNAUTHORIZED(401, &#34;未授权&#34;),
            FORBIDDEN(403, &#34;禁止访问&#34;),
            NOT_FOUND(404, &#34;资源不存在&#34;),
            SYSTEM_ERROR(500, &#34;系统错误&#34;);
            
            private final Integer code;
            private final String message;
            
            ErrorCode(Integer code, String message) {
                this.code = code;
                this.message = message;
            }
            
            // getter方法
        }
        

        3. 日志记录要恰当

        • 业务异常:一般用warn级别,不需要记录堆栈
        • 系统异常:用error级别,需要记录堆栈信息

        完整示例代码

        // 统一返回结果
        @Data
        @AllArgsConstructor
        @NoArgsConstwww.devze.comructor
        public class Result {
            private Integer code;
            private String message;
            private T data;
            
            public static  Result success(T data) {
                return new Result<>(200, &#34;成功&#34;, data);
            }
            
            public static  Result error(Integer code, String message) {
                return new Result<>(code, message, null);
            }
        }
        
        // 全局异常处理器
        @RestControllerAdvice
        public class GlobalExceptionHandler {
            
            private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
            
            // 处理业务异常
            @ExceptionHandler(BusinessException.class)
            public Result handleBusinessException(BusinessException e) {
                logger.warn(&#34;业务异常:code={}, message={}&#34;, e.getCode(), e.getMessage());
                return Result.error(e.getCode(), e.getMessage());
            }
            
            // 处理参数校验异常
            @ExceptionHandler(MethodArgumentNotValidException.class)
            public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
                String message = e.getBindingResult().getFieldErrors().stream()
                        .map(FieldError::getDefaultMessage)
                        .collect(Collectors.joining(&#34;, &#34;));
                return Result.error(400, message);
            }
            
            // 处理所有其他异常
            @ExceptionHandler(Exception.class)
            public Result handleException(Exception e) {
                logger.error(&#34;系统异常:&#34;, e);
                return Result.error(500, &#34;系统繁忙,请稍后重试&#34;);
            }
        }
        

        总结

        统一格式:前后端约定好错误结构,沟通更顺畅

        减少重复代码:不用每个方法写 try-catch

        提升健壮性:兜底处理防止系统崩溃,还能记录日志

        到此这篇关于SpringBoot使用@ControllerAdvice进行统一处理异常详解的文章就介绍到这了,更多相关SpringBoot异常处理内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0
        价值2999元 Java视频教程限时免费下载
        专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
        立即下载

        精彩评论

        暂无评论...
        验证码 换一张
        取 消