资讯专栏INFORMATION COLUMN

SpringBoot统一响应体解决方案

figofuture / 1750人阅读

摘要:前言最近在优化自己之前基于的统一响应体的实现方案。但是的状态码数量有限,而随着业务的增长,状态码无法很好地表示业务中遇到的异常情况。

前言

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

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

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

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

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

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

code为返回结果的状态码

msg为返回结果的消息

data为返回的业务数据

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

需求

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

原有的基于AOP的实现方案需要Controller的返回类型为Object,需要新方案不限制返回类型

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

方案思路

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

@ControllerAdvice:类注解,用于指定Controller增强处理器类。

ResponseBodyAdvice:接口,实现后beforeBodyWrite()方法后可以对响应的body进行修改,需要结合@ControllerAdvice使用。

@ExceptionHandler:方法注解,用于指定异常处理方法,需要结合@ControllerAdvice@ResponseBody使用。

示例关键代码

本示例使用的Spring Boot版本为2.1.6.RELEASE,同时需要开发工具安装lombok插件

引入依赖
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.projectlombok
            lombok
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
统一响应体

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;

/**
 * 统一响应注解
* 添加注解后,统一响应体才能生效 * @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-bas...

参考资料

https://docs.spring.io/spring...

https://docs.spring.io/spring...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75452.html

相关文章

  • Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error

    摘要:挺多人咨询的,异常处理用切面注解去实现去全局异常处理。全局异常处理类,代码如下代码解析如下抽象类是用来处理全局错误时进行扩展和实现注解标记的切面排序,值越小拥有越高的优先级,这里设置优先级偏高。 本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的。 一、为什么要全局...

    BicycleWarrior 评论0 收藏0
  • 慕课网_《SpringBoot进阶之Web进阶》学习总结

    摘要:时间年月日星期日说明本文部分内容均来自慕课网。慕课网教学示例源码个人学习源码第一章课程介绍课程介绍本课程紧接着小时学会课程,请先看入门课。异常返回通知在连接点抛出异常后执行。 时间:2017年3月19日星期日说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:htt...

    lifefriend_007 评论0 收藏0
  • 【ShareBook】1-后台框架与小程序用户登录接口实战

    摘要:注册流程是从小程序简称,以下替代获取用户的,给到服务器,服务器会用还有自己的等信息一起去微信服务器请求用户数据,注意每一个所对应的用户都是不一样的。 本博客 猫叔的博客,转载请申明出处阅读本文约 5分钟适读人群:Java后端、Java初级、小程序前端 前后端项目的地址 ShareBookServer ShareBookClient 小程序前端 showImg(https://seg...

    zorro 评论0 收藏0
  • SpringBoot 中 @SpringBootApplication注解背后的三结构探秘

    摘要:概述约定大于配置的功力让我们如沐春风,在我之前写的文章从到也对比过和这两个框架,不过最终以超高的代码信噪比和易上手性让我们映像颇深。至于,我想在非时代大家应该不陌生吧,作用是配置容器,也即形式的容器的配置类所使用。 showImg(https://segmentfault.com/img/remote/1460000015822144); 概 述 SpringBoot 约定大于配置...

    Tecode 评论0 收藏0
  • SpringBoot RocketMQ 整合使用和监控

    摘要:前提通过前面两篇文章可以简单的了解和安装,今天就将和整合起来使用。然后我运行之前的整合项目,查看监控信息如下总结整篇文章讲述了与整合和监控平台的搭建。 showImg(https://segmentfault.com/img/remote/1460000013232432?w=1920&h=1277); 前提 通过前面两篇文章可以简单的了解 RocketMQ 和 安装 RocketMQ...

    Jacendfeng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<