SpringBoot统一响应体解决方案

参考文章:https://segmentfault.com/a/1190000019795918

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

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

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

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

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

一、方案思路

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

  • @ControllerAdvice:类注解,用于指定Controller增强处理器类。
  • ResponseBodyAdvice:接口,实现后beforeBodyWrite()方法后可以对响应的body进行修改,需要结合@ControllerAdvice使用。
  • @ExceptionHandler:方法注解,用于指定异常处理方法,需要结合@ControllerAdvice和
    @ResponseBody使用。
  • @Configuration解决返回体为String时报错的问题

二、示例代码

1. 需要引入lombok依赖

<!--lombok-->
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
 </dependency>

2. 统一响应体

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;

}

3. 统一响应注解

统一响应注解是一个标记是否开启统一响应增强的注解

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 { }

4. 状态码枚举

统一响应体中返回的状态码code和状态信息msg对应的枚举类

/**
 * 返回状态码
 *
 * @author NULL
 * @date 2019-07-16
 */
public enum ResponseCode {
     
    /**
     * 成功返回的状态码
     */
    SUCCESS(10200, "success"),
    /**
     * 资源不存在的状态码
     */
    RESOURCES_NOT_EXIST(10201, "资源不存在"),
    /**
     * 所有无法识别的异常默认的返回状态码
     */
    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;
    }
}

5. 业务异常类

业务异常类是用于识别业务相关的异常,需要注意这个异常类强制需要以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;
    }
}

6. 异常处理类

用于处理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);
    }
}

7. 响应增强类

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;
    }
}

8. 使用示例

首先准备一个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();
        }
        if(userId.equals(2)){
     
            return "你好";
        }
        User user=new User();
        user.setId(userId);
        user.setName("test");
        return user;
    }
    
}

运行结果

  1. 在浏览器直接访问http://127.0.0.1:8080/users/0,则返回结果如下(结果经过格式化处理):
{
     
    "code": 10201,
    "msg": "资源不存在",
    "data": null
}
  1. 在浏览器直接访问http://127.0.0.1:8080/users/1,则返回结果如下(结果经过格式化处理):
    "code": 50000,
    "msg": "服务器异常",
    "data": null
}
  1. 在浏览器直接访问http://127.0.0.1:8080/users/2,则返回结果如下(结果经过格式化处理):
你好
  1. 在浏览器直接访问http://127.0.0.1:8080/users/3,则返回结果如下(结果经过格式化处理):
{
     
    "code": 10200,
    "msg": "success",
    "data": {
     
        "id": 2,
        "name": "test"
    }
}

由运行结果可以得知统一响应增强其实已经生效了,而且能够很好的处理异常。

9. 解决返回String类型时格式不准确的问题

参考文章:https://www.cnblogs.com/oldboyooxx/p/10824531.html

到这里统一响应和异常处理已经完成了,但是需要注意的是,在上述运行结果3中,如果返回的是字符串,并没有包装成统一的响应体,原因是我们在ResponseResultHandlerAdvice类中做了处理:“// 非JSON格式body直接返回即可”,若想让字符串也以统一响应格式返回的话,可以这么做

(1).去除ResponseResultHandlerAdvice类中的判断

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(body instanceof ResponseResult){
      // 如果响应返回的对象为统一响应体,则直接返回body
        	return body;
        }else{
     
            // 只有正常返回的结果才会进入这个判断流程,所以返回正常成功的状态码
            ResponseResult responseResult =new ResponseResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),body);
            return responseResult;
        }
    }
}

这样所有的类型都会使用ResponseResult包装一次,但是,这是候如果返回的是字符串的话,会报出如下错误:java.lang.ClassCastException: com.xxx.dto.common.ResponseResult cannot be cast to java.lang.String

(2).问题原因及解决方案

因为在所有的 HttpMessageConverter 实例集合中,StringHttpMessageConverter 要比其它的 Converter 排得靠前一些。我们需要将处理 Object 类型的 HttpMessageConverter 放得靠前一些,这可以在 Configuration 类中完成:
在WebConfiguration方法中添加configureMessageConverters方法

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
     

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
     
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}

这样在浏览器访问http://127.0.0.1:8080/users/2,则返回结果如下(结果经过格式化处理):

{
     
    "code": 10200,
    "msg": "success",
    "data": "你好"
}

你可能感兴趣的:(SpringBoot,Java,解决方案,springBoot,java)