全局异常处理+封装返回对象
全局异常处理流程
- 自定义业务状态码枚举类,规范状态码
- 封装返回对象例如Result
- 自定义异常类BusinessException,这类异常是可预见性的,直接手动throw异常抛出就可以捕获到,优先级最高
- 全局异常处理器@RestControllerAdvice
- 这套方案能够在异常处理器里面精确处理各种异常的返回情况,同时不用大量的try-catch代码,可预见的直接抛异常,不可预见的也可以被拦截进行处理
ResponseEntity
- 能封装完整的响应信息包括响应头,响应体,响应状态码。
- return ResponseEntity.badRequest().body(Map.of("code", 400, "message", "Username and password cannot be empty"));
这个badRequest中就封装了一个HttpStatus的状态码
public static BodyBuilder badRequest() {
return status(HttpStatus.BAD_REQUEST);
}
-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", 500, "message", "Internal server error"));
-
ResponseEntity.status(HttpStatus status) 静态方法,获取构建器,HttpStatus是一个枚举类,里面枚举了许多状态码。
-
常用状态码:
- 2xx,成功状态码。如OK(200)请求完全成功,create(201)资源创建成功
- 3xx,重定向状态码。MOVED_PERMANENTLY(301)永久重定向,访问旧域名返回301重定向到新域名。302临时重定向,用户未登录需要访问需要权限的页面就会重定向到登录页)
- 4xx,客户端异常码,400请求参数/地址错误,401用户未认证/未登录/认证失效,403已认证但是无权限,404未找到请求资源,405请求方法不支持,比如get请求使用了post请求
- 5xx,服务端错误状态码,500服务器内部报错,503服务器不可用

-
body() 方法会创建 ResponseEntity 实例,将传入的 Map 作为响应体(body), 传入的 HttpStatus.INTERNAL_SERVER_ERROR 作为状态码(status),响应头默认为空(父类 HttpEntity 初始化空响应头),响应头也可以设置
-
相关源码
//BodyBuilder接口去继承HeadersBuilder
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
BodyBuilder contentLength(long contentLength);
BodyBuilder contentType(MediaType contentType);
<T> ResponseEntity<T> body(@Nullable T body);
}
//这个静态类去实现BodyBuilder以及他的父类里面所有的方法
private static class DefaultBuilder implements BodyBuilder{
//在这个接口里面的一些方法返回值都是BodyBuilder,最后通过return this返回相同的对象,这个是实现函数链式调用的核心
//我们最后使用这个body函数他对响应头,响应体,响应状态码进行封装返回一个ResponseEntity结束这个链式调用
public BodyBuilder header(String headerName, String... headerValues) {
for(String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
}
return this;
}
public BodyBuilder headers(@Nullable HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
return this;
}
public BodyBuilder headers(Consumer<HttpHeaders> headersConsumer) {
headersConsumer.accept(this.headers);
return this;
}
}
Result
- 本质是一个普通的 POJO,主要封装了业务状态码(code),业务提示信息(message),业务数据(data),请求时间戳(便于问题排查,大厂必加)timestamp,静态工具类分为成功返回和失败返回两种;
public class Result<T> {
// 业务状态码
private Integer code;
// 业务提示信息
private String msg;
// 业务数据
private T data;
// 静态工具方法:成功响应(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(0);
result.setMsg("操作成功");
result.setData(data);
return result;
}
// 静态工具方法:失败响应
public static <T> Result<T> fail(String msg) {
Result<T> result = new Result<>();
result.setCode(1);
result.setMsg(msg);
result.setData(null);
return result;
}
}
@RestControllerAdvice全局处理器
- 在Spring异常处理体系中HandlerExceptionResolver是顶级接口,而RestControllerAdvice是对这个顶级接口的高级封装实现,里面拦截的顺序是子类优先级 > 父类优先级。 如果都是子类那么谁先抛出异常谁先执行拦截。
/**
* 全局异常处理器(大厂标准:统一拦截所有控制器异常)
* @RestControllerAdvice = @ControllerAdvice + @ResponseBody,自动返回JSON格式
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
// ==================== 1. 捕获自定义业务异常(优先级最高,可预见异常) ====================
@ExceptionHandler(BusinessException.class)
public GlobalResult<?> handleBusinessException(BusinessException e) {
// 业务异常:只打印普通日志(无需堆栈,因为是主动抛出的可预见异常)
log.warn("业务异常:{}", e.getMessage());
// 返回业务异常信息给前端(友好提示)
return GlobalResult.fail(e);
}
// ==================== 2. 捕获参数校验异常(高频场景,如@NotBlank、@NotNull校验失败) ====================
// 处理 @Valid 注解的请求体参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public GlobalResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
// 拼接所有参数错误信息(返回给前端,明确提示哪个参数有误)
String errorMsg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(";"));
// 打印日志
log.warn("参数校验异常:{}", errorMsg);
// 返回参数校验失败状态码和详细错误信息
return GlobalResult.fail(ResultCodeEnum.PARAM_VALID_ERROR.getCode(), errorMsg);
}
// 处理 @Valid 注解的请求参数(非请求体)校验异常
@ExceptionHandler(BindException.class)
public GlobalResult<?> handleBindException(BindException e) {
String errorMsg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(";"));
log.warn("参数绑定异常:{}", errorMsg);
return GlobalResult.fail(ResultCodeEnum.PARAM_VALID_ERROR.getCode(), errorMsg);
}
// ==================== 3. 捕获特定系统异常(精准处理,如404) ====================
// 处理接口不存在异常(404)
@ExceptionHandler(NoHandlerFoundException.class)
public GlobalResult<?> handleNoHandlerFoundException(NoHandlerFoundException e) {
String errorMsg = "请求接口不存在:" + e.getRequestURL();
// 打印日志
log.warn(errorMsg);
return GlobalResult.fail(ResultCodeEnum.RESOURCE_NOT_FOUND.getCode(), errorMsg);
}
// ==================== 4. 兜底捕获所有未处理异常(优先级最低,不可预见系统异常) ====================
@ExceptionHandler(Exception.class)
public GlobalResult<?> handleException(Exception e) {
// 系统异常:打印完整堆栈日志(便于排查问题,大厂必做)
log.error("系统内部异常", e);
// 对外返回友好提示,不暴露具体异常信息(安全考虑)
return GlobalResult.fail(ResultCodeEnum.SYSTEM_ERROR);
}
}

浙公网安备 33010602011771号