参考文章:https://segmentfault.com/a/1190000019795918
通过修改响应返回的JSON数据,让其带上一些固有的字段,例如以下这样的
{
"code": 10000,
"msg": "success",
"data": {
"id": 2,
"name": "test"
}
}
其中关键属性的用途如下:
这3个属性为固有属性,每次响应结果都会有带有它们。
基于上述的需求,选择使用Spring的Controller增强机制,其中关键的类为以下3个:
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;
}
}
运行结果
{
"code": 10201,
"msg": "资源不存在",
"data": null
}
"code": 50000,
"msg": "服务器异常",
"data": null
}
你好
{
"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": "你好"
}