为什么要全局异常处理类?
全局处理(Global Exception Handling
)是一种集中管理异常和错误响应的机制。通过全局处理,可以统一处理应用中抛出的异常,避免在每个控制器中重复编写异常处理代码。
以下是全局处理的必要性:
1. 统一异常处理
在大型应用中,异常可能分散在各个控制器中。如果没有全局处理机制,每个控制器都需要单独处理异常,导致代码重复和维护困难。
2. 提高代码可读性
将异常处理逻辑集中在一个地方,可以使业务逻辑更加清晰,减少控制器中的冗余代码。
3. 标准化错误响应
通过全局处理,可以确保所有异常都返回统一的错误响应格式,便于前端或其他客户端处理。
4. 增强安全性
全局处理可以捕获未处理的异常,避免敏感信息泄露(如堆栈跟踪),提升应用的安全性。
5. 简化测试
集中处理异常逻辑后,测试异常处理变得更加简单,无需在每个控制器中单独测试异常处理逻辑。
全局异常处理优势是什么?
减少代码重复
通过全局处理,可以避免在每个控制器中重复编写异常处理代码,减少代码冗余。
统一错误响应格式
全局处理可以确保所有异常都返回统一的错误响应格式,例如:
js{ "timestamp": "2023-10-01T12:00:00Z", "status": 400, "error": "Bad Request", "message": "Invalid input data", "path": "/api/users" }
提高开发效率
开发者无需在每个控制器中单独处理异常,可以专注于业务逻辑的实现,提升开发效率。
增强应用健壮性
全局处理可以捕获未处理的异常,避免应用崩溃,提升应用的健壮性。
支持自定义异常
通过全局处理,可以轻松定义和处理自定义异常,满足特定业务需求。
如何实现全局异常处理拦截?
在 xiaomayi-common/xiaomayi-core
核心类库中已自定义了全局异常处理类:
js
package com.xiaomayi.core.exception;
import com.xiaomayi.core.utils.R;
import com.xiaomayi.core.utils.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <p>
* 全局异常处理类
* </p>
*
* @author 小蚂蚁云团队
* @since 2024-05-21
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常处理类
*
* @param e 异常处理
* @param request 网络请求
* @return 返回结果
*/
@ExceptionHandler(BizException.class)
public R handleServiceException(BizException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? R.failed(code, e.getMessage()) : R.failed(e.getMessage());
}
/**
* 非法请求异常处理
*
* @param e 异常处理
* @param request 网络请求
* @return 返回结果
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return R.failed(e.getMessage());
}
/**
* 请求地址缺少参数
*
* @param e 异常处理
* @param request 网络请求
* @return 返回结果
*/
@ExceptionHandler(MissingPathVariableException.class)
public R handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return R.failed(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
* 请求参数类型不匹配
*
* @param e 异常处理
* @param request 网络请求
* @return 返回结果
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return R.failed(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), Objects.requireNonNull(e.getRequiredType()).getName(), e.getValue()));
}
/**
* Spring封装的参数验证异常处理
* 备注:作用于 @Validated @Valid 注解,接收参数加上@RequestBody注解(json格式)才会有这种异常
*
* @param e 异常处理
* @return 返回结果
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return R.failed(message);
}
/**
* 参数绑定、验证异常处理
* 备注:作用于@Validated @Valid 注解,仅对于表单提交有效,对于以json格式提交将会失效
* 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
*
* @param e 异常处理
* @return 返回结果
*/
@ExceptionHandler(BindException.class)
public R handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return R.failed(message, HttpStatus.BAD_REQUEST.value());
}
/**
* 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R ConstraintViolationExceptionHandler(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(v -> v.getMessage()).collect(Collectors.joining());
return R.failed(message);
}
/**
* 上传文件大小验证未通过异常处理
*
* @param e 异常处理
* @return 返回结果
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error("上传文件验证异常:{}", e.getMessage());
return R.failed("上传文件大小超过限制");
}
}
总结
在项目中,全局处理的必要性和好处包括:
统一异常处理:集中管理异常逻辑,减少代码重复。
标准化错误响应:确保所有异常返回统一的响应格式。
提高开发效率:减少重复代码,专注于业务逻辑。
增强应用健壮性:捕获未处理异常,避免应用崩溃。
支持自定义异常:灵活处理特定业务异常。
通过合理使用全局处理机制,可以显著提升代码质量、开发效率和系统健壮性。