springboot 统一返回体(随笔)
前言
在我们日常开发中,总是要和APP、H5、PC等多端的进行对接;
此时就需要设计一个合适的接口返回体,以便统一交互;
思路
- 定义一个泛型返回体,以此包装所有的接口返回值,让开发只关注业务就行;
- 自定义返回体注解,用于标注哪些类或方法需要包装返回值;
- ResponseBodyAdvice 实现类,并结合 @ControllerAdvice 注解,实现正常返回值或异常情况时返回值的封装;
定义一个泛型返回体
@Data
public class Response<T> implements Serializable {
private static final long serialVersionUID = -1220656299702215742L;
private String code;
private String message;
private T data;
public static <T> Response ok(T data) {
return new Response("200", "success", data);
}
public static <T> Response ok(String code, String message, T data) {
return new Response(code, message, data);
}
public static <T> Response fail(T data) {
return new Response("500", "fail request", data);
}
public static <T> Response fail(String code, String message, T data) {
return new Response(code, message, data);
}
private Response(String code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
}
这里,只是简单的定义了返回体,有兴趣的同学,可以了解下枚举+返回体
的操作;
返回体中,我们只定义了三个变量:
code:返回码
message:消息
data:返回的具体数据
这里我们还把构造私有化了,这里时防止调用的时候有人通过 new 的方式包装数据,这不符合我们一开始定义的规范,需要通过我们定义好的静态方法实现返回值的包装;
自定义返回体注解
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Documented
public @interface ResponseResult {
}
这个就不需要多解释了,声明这个注解可以使用在类和方法上;
ResponseBodyAdvice 实现类
@Slf4j
@ResponseBody
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/**
* 判断是否包含 @ResponseResult 注解,不包含直接返回,不重写返回体
*
* @param returnType
* @param converterType
* @return boolean
*/
@Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> clazz = returnType.getExecutable().getDeclaringClass();
return clazz.isAnnotationPresent(ResponseResult.class) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* 返回体重构
*
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return java.lang.Object
*/
@Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
log.info("重写结构。。。。");
return Response.ok(body);
}
/**
* 自定义异常拦截
*
* @param e
* @return com.example.demo.utils.Response
*/
@ExceptionHandler(MyException.class)
public Response getException(MyException e) {
log.error("自定义异常:{},{}", e.getCode(), e.getMessage());
return Response.fail(e.getCode(), e.getMessage(), null);
}
/**
* 最大异常拦截
*
* @param e
* @return com.example.demo.utils.Response
*/
@ExceptionHandler(Exception.class)
public Response getException(Exception e) {
log.error("异常:{}", e);
return Response.fail(e.getMessage());
}
}
重写 supports 和 beforeBodyWrite 方法,以及拦截异常;
通过阅读 ResponseBodyAdvice
源码中的 supports 和 beforeBodyWrite 方法,可以知道,当 supports 返回
true
时,才会执行 beforeBodyWrite
方法;
在 supports
方法中可以通过 MethodParameter
来获取类或方法上的注解,借此判断注解上是否有我们自定义的 @ResponseResult
注解,如果有的话,就返回true,否则就是false;
在 beforeBodyWrite
将返回值包装到我们自定的返回体中;
拦截异常,就是 @ExceptionHandler
注解中指定异常类型,那么该方法就会拦截你指定的异常,拦截后就会执行我们自定义的异常处理逻辑,最后返回时,使用我们自定的返回体封装结果,就万事大吉了;
注意:拦截异常时,需要有一个最大的异常兜底,因为没有最大异常兜底的话,那么当系统抛出一个拦截异常中最大异常还大的异常,就会捕捉不到,那么我们设置的异常拦截,那将毫无意义;
总结
小伙伴们,最好亲自动手,写一写,总会有不一样的收获,比如 你的最爱--BUG
!!!