【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现

本文主要是介绍【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文将以实际代码展示如何实现SpringCloudAlibaba的统一返回体及全局异常捕获。

作者:后端小肥肠

1. 前言

在构建微服务应用时,统一返回体和异常捕获机制的设计对于保持代码的整洁性和提高服务的可维护性至关重要。特别是在使用 Spring Boot 和 Spring Cloud Alibaba这样的现代开发框架时,这一点显得尤为重要。本文将重点介绍如何在Spring Cloud Alibaba环境中实现统一的响应体和异常处理策略。通过这种方式,无论是在单体应用还是在复杂的微服务架构中,开发者都能保证返回信息的一致性和异常的有效管理。

2. 开发环境搭建

2.1. 所需版本依赖

依赖版本
Spring Boot2.6.3
Spring Cloud

2021.0.1

java1.8以上
Spring Cloud Alibaba2021.0.1.0

2.2. pom依赖  

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><spring-cloud.version>2021.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version></properties><dependencies><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.jetbrains</groupId><artifactId>annotations</artifactId><version>17.0.0</version></dependency></dependencies>

3. Spring Cloud统一返回体和异常捕获实现

3.1. Spring Cloud统一返回体实现

微服务项目一般由网关加下游微服务组成,我的习惯是分成网关模块,api模块(存放远程调用方法及公共实体类),common模块(存放一些公共Bean及工具类)及各类业务模块,通常返回体是放在common类中:

SpringCloud项目结构示例

在common中新建response包,将统一返回相关类放入其中即可。

3.1.1. 编写响应状态码枚举
public enum ResponseStatusCodeEnum implements IResponseStatusCode {//服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。SUCCESS(200, "OK"),//用户新建或修改数据成功。UPDATE_RETURN_201(201, "CREATED"),//表示一个请求已经进入后台排队(异步任务)ALL_RETURN_202(202, "Accepted"),//用户删除数据成功。DELETE_RETURN_204(204, "NO CONTENT"),//用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。UPDATE_RETURN_400(400, "INVALID REQUEST"),//用户发出的请求参数有误,服务器没有找到对应资源 这是新加的WRONG_PARAMETER_NOT_FIND_400(400,"请求参数有误,资源不存在"),//401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。ALL_RETURN_401(401, "Unauthorized"),TOKEN_PAST(1401, "身份过期,请求重新登录!"),//403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。ALL_RETURN_403(403, "Forbidden"),//404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。ALL_RETURN_404(404, "NOT FOUND"),//406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。GET_RETURN_406(406, "Not Acceptable"),//410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。GET_RETURN_410(410, "Gone"),//422 Unprocessable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。UPDATE_RETURN_422(422, "Unprocessable entity"),//500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。*/GET_RETURN_500(500, "INTERNAL SERVER ERROR"),CONFLICT_RETURN_409(409,"CONFLICT");private final Integer code;private final String message;ResponseStatusCodeEnum(Integer code, String message) {this.code = code;this.message = message;}@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getMessage() {return this.message;}
}​
3.1.2. 编写响应状态码接口
public interface IResponseStatusCode {/*** 获取响应状态码** @return 响应状态码*/Integer getCode();/*** 获取响应消息** @return 响应消息*/String getMessage();
}
3.1.3. 编写统一返回结构体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseStructure<T>{private Integer code;private String status;private String message;private T data;private static final String SUCCESS = "success";private static final String FAIL = "fail";public static <T> ResponseStructure<T> success(T data) {return new ResponseStructure<>(ResponseStatusCodeEnum.SUCCESS.getCode(),SUCCESS,ResponseStatusCodeEnum.SUCCESS.getMessage(),data);}public static <T> ResponseStructure<T> created(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.UPDATE_RETURN_201.getCode(),SUCCESS,message,null);}public static <T> ResponseStructure<T> unauthorized(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.ALL_RETURN_403.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> unauthenticated(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.ALL_RETURN_401.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> success(String message, T data) {return new ResponseStructure<>(ResponseStatusCodeEnum.SUCCESS.getCode(),SUCCESS,message,data);}public static <T> ResponseStructure<T> success(Integer code, String message) {return new ResponseStructure<>(code, SUCCESS, message, null);}public static <T> ResponseStructure<T> success(Integer code, String message, T data) {return new ResponseStructure<>(code, SUCCESS, message, data);}public static ResponseStructure<Object> failed() {return new ResponseStructure<>(ResponseStatusCodeEnum.GET_RETURN_500.getCode(),FAIL,ResponseStatusCodeEnum.GET_RETURN_500.getMessage(),null);}public static ResponseStructure<String> failed(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.GET_RETURN_500.getCode(),FAIL,message,null);}public static ResponseStructure<Object> failed(IResponseStatusCode errorResult) {return new ResponseStructure<>(errorResult.getCode(),FAIL,errorResult.getMessage(),null);}public static ResponseStructure<Object> conflict(String message) {return new ResponseStructure<>(ResponseStatusCodeEnum.CONFLICT_RETURN_409.getCode(),FAIL,message,null);}public static <T> ResponseStructure<T> instance(Integer code, String message, T data) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code);responseStructure.setMessage(message);responseStructure.setData(data);if (code >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}public static <T> ResponseStructure<T> instance(Integer code, String message) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code);responseStructure.setMessage(message);responseStructure.setData(null);if (code >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}public static <T> ResponseStructure<T> instance(IResponseStatusCode code) {ResponseStructure<T> responseStructure = new ResponseStructure<>();responseStructure.setCode(code.getCode());responseStructure.setMessage(code.getMessage());responseStructure.setData(null);if (code.getCode() >= 300) {responseStructure.setStatus(FAIL);} else {responseStructure.setStatus(SUCCESS);}return responseStructure;}
}
3.1.4. 编写GlobalResponseBodyAdvice
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(@NotNull MethodParameter returnType,@NotNull Class<? extends HttpMessageConverter<?>> converterType) {GlobalResponse globalResponse = returnType.getMethodAnnotation(GlobalResponse.class);return globalResponse == null || globalResponse.format();}@Overridepublic Object beforeBodyWrite(Object body,@NotNull MethodParameter returnType,@NotNull MediaType selectedContentType,@NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,@NotNull ServerHttpRequest request,@NotNull ServerHttpResponse response) {// 如果是 actuator 请求,直接返回if (isActuatorRequest(request)) {return body;}/* 如果是 Feign 请求,直接返回*/if (Objects.requireNonNull(request.getHeaders().get("user-agent")).get(0).startsWith("Java")) {return body;}// 以下代码主要解决和 Swagger 的冲突if (body instanceof ResponseStructure || body instanceof Json || body instanceof UiConfiguration ||(body instanceof ArrayList && !((ArrayList<?>) body).isEmpty() &&((ArrayList<?>) body).get(0) instanceof SwaggerResource)) {return body;}ResponseStructure<Object> responseStructure;// 如果是 POST 请求,业务状态码统一设置为 201if ("POST".equals(((ServletServerHttpRequest) request).getServletRequest().getMethod())) {responseStructure = ResponseStructure.created("OK");} else {responseStructure = ResponseStructure.success(null);}// 如果返回值是字符串类型,则用其替换 messageif (body instanceof String) {responseStructure.setData(body);return JSON.toJSONString(responseStructure);}if (body instanceof byte[]) {return body;}responseStructure.setData(body);return responseStructure;}private boolean isActuatorRequest(ServerHttpRequest request) {return ((ServletServerHttpRequest) request).getServletRequest().getRequestURI().endsWith("/actuator");}
}

这个类的主要作用是为应用提供一个统一的响应结构,帮助前端开发者和最终用户更好地理解和处理 API 的响应。通过拦截所有非特殊请求的响应体,并将它们包装成统一的格式,这个实现可以极大地增强 API 的一致性和可维护性。同时,它也处理了一些特殊情况,如避免改变对于特定工具或请求的原始响应(Swagger UI 或 Feign 客户端请求)。

3.1.5. 编写GlobalResponse
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalResponse {boolean format() default true;
}
3.1.6. 效果测试 

1. controller层方法编写,只需要返回实际数据结构即可

2. 返回结构测试

3.2 Spring Cloud异常捕获实现

在common中新建expection包,将异常捕获类类放入其中即可。

3.2.1. 编写ExceptionAdvice
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {@ExceptionHandler(BindException.class)public Object bindException(BindException bindException) {bindException.printStackTrace();String message = Objects.requireNonNull(bindException.getBindingResult().getFieldError()).getDefaultMessage();return ResponseStructure.conflict(message);}@ResponseStatus(value = HttpStatus.CONFLICT)@ExceptionHandler({ValidationException.class})public ResponseStructure<Object> handleValidationException(ValidationException validationException) {validationException.printStackTrace();return (ResponseStructure<Object>) ResponseStructure.conflict(validationException.getMessage());}@ResponseStatus(value = HttpStatus.CONFLICT)@ExceptionHandler({MaxUploadSizeExceededException.class})public ResponseStructure<Object> handleMaxUploadSizeException(MaxUploadSizeExceededException maxUploadSizeExceededException) {maxUploadSizeExceededException.printStackTrace();return (ResponseStructure<Object>) ResponseStructure.conflict("当前文件大小已超过限制大小,请重新上传文件");}/*** 顶级异常捕获,当其他异常无法处理时选择使用*/@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)@ExceptionHandler({Exception.class})public ResponseStructure<String> handle(Exception exception) {exception.printStackTrace();return (ResponseStructure<String>) ResponseStructure.failed(exception.getMessage());}/*** 认证异常捕获*/@ResponseStatus(value = HttpStatus.UNAUTHORIZED)@ExceptionHandler({AuthenticationException.class})public ResponseStructure<String> handleUnAhthorized(AuthenticationException exception) {exception.printStackTrace();return ResponseStructure.instance(ALL_RETURN_401.getCode(), exception.getMessage());}
}

ExceptionAdvice 类通过定义一系列异常处理器,使得应用能够在抛出异常时提供友好的用户反馈,而不是让用户面对不友好的原始错误信息或空白页面。这样的处理机制不仅提高了应用的可用性和可维护性,还能通过日志记录帮助开发者快速定位和解决问题。此外,通过统一异常处理和响应格式,开发者可以更容易地保持前后端的一致性和同步。 

3.2.2. 效果测试

1. 编写异常测试controller层方法

2. 返回结构测试

5. 结语

本文以代码实例展示了如何在SpringCloudAlibaba中实现统一返回及全部异常捕获,如您有更好观点欢迎在评论区留言探讨~

这篇关于【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/903107

相关文章

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

QT Creator配置Kit的实现示例

《QTCreator配置Kit的实现示例》本文主要介绍了使用Qt5.12.12与VS2022时,因MSVC编译器版本不匹配及WindowsSDK缺失导致配置错误的问题解决,感兴趣的可以了解一下... 目录0、背景:qt5.12.12+vs2022一、症状:二、原因:(可以跳过,直奔后面的解决方法)三、解决方

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

Spring 依赖注入与循环依赖总结

《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Spring 三级缓存解决循环依赖1. 创建UserService原始对象2. 将原始对象包装成工

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方