当前位置: 首页 > web >正文

Java全局异常处理器:优雅处理系统异常

Java全局异常处理器:优雅处理系统异常的最佳实践

概述

在Java Web应用开发中,异常处理是一个至关重要的环节。传统的异常处理方式往往需要在每个Controller方法中编写大量的try-catch代码,这不仅造成代码冗余,还降低了代码的可读性和可维护性。全局异常处理器(Global Exception Handler)应运而生,它能够统一处理整个应用程序中抛出的异常,让我们的代码更加简洁和优雅。

什么是全局异常处理器?

全局异常处理器是Spring框架提供的一种统一异常处理机制,通过@ControllerAdvice@ExceptionHandler注解的组合使用,能够捕获并处理整个应用程序中抛出的各种异常。它的核心思想是将异常处理逻辑从业务代码中分离出来,集中在专门的异常处理类中进行管理。

工作原理

1. 异常传播机制

当应用程序中发生异常时,异常会沿着调用栈向上传播:

Controller Layer → Service Layer → Repository Layer↓
Exception Handler (全局异常处理器)↓
统一的错误响应

2. 注解工作原理

  • @ControllerAdvice:标识这是一个全局异常处理类,Spring会自动扫描并注册
  • @ExceptionHandler:指定该方法处理哪种类型的异常
  • @ResponseBody:将返回值序列化为JSON格式(或配合@RestControllerAdvice使用)

核心组件解析

异常处理优先级

Spring的异常处理遵循就近原则:

  1. Controller内部的@ExceptionHandler
  2. @ControllerAdvice中的@ExceptionHandler
  3. 默认的异常处理机制

异常匹配规则

当异常发生时,Spring会按照以下规则选择合适的异常处理器:

  1. 精确匹配异常类型
  2. 匹配父类异常类型
  3. 匹配接口异常类型

完整Demo实现

1. 自定义异常类

package com.example.exception;/*** 业务异常基类*/
public class BusinessException extends RuntimeException {private final String code;private final String message;public BusinessException(String code, String message) {super(message);this.code = code;this.message = message;}public BusinessException(String code, String message, Throwable cause) {super(message, cause);this.code = code;this.message = message;}public String getCode() {return code;}@Overridepublic String getMessage() {return message;}
}/*** 资源未找到异常*/
public class ResourceNotFoundException extends BusinessException {public ResourceNotFoundException(String resource) {super("RESOURCE_NOT_FOUND", "资源未找到: " + resource);}
}/*** 参数校验异常*/
public class ValidationException extends BusinessException {public ValidationException(String message) {super("VALIDATION_ERROR", message);}
}/*** 权限不足异常*/
public class AccessDeniedException extends BusinessException {public AccessDeniedException() {super("ACCESS_DENIED", "权限不足,拒绝访问");}
}

2. 统一响应结果类

package com.example.common;import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;/*** 统一响应结果封装*/
public class ApiResponse<T> {private String code;private String message;private T data;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;public ApiResponse() {this.timestamp = LocalDateTime.now();}public ApiResponse(String code, String message) {this();this.code = code;this.message = message;}public ApiResponse(String code, String message, T data) {this(code, message);this.data = data;}// 成功响应public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>("SUCCESS", "操作成功", data);}public static <T> ApiResponse<T> success(String message, T data) {return new ApiResponse<>("SUCCESS", message, data);}// 失败响应public static <T> ApiResponse<T> error(String code, String message) {return new ApiResponse<>(code, message);}public static <T> ApiResponse<T> error(String message) {return new ApiResponse<>("ERROR", message);}// Getters and Setterspublic String getCode() { return code; }public void setCode(String 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 LocalDateTime getTimestamp() { return timestamp; }public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}

3. 全局异常处理器

package com.example.handler;import com.example.common.ApiResponse;
import com.example.exception.BusinessException;
import com.example.exception.ResourceNotFoundException;
import com.example.exception.ValidationException;
import com.example.exception.AccessDeniedException;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;/*** 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 处理业务异常*/@ExceptionHandler(BusinessException.class)public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e, WebRequest request) {logger.warn("业务异常: {}", e.getMessage());ApiResponse<Void> response = ApiResponse.error(e.getCode(), e.getMessage());return ResponseEntity.badRequest().body(response);}/*** 处理资源未找到异常*/@ExceptionHandler(ResourceNotFoundException.class)public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(ResourceNotFoundException e, WebRequest request) {logger.warn("资源未找到: {}", e.getMessage());ApiResponse<Void> response = ApiResponse.error(e.getCode(), e.getMessage());return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);}/*** 处理权限异常*/@ExceptionHandler(AccessDeniedException.class)public ResponseEntity<ApiResponse<Void>> handleAccessDeniedException(AccessDeniedException e, WebRequest request) {logger.warn("权限不足: {}", e.getMessage());ApiResponse<Void> response = ApiResponse.error(e.getCode(), e.getMessage());return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);}/*** 处理参数校验异常 - @Valid注解校验失败*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<ApiResponse<Void>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, WebRequest request) {logger.warn("参数校验失败: {}", e.getMessage());String errorMessage = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining("; "));ApiResponse<Void> response = ApiResponse.error("VALIDATION_ERROR", errorMessage);return ResponseEntity.badRequest().body(response);}/*** 处理参数绑定异常*/@ExceptionHandler(BindException.class)public ResponseEntity<ApiResponse<Void>> handleBindException(BindException e, WebRequest request) {logger.warn("参数绑定异常: {}", e.getMessage());String errorMessage = e.getBindingResult().getFieldErrors().stream().map(error -> error.getField() + ": " + error.getDefaultMessage()).collect(Collectors.joining("; "));ApiResponse<Void> response = ApiResponse.error("BIND_ERROR", errorMessage);return ResponseEntity.badRequest().body(response);}/*** 处理约束违规异常*/@ExceptionHandler(ConstraintViolationException.class)public ResponseEntity<ApiResponse<Void>> handleConstraintViolationException(ConstraintViolationException e, WebRequest request) {logger.warn("约束违规: {}", e.getMessage());String errorMessage = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));ApiResponse<Void> response = ApiResponse.error("CONSTRAINT_VIOLATION", errorMessage);return ResponseEntity.badRequest().body(response);}/*** 处理参数类型不匹配异常*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)public ResponseEntity<ApiResponse<Void>> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, WebRequest request) {logger.warn("参数类型不匹配: {}", e.getMessage());String errorMessage = String.format("参数 '%s' 的值 '%s' 类型不正确,期望类型: %s",e.getName(), e.getValue(), e.getRequiredType().getSimpleName());ApiResponse<Void> response = ApiResponse.error("TYPE_MISMATCH", errorMessage);return ResponseEntity.badRequest().body(response);}/*** 处理IllegalArgumentException*/@ExceptionHandler(IllegalArgumentException.class)public ResponseEntity<ApiResponse<Void>> handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) {logger.warn("非法参数: {}", e.getMessage());ApiResponse<Void> response = ApiResponse.error("ILLEGAL_ARGUMENT", e.getMessage());return ResponseEntity.badRequest().body(response);}/*** 处理其他未捕获的异常*/@ExceptionHandler(Exception.class)public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception e, WebRequest request) {logger.error("系统异常: ", e);ApiResponse<Void> response = ApiResponse.error("INTERNAL_ERROR", "系统内部错误,请联系管理员");return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);}
}

4. 业务Service层示例

package com.example.service;import com.example.exception.ResourceNotFoundException;
import com.example.exception.ValidationException;
import org.springframework.stereotype.Service;/*** 用户服务示例*/
@Service
public class UserService {public String getUserById(Long id) {if (id == null || id <= 0) {throw new ValidationException("用户ID不能为空或小于等于0");}if (id == 999L) {throw new ResourceNotFoundException("用户ID: " + id);}return "用户信息: ID=" + id;}public void deleteUser(Long id) {if (id == null) {throw new ValidationException("用户ID不能为空");}if (id == 1L) {throw new IllegalArgumentException("不能删除管理员用户");}// 模拟删除逻辑System.out.println("删除用户: " + id);}
}

5. Controller层示例

package com.example.controller;import com.example.common.ApiResponse;
import com.example.exception.AccessDeniedException;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;/*** 用户控制器示例*/
@RestController
@RequestMapping("/api/users")
public class UserController {@Autowiredprivate UserService userService;/*** 获取用户信息*/@GetMapping("/{id}")public ApiResponse<String> getUser(@PathVariable @NotNull @Min(value = 1, message = "用户ID必须大于0") Long id) {String user = userService.getUserById(id);return ApiResponse.success(user);}/*** 删除用户*/@DeleteMapping("/{id}")public ApiResponse<Void> deleteUser(@PathVariable Long id) {userService.deleteUser(id);return ApiResponse.success("删除成功", null);}/*** 管理员操作示例*/@PostMapping("/admin/reset")public ApiResponse<Void> adminReset(@RequestParam String action) {// 模拟权限检查boolean isAdmin = false; // 假设当前用户不是管理员if (!isAdmin) {throw new AccessDeniedException();}return ApiResponse.success("重置成功", null);}/*** 模拟系统异常*/@GetMapping("/error")public ApiResponse<Void> triggerError() {// 故意抛出运行时异常throw new RuntimeException("模拟系统异常");}
}

最佳实践

1. 异常分层设计

// 按业务模块划分异常
public class UserException extends BusinessException {public UserException(String message) {super("USER_ERROR", message);}
}public class OrderException extends BusinessException {public OrderException(String message) {super("ORDER_ERROR", message);}
}

2. 日志记录策略

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e) {// 业务异常记录为WARN级别logger.warn("业务异常 [{}]: {}", e.getCode(), e.getMessage());// 系统异常记录为ERROR级别,包含堆栈信息// logger.error("系统异常: ", e);
}

3. 异常信息国际化

@Component
public class MessageHelper {@Autowiredprivate MessageSource messageSource;public String getMessage(String code, Object... args) {return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());}
}

测试验证

使用以下curl命令测试各种异常情况:

# 1. 正常请求
curl http://localhost:8080/api/users/1# 2. 参数校验异常
curl http://localhost:8080/api/users/0# 3. 资源未找到异常
curl http://localhost:8080/api/users/999# 4. 权限不足异常
curl -X POST http://localhost:8080/api/users/admin/reset?action=reset# 5. 系统异常
curl http://localhost:8080/api/users/error

总结

全局异常处理器是Spring Boot应用中不可或缺的组件,它具有以下优势:

  1. 代码解耦:将异常处理逻辑从业务代码中分离
  2. 统一响应:确保所有异常都以统一的格式返回
  3. 便于维护:集中管理异常处理逻辑,便于修改和扩展
  4. 提升用户体验:提供友好的错误信息
  5. 便于监控:统一的异常记录便于系统监控和问题排查

通过合理使用全局异常处理器,我们可以构建更加健壮、可维护的Java Web应用程序。记住,好的异常处理不仅能够提升代码质量,更能为用户提供更好的使用体验。

http://www.xdnf.cn/news/19503.html

相关文章:

  • 数学运算符号:跨越千年的智慧结晶与文明印记
  • strtok()字符串分隔函数
  • VideoPoet:Google发布的用于视频生成的大语言模型
  • 【C#】在一个任意旋转的矩形(由四个顶点定义)内绘制一个内切椭圆
  • SpringAI应用开发面试实录:核心技术、架构设计与业务场景全解析
  • 华为研发投资与管理实践(IPD)读书笔记
  • VSCode `tasks.json` 中 `tasks` 数组的详细解析
  • 语义分析:从读懂到理解的深度跨越
  • Photoshop - Ps 标尺
  • JVM参数配置调优指南
  • 在开发过程中经常遇到 OOM(内存溢出)问题,如何解决?
  • 解决IDEA 2025.2升级报错:Scannning Files to Index卡住问题分析与修复
  • 设计模式:外观模式(Facade Pattern)
  • 【Proteus仿真】开关控制系列仿真——开关控制LED/拨码开关二进制计数/开关和继电器控制灯灭
  • 第3章 乱码的前世今生-字符集和比较规则
  • 常见线程池的创建方式及应用场景
  • 将基于 Spring Boot 3.0.0 的 JavaWeb 应用部署到腾讯云并配置域名
  • Iterative loop of ML development|机器学习的迭代发展
  • C#基础(②音乐播发器MCI(Media Control Interface))
  • MySQL 常用语法
  • PortSwigger靶场之Stored XSS into HTML context with nothing encoded通关秘籍
  • Spring Boot 3.x 微服务架构实战指南
  • k8s中 discovery-token和token 的区别
  • Openstack Eproxy 2025.1 安装指南
  • 基于OpenCv做照片分析应用一(Java)
  • 学习记录(二十二)--Overleaf中生成的PDF左上角1.5em问题
  • 【AI编程工具】使用Cursor快速搭建一套小型项目管理系统
  • Elasticsearch vs Solr vs OpenSearch:搜索引擎方案对比与索引设计最佳实践
  • 微服务的编程测评系统18-判题功能-Rabbitmq-用户拉黑
  • 7.2elementplus的表单布局与模式