SpringBoot统一响应体解决方案_2的32次方的博客-程序员秘密_{code:10000,msg:success}

技术标签: Spring  Springboot  统一响应体  

前言

最近在优化自己之前基于Spring AOP的统一响应体的实现方案。

什么是统一响应体呢?在目前的前后端分离架构下,后端主要是一个RESTful API的数据接口。

但是HTTP的状态码数量有限,而随着业务的增长,HTTP状态码无法很好地表示业务中遇到的异常情况。

那么可以通过修改响应返回的JSON数据,让其带上一些固有的字段,例如以下这样的

{
	"code": 10000,
	"msg": "success",
	"data": {
		"id": 2,
		"name": "test"
	}
}

其中关键属性的用途如下:

  • code为返回结果的状态码
  • msg为返回结果的消息
  • data为返回的业务数据

3个属性为固有属性,每次响应结果都会有带有它们。

需求

希望实现一个能够代替基于AOP的实现方案,需要满足以下几点:

  1. 原有的基于AOP的实现方案需要Controller的返回类型为Object,需要新方案不限制返回类型
  2. 原有的基于AOP的实现方案需要通过切面表达式+注解控制切点的Controller(注解的包名修改会导致切面表达式的修改,即需要修改两处地方),需要新方案能够基于注解,而不需要修改切面表达式

方案思路

基于上述的需求,选择使用SpringController增强机制,其中关键的类为以下3个:

  • @ControllerAdvice:类注解,用于指定Controller增强处理器类。
  • ResponseBodyAdvice:接口,实现后beforeBodyWrite()方法后可以对响应的body进行修改,需要结合@ControllerAdvice使用。
  • @ExceptionHandler:方法注解,用于指定异常处理方法,需要结合@ControllerAdvice@ResponseBody使用。

示例关键代码

本示例使用的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;
    }
    
}

运行结果

  1. 在浏览器直接访问http://127.0.0.1:8080/users/0,则返回结果如下(结果经过格式化处理):
    {
    	"code": 10001,
    	"msg": "资源不存在",
    	"data": null
    }
    
  2. 在浏览器直接访问http://127.0.0.1:8080/users/1,则返回结果如下(结果经过格式化处理):
    {
    	"code": 50000,
    	"msg": "服务器异常",
    	"data": null
    }
    
  3. 在浏览器直接访问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

参考资料

  1. https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-controller-advice
  2. https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-ann-exceptionhandler
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a158123/article/details/96422122

智能推荐

GridView中为按钮添加相应的事件_chenguang79的博客-程序员秘密_yii gridview button添加自定义事件

今天看一个兄弟在CSDN上发了一个帖子,问在GridView中为一个按钮加一个事件,其实就是让一个panel显示出来的问题。个人认为这个问题的主要在于GridView中为一个按钮添加事件,并把相应的ID给它,只要有ID值了。省下的事就好办多了。下面是我做的一个小测试。功能简单,代码如下,在VS2008下测试通过.net2.0前台:asp:GridView ID="GridView1" runat=

20180619 Linux 软件包安装和卸载 7.1 - 7.9_chuzanban6025的博客-程序员秘密

20180619 Linux 软件包安装和卸载 7.1 - 7.97.1 安装软件包的三种方法 7.2 rpm 包介绍7.3 rpm 工具用法7.4 yum 工具用噶7.5 yum 搭建本地仓库 7.6 yum 更换国内源7.7 yum 下载 rpm 包 7.8/7.9 源码包...

从未在一起更让人遗憾_从未在一起和在一起最后又错过了,哪个更遗憾?_weixin_39985286的博客-程序员秘密

从未在一起和在一起又分开了,哪个更遗憾?我觉得从未在一起过更让人遗憾。在一起过又分开,说明两个人不合适或者其他原因。但是分开以后,起码两个人都收获了一段回忆。共同经历了一段时光。彼此拥有过,便不算太遗憾。最让人遗憾的是,你喜欢一个人,两个人却没有交际,从未在一起过。上大学的时候,我们班还是有几个长得还可以的男孩子的。刚开学,大家便给一位男同学扣上了班草的帽子。也许跟所有女孩子心中的完美形象比较切合...

【读书笔记二】《企业IT架构转型之道-阿里巴巴中台战略思想与架构实战》_尘世间一名迷途小码农的博客-程序员秘密_如何引用 企业it架构转型之道 这本书

目录第三章分布式服务框架的选择一、淘宝服务化历程二、“中心化”与“去中心化”服务第三章分布式服务框架的选择一、淘宝服务化历程截止到2007年,淘宝已经拥有超过500人的技术团队规模,整个淘宝网站是一个几百兆字节的WAR包,功能模块超过200个。几百人维护一个WAR包的模式,带来了以下几个主要问题: 项目团队间协同成本高,业务响应越来越慢。 应用复杂度已超出人的认知负载。各种业务互相交错,已经没有一个人能完全清楚每个功能或流程的细节,毕竟人的认知...

Unity 渲染层级 RenderQueue SortingLayer SortingOrder_Eggbreaker2077的博客-程序员秘密

Unity 渲染层级 RenderQueue SortingLayer SortingOrder相机渲染顺序相机的渲染顺序,由Camera的Depth参数控制RenderQueueQueue分为五个队列:Background,天空盒,天生用来被覆盖的(1000)Geometry,不透明物渲染队列(2000)AlphaTest,通道测试的几何体使用这个队列,这些物体的渲染有点介于Opaue与Transparent之间(2450)Geometrylast,特殊的点,定义了透明与不透明的分界点(2

力扣(leetcode) 461. 汉明距离(超简单解法)(传统解法)_深度不学习!!的博客-程序员秘密

题目在这:https://leetcode-cn.com/problems/hamming-distance/题目解释:题目求二进制的汉明距离,实际上就是求将一个字符串变成另一个字符串要改变的位数。比如字符串A:1010 B:1110 。显然将A转换成B需要将第二位的0改成1。只需要改变一位。所以A和B的汉明距离为1。法一:思路分析:可以直接把A和B两个字符串变成二进制放到列表中,然后对比两个列表的相同位。不同则让结果加1。而在python中使用bin()函数将十进制转换成二进制,位数可能

随便推点

contacts(通讯录)数据库_科克蛙的博客-程序员秘密_contacts calls表格

Android通讯录有关通讯录的资料都放在contacts2.db中这个数据库放在/data/data/com.android.providers.contacts/databases/contacts.db通话记录表calls字段名称说明_id索引Key,自增长number通话电话号码date拨打该电话号码的开始时间(以1970-01-01 00:00:0

matlab矩阵鞍点,最大熵模型(matlab应用).ppt_机器人之路的博客-程序员秘密

非线性规划中的对偶问题 拉格朗日函数: 于是: 因此,为了尽量大,p的选取必须保证 考虑: 只要令lambda(i)=负无穷大就行了 对偶问题与拉格朗日函数: 同时: 等价于: 而 可以证明,这里等号成立。不过证明比较复杂,是单独一篇论文了(见参考资料4) 对偶问题与拉格朗日函数: 至此,我们可以通过找min lambda L(p*,lambda)来找出合适的lambda了,这可以用各种近似方法(...

能够PING通同网段的节点, 但却PING不通其他网段的所有节点的最可能的原因是_小朋友/?的博客-程序员秘密_同网段可以ping,跨网段不通

能够PING通同网段的节点, 但却PING不通其他网段的所有节点的最可能的原因是A: 本机网关设置错误B: 本机没有正确设置DNSC: 对方运行的是不同的操作系统D: 二层交换机故障正确选项: A解析:A:ping是TCP/IP协议族的一部分,其属于网络层协议。主要是用来检测网络是否通畅。如果要ping其他网段,则需要设置网关。B:和DNS没有关系, DNS设置错会...

ImageView显示长图_chencangui的博客-程序员秘密_imageview长图

显示长图失败我们知道,ImageView经常用来显示一些位图,但是不是所有的图片都能够正常显示的,很明显不是的,当图片超过了一定的大小之后,你会发现,调用setImageBitmap(Bitmap bitmap)方法之后,图片没有正常显示出来,并且可显示的区域也没有显示对应的图片,这是为什么呢?测试做了一个实验,将一个如图大小的Bitmap设置给ImageView,图片没有显示出来,并且出现...

基于STM32的电梯智能控制系统(软件实现)_Ombré_mi的博客-程序员秘密

主函数:运行结果: IIC oled屏幕驱动原理处理器和芯片间的通信可以形象的比喻成两个人讲话:1、你说的别人得能听懂:双方约定信号的协议。2、你的语速别人得能接受:双方满足时序要求。一、IIC总线的信号类型1、开始信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。2、停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它

Faster R-CNN源码阅读之一:Faster R-CNN/lib/networks/network.py_子为空的博客-程序员秘密

一、介绍    本demo由Faster R-CNN官方提供,我只是在官方的代码上增加了注释,一方面方便我自己学习,另一方面贴出来和大家一起交流。    该文件中的函数和类的主要目的是产生一个基类,并在类中封装好需要的方法,以后生成网络时可以直接调用已经封装好的方法。二、代码以及注释 # -*- coding:utf-8 -*-import numpy as npimport...

推荐文章

热门文章

相关标签