【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将lib和jar分离的操作方法

《springboot将lib和jar分离的操作方法》本文介绍了如何通过优化pom.xml配置来减小SpringBoot项目的jar包大小,主要通过使用spring-boot-maven-plugin... 遇到一个问题,就是每次maven package或者maven install后target中的ja

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

如何利用Java获取当天的开始和结束时间

《如何利用Java获取当天的开始和结束时间》:本文主要介绍如何使用Java8的LocalDate和LocalDateTime类获取指定日期的开始和结束时间,展示了如何通过这些类进行日期和时间的处... 目录前言1. Java日期时间API概述2. 获取当天的开始和结束时间代码解析运行结果3. 总结前言在J

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

java父子线程之间实现共享传递数据

《java父子线程之间实现共享传递数据》本文介绍了Java中父子线程间共享传递数据的几种方法,包括ThreadLocal变量、并发集合和内存队列或消息队列,并提醒注意并发安全问题... 目录通过 ThreadLocal 变量共享数据通过并发集合共享数据通过内存队列或消息队列共享数据注意并发安全问题总结在 J

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤

《SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤》本文主要介绍了SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤,文中通过示例代码介绍的非常详... 目录 目标 步骤 1:确保 ProxySQL 和 mysql 主从同步已正确配置ProxySQL 的