SpringMVC-异常处理器
Spring MVC 异常处理器是处理 Spring MVC 应用中发生的异常的机制
一、异常处理的核心价值
-
系统健壮性保障
-
防止未处理异常导致服务崩溃
-
避免敏感信息泄露(如数据库连接信息)
-
确保关键业务逻辑的容错能力
-
-
可观测性提升
-
统一的错误日志格式
-
精准的错误分类统计
-
完整的调用链追踪支持
-
-
开发效率优化
-
减少重复的错误处理代码
-
规范化的错误响应格式
-
明确的错误定位指引
-
二、异常体系设计规范
-
异常分类标准
异常类型 错误码范围 典型场景 处理策略 系统异常 50xxx 数据库连接失败、Redis超时 报警+自动恢复 参数校验异常 60xxx 手机号格式错误、必填项缺失 前端提示重填 业务规则异常 61xxx 库存不足、订单状态冲突 引导用户修正操作 权限认证异常 62xxx 未登录访问、接口权限不足 跳转登录页/提示升级权限 第三方服务异常 70xxx 支付接口超时、短信服务失败 降级处理+异步重试
三、异常处理器方法注解
@ControllerAdvice
:用于定义一个全局的异常处理类。它可以标注在一个类上,表明该类是一个处理控制器层异常的切面类。使用这个注解后,该类中的异常处理方法可以应用到所有被@RequestMapping
注解标注的方法上。@ExceptionHandler
:用于标注在方法上,指定该方法用来处理特定类型的异常。它可以在@ControllerAdvice
类中使用,也可以在单个控制器类中使用。当指定的异常类型发生时,Spring MVC 会调用被该注解标注的方法来处理异常。
四、出现异常现象的常见位置与常见诱因
- 数据绑定阶段
- 诱因:当客户端发送的数据与服务器端期望的数据类型不匹配时,就会发生数据绑定异常。例如,客户端发送一个字符串类型的数据,而服务器端期望接收一个整数类型的数据。
- 示例:在表单提交中,用户输入了非数字字符,而后台需要将该字段解析为整数。
- 业务逻辑执行阶段
- 诱因:业务逻辑中可能会出现各种异常,如违反业务规则、数据库操作失败等。例如,在进行用户注册时,用户名已经存在,这就违反了业务规则。
- 示例:在保存订单时,数据库突然出现连接问题,导致订单数据无法插入到数据库中。
- 控制器方法调用阶段
- 诱因:控制器方法在执行过程中可能会抛出各种异常,如空指针异常、数组越界异常等。这可能是由于代码逻辑不完善或者参数传递不正确导致的。
- 示例:控制器方法中调用了一个可能返回空值的服务方法,而没有进行空值判断,导致后续操作出现空指针异常。
项目异常处理方案
- 全局异常处理:通过使用
@ControllerAdvice
和@ExceptionHandler
注解,可以创建一个全局的异常处理器,统一处理所有控制器中抛出的异常。这样可以将异常处理逻辑集中在一个地方,便于维护和管理。 - 局部异常处理:在单个控制器类中,也可以使用
@ExceptionHandler
注解来处理该控制器中特定的异常。这种方式适用于某些控制器有特殊的异常处理需求,不适合全局处理的情况。 - 自定义异常处理:可以根据项目的业务需求,自定义异常类型。例如,定义一个
BusinessException
来表示业务逻辑上的异常,然后在异常处理器中对自定义异常进行特殊处理,返回给客户端特定的错误信息和状态码。
五、异常处理器结构示例
自定义异常(BusinessException SystemException)
- 定义异常:在自定义异常类中,可以定义一个异常编码字段,用于表示不同类型的异常。例如:
package com.weilai.controller.exceptionHandler;public class BusinessException extends RuntimeException {private final Integer errorCode;private final String errorMessage;public BusinessException(Integer errorCode, String errorMessage) {super(errorMessage);this.errorCode = errorCode;this.errorMessage = errorMessage;}// Getterspublic Integer getErrorCode() {return errorCode;}public String getErrorMessage() {return errorMessage;}
}
package com.weilai.controller.exceptionHandler;public class SystemException extends RuntimeException {private final Integer errorCode;private final String errorMessage;public SystemException(Integer errorCode, String errorMessage, Throwable cause) {super(errorMessage, cause);this.errorCode = errorCode;this.errorMessage = errorMessage;}// Getterspublic Integer getErrorCode() {return errorCode;}public String getErrorMessage() {return errorMessage;}
}
异常编码 (Code)
package com.weilai.controller.exceptionHandler;/*** 全局异常错误码定义(按模块分类)*/
public class Code {//------------------------ 系统级错误(50xxx)------------------------/** 系统未知错误(如未明确捕获的异常) */public static final Integer SYSTEM_UNKNOW_ERROR = 50001;/** 系统超时错误(如接口响应超时) */public static final Integer SYSTEM_TIMEOUT_ERROR = 50002;/** 数据库错误(如连接失败、SQL异常) */public static final Integer SYSTEM_DB_ERROR = 50011;/** 网络通信错误(如HTTP调用失败) */public static final Integer SYSTEM_NETWORK_ERROR = 50021;/** 第三方服务异常(如微信支付接口错误) */public static final Integer SYSTEM_THIRD_PARTY_ERROR = 50031;/** 配置文件错误(如缺失必要配置) */public static final Integer SYSTEM_CONFIG_ERROR = 50041;/** 文件IO异常(如读写文件失败) */public static final Integer SYSTEM_IO_ERROR = 50051;/** 数据格式错误(如JSON解析失败) */public static final Integer SYSTEM_DATA_FORMAT_ERROR = 50061;/** 权限不足(如系统资源访问被拒绝) */public static final Integer SYSTEM_ACCESS_DENIED = 50071;/** 资源不足(如内存溢出、线程池耗尽) */public static final Integer SYSTEM_RESOURCE_EXHAUSTED = 50081;/** 加密/解密异常(如AES解密失败) */public static final Integer SYSTEM_ENCRYPTION_ERROR = 50091;/** 序列化/反序列化错误(如Redis缓存数据转换失败) */public static final Integer SYSTEM_SERIALIZATION_ERROR = 50101;//------------------------ 项目级错误(60xxx)------------------------/** 参数校验失败(如@Valid校验不通过) */public static final Integer PROJECT_VALIDATE_ERROR = 60001;/** 业务逻辑异常(如用户余额不足) */public static final Integer PROJECT_BUSINESS_ERROR = 60002;/** 数据重复(如唯一键冲突) */public static final Integer PROJECT_DATA_DUPLICATE = 60011;/** 数据状态冲突(如订单已支付无法取消) */public static final Integer PROJECT_STATE_CONFLICT = 60021;/** 用户权限不足(如非管理员操作) */public static final Integer PROJECT_ACCESS_DENIED = 60031;/** 数据不存在(如查询ID不存在) */public static final Integer PROJECT_DATA_NOT_FOUND = 60041;/** 操作限制(如频繁调用接口) */public static final Integer PROJECT_OPERATION_LIMIT = 60051;/** 依赖服务异常(如内部微服务不可用) */public static final Integer PROJECT_DEPENDENCY_ERROR = 60061;/** 版本冲突(如乐观锁更新失败) */public static final Integer PROJECT_VERSION_CONFLICT = 60071;/** 流程错误(如审批流程未按顺序执行) */public static final Integer PROJECT_WORKFLOW_ERROR = 60081;
全局异常处理器 (GlobalExceptionHandler)
- 在全局异常处理器中使用异常编码:在异常处理器方法中,可以根据异常的编码来返回不同的响应信息。
1.生成专门的error页面返回异常信息
package com.weilai.controller.exceptionHandler;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;@ControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);// 统一构建错误视图private ModelAndView buildErrorView(Integer errorCode, String message, HttpStatus status, HttpServletRequest request) {ModelAndView mav = new ModelAndView("errorView");mav.addObject("errorCode", errorCode != null ? errorCode : status.value());mav.addObject("errorMessage", message);mav.addObject("timestamp", System.currentTimeMillis());mav.addObject("path", request.getRequestURI());mav.setStatus(status);return mav;}// 处理业务异常@ExceptionHandler(BusinessException.class)public ModelAndView handleBusinessException(BusinessException ex, HttpServletRequest request) {return buildErrorView(ex.getErrorCode(), ex.getErrorMessage(), HttpStatus.BAD_REQUEST, request);}// 处理系统异常@ExceptionHandler(SystemException.class)public ModelAndView handleSystemException(SystemException ex, HttpServletRequest request) {logger.error("系统异常 [code={}, path={}]", ex.getErrorCode(), request.getRequestURI(), ex);return buildErrorView( ex.getErrorCode(), ex.getErrorMessage(), HttpStatus.INTERNAL_SERVER_ERROR, request);}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ModelAndView handleValidationException(MethodArgumentNotValidException ex) {BindingResult result = ex.getBindingResult();List<String> errors = result.getFieldErrors().stream().map(f -> f.getField() + ": " + f.getDefaultMessage()).collect(Collectors.toList());ModelAndView mav = buildErrorView( Code.PROJECT_VALIDATE_ERROR, "参数校验失败", HttpStatus.BAD_REQUEST, null);mav.addObject("errors", errors);return mav;}// 处理404异常@ExceptionHandler(NoHandlerFoundException.class)public ModelAndView handleNotFound(NoHandlerFoundException ex, HttpServletRequest request) {return buildErrorView(Code.PROJECT_DATA_NOT_FOUND, "资源不存在", HttpStatus.NOT_FOUND, request);}// 处理全局未分类异常(其他异常)@ExceptionHandler(Exception.class)public ModelAndView handleGlobalException(Exception ex, HttpServletRequest request) {// 常见系统异常分类if (ex instanceof NullPointerException) {return handleSystemException(new SystemException(Code.SYSTEM_UNKNOWN_ERROR, "空指针异常", ex),request);} else if (ex instanceof IllegalArgumentException) {return handleSystemException(new SystemException(Code.PROJECT_VALIDATE_ERROR, "非法参数", ex),request);}logger.error("未捕获异常 [path={}]", request.getRequestURI(), ex);return buildErrorView( Code.SYSTEM_UNKNOWN_ERROR, "系统繁忙,请稍后重试", HttpStatus.INTERNAL_SERVER_ERROR, request);}
}
这段代码定义了一个名为 GlobalExceptionHandler
的全局异常处理类,使用了 Spring 的 @ControllerAdvice
注解,其作用是对整个 Spring MVC 应用中控制器抛出的异常进行统一处理,增强了应用的健壮性和可维护性。下面是对代码的详细总结:
主要功能概述
- 日志记录:借助 SLF4J 框架记录各类异常信息,便于后续问题排查与系统监控。
- 统一错误视图构建:
buildErrorView
方法可构建包含错误码、错误消息、时间戳、请求路径等信息的错误视图,确保异常处理结果的一致性。 - 多类型异常处理:
- 业务异常(
BusinessException
):针对业务逻辑层面的异常,返回HttpStatus.BAD_REQUEST
状态码。 - 系统异常(
SystemException
):处理系统层面的异常,记录详细日志并返回HttpStatus.INTERNAL_SERVER_ERROR
状态码。 - 参数校验异常(
MethodArgumentNotValidException
):当请求参数校验失败时,收集错误信息并返回HttpStatus.BAD_REQUEST
状态码。 - 404 异常(
NoHandlerFoundException
):若请求的资源不存在,返回HttpStatus.NOT_FOUND
状态码。 - 全局未分类异常(
Exception
):对于其他未明确归类的异常,进一步细分处理,如NullPointerException
和IllegalArgumentException
,最终统一返回HttpStatus.INTERNAL_SERVER_ERROR
状态码。
- 业务异常(
代码结构分析
- 类定义:使用
@ControllerAdvice
注解,将GlobalExceptionHandler
类标记为全局异常处理类。 - 日志记录:定义了
Logger
对象,用于记录异常信息。 - 方法说明:
buildErrorView
:封装错误信息到ModelAndView
对象,方便返回给前端。handleBusinessException
:处理业务异常,调用buildErrorView
方法生成错误视图。handleSystemException
:处理系统异常,记录错误日志并调用buildErrorView
方法。handleValidationException
:处理参数校验异常,收集校验错误信息并添加到错误视图中。handleNotFound
:处理 404 异常,调用buildErrorView
方法返回资源不存在的错误视图。handleGlobalException
:处理其他未分类异常,对常见异常进行分类处理,最终统一处理并记录日志。
2.在当前页面返回异常信息
package com.weilai.controller.exceptionHandler;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;@ControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);// 处理业务异常@ExceptionHandler(BusinessException.class)public ModelAndView handleBusinessException(BusinessException ex,HttpServletRequest request,RedirectAttributes redirectAttributes) {// 记录警告日志(业务异常通常不需要记录错误级别)logger.warn("业务异常 [code={}, path={}] {}",ex.getErrorCode(),request.getRequestURI(),ex.getMessage());// 设置重定向属性redirectAttributes.addFlashAttribute("errorCode", ex.getErrorCode());redirectAttributes.addFlashAttribute("errorMessage", ex.getErrorMessage());// 获取来源页面并处理空指针String referer = getSafeReferer(request);return new ModelAndView("redirect:" + referer);}// 处理系统异常@ExceptionHandler(SystemException.class)public ModelAndView handleSystemException(SystemException ex,HttpServletRequest request,RedirectAttributes redirectAttributes) {// 记录错误日志(包含异常堆栈)logger.error("系统异常 [code={}, path={}]",ex.getErrorCode(),request.getRequestURI(),ex);// 设置重定向属性redirectAttributes.addFlashAttribute("errorCode", ex.getErrorCode());redirectAttributes.addFlashAttribute("errorMessage", "系统繁忙,请稍后再试");String referer = getSafeReferer(request);return new ModelAndView("redirect:" + referer);}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ModelAndView handleValidationException(MethodArgumentNotValidException ex,HttpServletRequest request,RedirectAttributes redirectAttributes) {BindingResult result = ex.getBindingResult();List<String> errors = result.getFieldErrors().stream().map(f -> f.getField() + ": " + f.getDefaultMessage()).collect(Collectors.toList());// 记录校验失败详情logger.info("参数校验失败 [path={}] {}", request.getRequestURI(), errors);redirectAttributes.addFlashAttribute("errors", errors);redirectAttributes.addFlashAttribute("errorCode", Code.PROJECT_VALIDATE_ERROR);String referer = getSafeReferer(request);return new ModelAndView("redirect:" + referer);}// 处理404异常@ExceptionHandler(NoHandlerFoundException.class)public ModelAndView handleNotFound(NoHandlerFoundException ex,HttpServletRequest request,RedirectAttributes redirectAttributes) {logger.warn("404异常 [method={}, path={}]",ex.getHttpMethod(),ex.getRequestURL());redirectAttributes.addFlashAttribute("errorCode", Code.PROJECT_DATA_NOT_FOUND);redirectAttributes.addFlashAttribute("errorMessage", "请求的资源不存在");// 如果404页面是API请求,可以重定向到首页return new ModelAndView("redirect:/");}// 处理全局未分类异常@ExceptionHandler(Exception.class)public ModelAndView handleGlobalException(Exception ex,HttpServletRequest request,RedirectAttributes redirectAttributes) {// 特殊异常处理if (ex instanceof NullPointerException) {logger.error("空指针异常 [path={}]", request.getRequestURI(), ex);redirectAttributes.addFlashAttribute("errorCode", Code.SYSTEM_UNKNOWN_ERROR);redirectAttributes.addFlashAttribute("errorMessage", "系统内部错误");} else if (ex instanceof IllegalArgumentException) {logger.warn("非法参数异常 [path={}] {}", request.getRequestURI(), ex.getMessage());redirectAttributes.addFlashAttribute("errorCode", Code.PROJECT_VALIDATE_ERROR);redirectAttributes.addFlashAttribute("errorMessage", ex.getMessage());} else {logger.error("未捕获异常 [path={}]", request.getRequestURI(), ex);redirectAttributes.addFlashAttribute("errorCode", Code.SYSTEM_UNKNOWN_ERROR);redirectAttributes.addFlashAttribute("errorMessage", "系统繁忙,请稍后重试");}String referer = getSafeReferer(request);return new ModelAndView("redirect:" + referer);}// 安全获取Referer地址private String getSafeReferer(HttpServletRequest request) {String referer = request.getHeader("Referer");if (referer == null || referer.isEmpty()) {// 默认回退到应用根路径return request.getContextPath() + "/";}return referer;}
}
这段 Java 代码定义了一个名为 GlobalExceptionHandler
的全局异常处理类,使用 Spring 的 @ControllerAdvice
注解,它的作用是对 Spring MVC 应用中控制器抛出的各类异常进行统一处理,确保在不同异常情况下都能给用户友好的反馈。
主要功能概述
- 日志记录:利用 SLF4J 框架对不同类型的异常进行日志记录,针对业务异常记录警告日志,系统异常和未捕获异常记录错误日志,参数校验失败记录信息日志,便于后续的问题排查和系统监控。
- 异常分类处理:
- 业务异常(
BusinessException
):捕获业务逻辑层面的异常,记录警告日志,将异常的错误码和错误信息通过RedirectAttributes
传递到重定向页面,并重定向到来源页面。 - 系统异常(
SystemException
):处理系统层面的异常,记录详细的错误日志(包含异常堆栈),设置通用的错误提示信息,将错误码和信息传递到重定向页面,然后重定向到来源页面。 - 参数校验异常(
MethodArgumentNotValidException
):当请求参数校验失败时,收集校验错误信息并记录日志,将错误信息和错误码传递到重定向页面,最后重定向到来源页面。 - 404 异常(
NoHandlerFoundException
):针对请求的资源不存在的情况,记录警告日志,设置相应的错误码和错误信息,若为 API 请求则重定向到应用首页。 - 全局未分类异常(
Exception
):对其他未明确归类的异常进行处理,根据异常类型进一步细分,如NullPointerException
和IllegalArgumentException
,分别设置不同的错误信息,记录相应日志,最后重定向到来源页面。
- 业务异常(
- 安全获取来源页面:通过
getSafeReferer
方法安全地获取请求的来源页面(Referer
头),若来源页面为空则默认回退到应用根路径。
代码结构分析
- 类定义:使用
@ControllerAdvice
注解将GlobalExceptionHandler
标记为全局异常处理类。 - 日志记录:定义
Logger
对象,用于记录不同级别的异常信息。 - 异常处理方法:每个
@ExceptionHandler
注解标注的方法对应处理一种或一类异常,在方法内部进行日志记录、错误信息设置和页面重定向操作。 - 辅助方法:
getSafeReferer
方法用于安全获取请求的来源页面,避免出现空指针异常。
核心区别说明
ModelAndView.addObject() | RedirectAttributes.addFlashAttribute() | |
---|---|---|
存储位置 | 当前请求的 Model 中 | Session(临时存储,重定向后自动清除) |
有效范围 | 当前请求 | 下一个请求(即重定向后的请求) |
适用场景 | 直接渲染视图时使用 | 重定向场景下传递临时数据 |
前端获取方式对比
原方案(使用 addObject)
<!-- 直接通过 Model 属性访问 -->
<div th:if="${errorCode}">Error Code: [[${errorCode}]]
</div>
新方案(使用 addFlashAttribute)
<!-- 通过 Flash Attribute 访问 -->
<div th:if="${#request.getAttribute('errorCode')}">Error Code: [[${errorCode}]]
</div><!-- 或更简洁的写法(推荐) -->
<div th:if="${errorCode}">Error Code: [[${errorCode}]]
</div>
六、手动抛出异常时机说明
需要明确几个方面:
**全局异常处理器的作用**:自动捕获未被处理的异常,包括运行时异常和自定义异常。但有些情况下,开发者需要显式地抛出异常,以便异常处理器能够捕获并进行相应的处理。
**何时需要手动抛出异常**:比如在业务逻辑中,当遇到无效参数、权限不足、数据不存在等情况时,主动抛出对应的自定义异常,让异常处理器统一处理,而不是在每个地方都处理错误逻辑。
**不抛出异常的情况**:如果某个异常已经被try-catch块捕获并处理,那么异常处理器就不会再处理它。但如果开发者希望异常被全局处理器处理,就需要在catch块中重新抛出或直接不捕获。
不是所有异常都需要手动抛出。比如,像数据库操作失败导致的DataAccessException,可能由Spring框架自动抛出,但业务相关的异常需要手动抛出。
哪些情况下需要手动抛出异常,比如参数校验失败时抛出BusinessException,数据库操作异常时可能由框架抛出,但需要包装成SystemException。
在Service层抛出业务异常,在Controller层不处理异常,而是让全局处理器统一处理。
在使用全局异常处理器的情况下,是否需要手动抛出异常取决于具体的业务场景和设计需求。以下是清晰的分类说明:
1. 需要手动抛出异常的场景
以下情况需要显式抛出异常,由全局异常处理器统一处理:
① 业务规则校验失败
// 用户注册时检查用户名重复
public void registerUser(String username) {if (userRepository.existsByUsername(username)) {throw new BusinessException(Code.PROJECT_DATA_DUPLICATE, "用户名已存在");}// ...其他逻辑
}
② 数据状态冲突
// 取消订单时检查状态
public void cancelOrder(Long orderId) {Order order = orderRepository.findById(orderId).orElseThrow(() -> new BusinessException(Code.PROJECT_DATA_NOT_FOUND, "订单不存在"));if (order.getStatus() == OrderStatus.PAID) {throw new BusinessException(Code.PROJECT_STATE_CONFLICT, "已支付订单不可取消");}// ...取消逻辑
}
③ 权限校验失败
// 管理员操作权限校验
public void adminOperation(User operator) {if (!operator.isAdmin()) {throw new BusinessException(Code.PROJECT_ACCESS_DENIED, "无管理员权限");}// ...操作逻辑
}
2. 不需要手动抛出的场景
以下情况框架会自动抛出异常,无需手动处理:
① 参数校验失败(@Valid)
// 使用@Valid自动触发校验
@PostMapping("/users")
public void createUser(@Valid @RequestBody UserDTO user) {// 如果参数校验失败,会自动抛出MethodArgumentNotValidExceptionuserService.create(user);
}
② 数据库操作异常
// Spring Data JPA 会抛出DataAccessException
@Transactional
public void updateProduct(Product product) {// 如果数据库操作失败(如唯一键冲突),会抛出DataAccessExceptionproductRepository.save(product);
}
③ 框架级异常
-
NoHandlerFoundException
(404) -
HttpRequestMethodNotSupportedException
(405) -
TypeMismatchException
(参数类型错误)
3. 最佳实践原则
场景 | 处理方式 |
---|---|
业务逻辑错误 | 手动抛出BusinessException |
基础设施错误 | 手动包装为SystemException (如捕获数据库异常后转换) |
参数校验 | 依赖@Valid +自动异常 |
权限校验 | 在拦截器/切面中统一抛出异常 |
不可预知系统错误 | 由全局异常处理器兜底处理(如空指针异常) |
4. 为什么需要手动抛出?
-
明确错误语义
通过自定义异常类型,明确区分错误类型(如PROJECT_DATA_DUPLICATE
vsPROJECT_STATE_CONFLICT
)。 -
统一错误处理
避免每个Controller重复处理错误逻辑,通过全局异常处理器统一生成响应格式。 -
代码可读性
异常抛出点直接反映业务逻辑的失败条件,代码意图更清晰。 -
错误传播控制
在Service层抛出异常后,可以跨多层调用栈传递到Controller层,无需逐层返回错误码。
5. 错误处理流程示例
6. 常见问题解答
Q1:捕获异常后还需要抛出吗?
try {// 可能抛出DataAccessException的操作
} catch (DataAccessException e) {// 需要转换为系统异常后重新抛出throw new SystemException(Code.SYSTEM_DB_ERROR, "数据库操作失败", e);
}
Q2:如何避免过度抛出异常?
-
校验前置:在参数进入业务逻辑前完成校验(如使用
@Valid
) -
状态检查:通过返回
Optional
或Result
对象处理非异常场景 -
防御性编程:对可预见的错误使用条件判断而非依赖异常
通过合理的手动异常抛出与全局处理器的配合,可以实现:
-
✅ 清晰的错误分类
-
✅ 统一的错误响应格式
-
✅ 业务逻辑与错误处理的解耦
-
✅ 更易维护的代码结构