前言
最近在优化自己之前基于Spring AOP的统一响应体的实现方案。
什么是统一响应体呢?在目前的前后端分离架构下,后端主要是一个RESTful API的数据接口。
但是HTTP的状态码数量有限,而随着业务的增长,HTTP状态码无法很好地表示业务中遇到的异常情况。
那么可以通过修改响应返回的JSON数据,让其带上一些固有的字段,例如以下这样的
{ "code": 10000, "msg": "success", "data": { "id": 2, "name": "test" } }
其中关键属性的用途如下:
这3个属性为固有属性,每次响应结果都会有带有它们。
需求
希望实现一个能够代替基于AOP的实现方案,需要满足以下几点:
方案思路
基于上述的需求,选择使用Spring的Controller增强机制,其中关键的类为以下3个:
示例关键代码
本示例使用的Spring Boot版本为2.1.6.RELEASE,同时需要开发工具安装lombok插件
引入依赖
<dependencies> <!--web-starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--test-starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
统一响应体
Controller增强后统一响应体对应的对象
import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; /** * 统一的公共响应体 * @author NULL * @date 2019-07-16 */ @Data @AllArgsConstructor public class ResponseResult implements Serializable { /** * 返回状态码 */ private Integer code; /** * 返回信息 */ private String msg; /** * 数据 */ private Object data; }
统一响应注解
统一响应注解是一个标记是否开启统一响应增强的注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 统一响应注解<br/> * 添加注解后,统一响应体才能生效 * @author NULL * @date 2019-07-16 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface BaseResponse { }
状态码枚举
统一响应体中返回的状态码code和状态信息msg对应的枚举类
/** * 返回状态码 * * @author NULL * @date 2019-07-16 */ public enum ResponseCode { /** * 成功返回的状态码 */ SUCCESS(10000, "success"), /** * 资源不存在的状态码 */ RESOURCES_NOT_EXIST(10001, "资源不存在"), /** * 所有无法识别的异常默认的返回状态码 */ SERVICE_ERROR(50000, "服务器异常"); /** * 状态码 */ private int code; /** * 返回信息 */ private String msg; ResponseCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
业务异常类
业务异常类是用于识别业务相关的异常,需要注意这个异常类强制需要以ResponseCode作为构造方法入参,这样可以通过捕获异常获得返回的状态码信息
import com.rjh.web.response.ResponseCode; import lombok.Data; import lombok.EqualsAndHashCode; /** * 业务异常类,继承运行时异常,确保事务正常回滚 * * @author NULL * @since 2019-07-16 */ @Data @EqualsAndHashCode(callSuper = false) public class BaseException extends RuntimeException{ private ResponseCode code; public BaseException(ResponseCode code) { this.code = code; } public BaseException(Throwable cause, ResponseCode code) { super(cause); this.code = code; } }
异常处理类
用于处理Controller运行时未捕获的异常的处理类。
import com.rjh.web.exception.BaseException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 异常处理器 * * @author NULL * @since 2019-07-16 */ @ControllerAdvice(annotations = BaseResponse.class) @ResponseBody @Slf4j public class ExceptionHandlerAdvice { /** * 处理未捕获的Exception * @param e 异常 * @return 统一响应体 */ @ExceptionHandler(Exception.class) public ResponseResult handleException(Exception e){ log.error(e.getMessage(),e); return new ResponseResult(ResponseCode.SERVICE_ERROR.getCode(),ResponseCode.SERVICE_ERROR.getMsg(),null); } /** * 处理未捕获的RuntimeException * @param e 运行时异常 * @return 统一响应体 */ @ExceptionHandler(RuntimeException.class) public ResponseResult handleRuntimeException(RuntimeException e){ log.error(e.getMessage(),e); return new ResponseResult(ResponseCode.SERVICE_ERROR.getCode(),ResponseCode.SERVICE_ERROR.getMsg(),null); } /** * 处理业务异常BaseException * @param e 业务异常 * @return 统一响应体 */ @ExceptionHandler(BaseException.class) public ResponseResult handleBaseException(BaseException e){ log.error(e.getMessage(),e); ResponseCode code=e.getCode(); return new ResponseResult(code.getCode(),code.getMsg(),null); } }
响应增强类
Conrtoller增强的统一响应体处理类,需要注意异常处理类已经进行了增强,所以需要判断一下返回的对象是否为统一响应体对象。
import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * 统一响应体处理器 * @author NULL * @date 2019-07-16 */ @ControllerAdvice(annotations = BaseResponse.class) @Slf4j public class ResponseResultHandlerAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { log.info("returnType:"+returnType); log.info("converterType:"+converterType); return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if(MediaType.APPLICATION_JSON.equals(selectedContentType) || MediaType.APPLICATION_JSON_UTF8.equals(selectedContentType)){ // 判断响应的Content-Type为JSON格式的body if(body instanceof ResponseResult){ // 如果响应返回的对象为统一响应体,则直接返回body return body; }else{ // 只有正常返回的结果才会进入这个判断流程,所以返回正常成功的状态码 ResponseResult responseResult =new ResponseResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),body); return responseResult; } } // 非JSON格式body直接返回即可 return body; } }
使用示例
首先准备一个User对象
import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; /** * 用户类 * @author NULL * @date 2019-07-16 */ @Data @EqualsAndHashCode public class User implements Serializable { private Integer id; private String name; }
然后是准备一个简单的UserController即可
import com.rjh.web.entity.User; import com.rjh.web.exception.BaseException; import com.rjh.web.response.BaseResponse; import com.rjh.web.response.ResponseCode; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 测试用的Controller * * @author NULL * @date 2019-07-16 */ @BaseResponse @RestController @RequestMapping("users") public class UserController { @GetMapping("/{userId}") public User getUserById(@PathVariable Integer userId){ if(userId.equals(0)){ throw new BaseException(ResponseCode.RESOURCES_NOT_EXIST); } if(userId.equals(1)){ throw new RuntimeException(); } User user=new User(); user.setId(userId); user.setName("test"); return user; } }
运行结果
在浏览器直接访问http://127.0.0.1:8080/users/0,则返回结果如下(结果经过格式化处理):
{ "code": 10001, "msg": "资源不存在", "data": null }
在浏览器直接访问http://127.0.0.1:8080/users/1,则返回结果如下(结果经过格式化处理):
{ "code": 50000, "msg": "服务器异常", "data": null }
在浏览器直接访问http://127.0.0.1:8080/users/2,则返回结果如下(结果经过格式化处理):
{ "code": 10000, "msg": "success", "data": { "id": 2, "name": "test" } }
由运行结果可以得知统一响应增强其实已经生效了,而且能够很好的处理异常。
示例代码地址
下面是这个示例的代码地址,如果觉得不错或者帮助到你,希望大家给个Star:
https://github.com/spring-based-solutions/spring-web-unified-response-demo
参考资料
https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-controller-advice
https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-exceptionhandler
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍SpringBoot使用统一异常处理详解,包括了SpringBoot使用统一异常处理详解的使用技巧和注意事项,需要的朋友参考一下 场景:针对异常处理,我们原来的做法是一般在最外层捕获异常即可,例如在Controller中 这样的话也能解决部分问题,但是无法获取到自己指定的异常,引入全局统一异常处理的话将会极大的改善代码,减少冗余代码的产生。 自定义异常类:注意要继承自RuntimeE
本文向大家介绍详解Django CAS 解决方案,包括了详解Django CAS 解决方案的使用技巧和注意事项,需要的朋友参考一下 CAS单点登录主要是为了解决主系统和子系统的统一登录问题,能够做到任意一个子系统登录成功后,再登录其他子系统后不再需要认证,让用户不用重复地进行登录认证。CAS单点登录的方案很多,并且大多数都是采用session的方式,而本文结合个人实践,着重讨论django cas
本文向大家介绍详解springboot解决第三方依赖jar包的问题,包括了详解springboot解决第三方依赖jar包的问题的使用技巧和注意事项,需要的朋友参考一下 公司现在用的是springboot+maven,想要把一些老的项目都改成这种框架。但是一些老的项目中有好多第三方的jar包或者是自己的jar包,maven库上没有。最初的解决方案是一个个的deploy到maven库上,但是遇到太多的
本文向大家介绍详解SpringBoot基于Dubbo和Seata的分布式事务解决方案,包括了详解SpringBoot基于Dubbo和Seata的分布式事务解决方案的使用技巧和注意事项,需要的朋友参考一下 1. 分布式事务初探 一般来说,目前市面上的数据库都支持本地事务,也就是在你的应用程序中,在一个数据库连接下的操作,可以很容易的实现事务的操作。 但是目前,基于SOA的思想,大部分项目都采用微服务
本文向大家介绍详解Nginx反向代理WebSocket响应403的解决办法,包括了详解Nginx反向代理WebSocket响应403的解决办法的使用技巧和注意事项,需要的朋友参考一下 在Nginx反向代理一个带有WebSocket功能的Spring Web程序(源代码地址 )时,发现访问WebSocket接口时总是出现403响应,Nginx的配置参考的是 官方文档 : 唯一不同的是我们的Nginx
本文向大家介绍详解AngularJS用Interceptors来统一处理HTTP请求和响应,包括了详解AngularJS用Interceptors来统一处理HTTP请求和响应的使用技巧和注意事项,需要的朋友参考一下 Web 开发中,除了数据操作之外,最频繁的就是发起和处理各种 HTTP 请求了,加上 HTTP 请求又是异步的,如果在每个请求中来单独捕获各种常规错误,处理各类自定义错误,那将会有大量