基于Spring实现自定义错误信息返回详解
背景
Spring 提供了 @RestControllerAdvice 用来实现 HTTP 协议的全局异常处理。在异常信息的处理上通常只返回特定的 Response 对象,如下。
@Slf4j
@RestControllerAdvice
public class RestExceptionResolver {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> processException(Exception ex) {
BodyBuilder builder;
Response response;
ResponseStatus responseStatus =
AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
builder = ResponseEntity.status(responseStatus.value());
response = Response.buildFailure("500", responseStatus.reason());
} else {
builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
response = Response.buildFailure("500", ex.getMessage());
}
this.process(ex, response);
return builder.body(response);
}
}
作为基础框架,笔者就遇到项目A 要求返回 Response1 对象,项目B 要求返回 Response2 对象,这个时候,适配起来就很痛苦,例如下方的代码。
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class Response extends DTO {
private static final long serialVersionUID = 1L;
private boolean success;
private String errCode; // 项目A 要求错误码是字符型
private String errMessage; // 项目A 要求用这个名字
private int code; // 项目B 要求错误码是整型
private String message; // 项目B 要求用这个名字
}
另外,@RestControllerAdvice 只适用于 Web 异常捕获,我们还要考虑其他组件的情况,例如 Dubbo 捕获 RPC 异常、Sentinel 组件触发限流、Spring Security 安全框架抛出认证异常。
目标
不需要修改基础框架,允许业务方自行扩展异常返回对象。
实现
将 Response 提炼为 Builder 模式,改为 ResponseBuilder.builder() 构建返回对象。
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> resolveException(Exception ex) {
BodyBuilder builder;
Object response;
ResponseStatus status = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
builder = ResponseEntity.status(status.value());
response = ResponseBuilder.builder().buildFailure("500", status.reason());
} else {
builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
response = ResponseBuilder.builder().buildFailure("500", ex.getMessage());
}
this.postProcess(ex);
return builder.body(response);
}
}
public interface ResponseBuilder<T> {
static ResponseBuilder<?> builder() {
// 尝试从业务项目获取自定义的 Spring Bean
ResponseBuilder<?> builder = ApplicationContextHelper.getBean(ResponseBuilder.class);
if (builder != null) {
return builder;
}
// 如果业务项目没有自定义 Bean,返回默认的 Builder
return DefalutResponseBuilder.getInstance();
}
T buildSuccess();
<Body> T buildSuccess(Body data);
T buildFailure(String errCode, String errMessage, Object... params);
}
public class DefalutResponseBuilder implements ResponseBuilder<Response> {
private static final DefaultResponseBuilder INSTANCE = new DefaultResponseBuilder();
private DefaultResponseBuilder() {}
public static DefaultResponseBuilder getInstance() {
return INSTANCE;
}
@Override
public Response buildSuccess() {
Response response = new Response();
response.setSuccess(true);
return response;
}
@Override
public <Body> Response buildSuccess(Body data) {
SingleResponse<Body> response = new SingleResponse<>();
response.setSuccess(true);
response.setData(data);
return response;
}
@Override
public Response buildFailure(String errCode, String errMessage, Object... params) {
Response response = new Response();
response.setSuccess(false);
response.setErrCode(errCode);
response.setErrMessage(MessageFormatter.arrayFormat(message, placeholders).getMessage());
return response;
}
}
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class Response extends DTO {
private static final long serialVersionUID = 1L;
private boolean success;
private String errCode;
private String errMessage;
}
业务方觉得 Response 不能满足需求,重新定义了新对象,如下。
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
@Data
public class CustomResponse {
private static final long serialVersionUID = 1L;
private boolean success;
private int code; // 要求错误码是整型
private String message; // 前端要求用这个名字
}创建 CustomResponseBuilder 包装 CustomResponse 对象,并标记 @Component 注解,放入 Spring Bean 管理。
@Component
public class CustomResponseBuilder implements ResponseBuilder<CustomResponse> {
@Override
public CustomResponse buildSuccess() {
CustomResponse response = new CustomResponse();
response.setSuccess(true);
return response;
}
@Override
public <Body> CustomResponse buildSuccess(Body data) {
// 略
}
@Override
public CustomResponse buildFailure(int code, String message, Object... params) {
CustomResponse response = new CustomResponse();
response.setSuccess(false);
response.setCode(code);
response.setMessage(MessageFormatter.arrayFormat(message, placeholders).getMessage());
return response;
}
}
上述已提到 ResponseBuilder.builder() 优先查找 Spring Bean,所以 CustomResponseBuilder 覆盖了框架内置的 DefaultResponseBuilder 类,全局异常捕获器返回结果时,就能返回业务方自定义的 CustomResponse 对象,这样,不需要改动框架,就能满足业务需求。
产出
根据这个思路,我们分别实现了 Web 异常、Dubbo 异常、Sentinel 限流、Security 认证等各种场景的异常处理机制,业务方只需要自行创建 ResponseBuilder 扩展自己的返回对象即可,不需要修改框架。
到此这篇关于基于Spring实现自定义错误信息返回详解的文章就介绍到这了,更多相关Spring自定义错误信息内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
教你怎么用SpringBoot+Mybati-Plus快速搭建代码
Mybatis自身通过了逆向工程来帮助我们快速生成代码,但Mybatis-plus却更加强大,不仅仅可以生成dao,pojo,mapper,还有基本的controller和service层代码,接下来我们来写一个简单的人门案例是看看如何mybatis-plus是怎么实现的,需要的朋友可以参考下2021-06-06
Java面试岗常见问题之ArrayList和LinkedList的区别
ArrayList和LinkedList作为我们Java中最常使用的集合类,很多人在被问到他们的区别时,憋了半天仅仅冒出一句:一个是数组一个是链表。这样回答简直让面试官吐血。为了让兄弟们打好基础,我们通过实际的使用测试,好好说一下ArrayList和LinkedList的区别这道经典的面试题2022-01-01
SpringCache缓存抽象之CacheManager与自定义键生成方式
本文将深入探讨Spring Cache的核心组件CacheManager及自定义键生成策略,帮助开发者掌握缓存配置与优化技巧,从而构建高效可靠的缓存系统,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2025-04-04
SpringBoot整合MybatisPlus实现增删改查功能
MybatisPlus是国产的第三方插件, 它封装了许多常用的CURDapi,免去了我们写mapper.xml的重复劳动。本文将整合MybatisPlus实现增删改查功能,感兴趣的可以了解一下2022-05-05


最新评论