统一数据返回格式
统一数据返回格式就像我们寄快递一样,不管你需要寄的东西具体是什么都需要将它打包到统一的快递箱中。
此时我们需要一个“快递箱”用来将返回的数据“装”在里面。这个类是根据业务情况来自行定义的。
@Data
public class Resp<T> {
//200-正常 0-发生异常
private Integer code;
//错误信息
private String desc;
//返回数据
private T data;
//成功
public static <T> Resp<T> seccess(T data) {
Resp<T> resp = new Resp<>();
resp.setCode(200);
resp.setData(data);
return resp;
}
}
有了“快递箱之后”还需要一个“工作人员”来将“快递”进行打包,此时我们需要创建一个新类并使其实现ResponseBodyAdvice 接口并重写里面的方法,然后给当前类加上@ControllerAdvice注解。
@ControllerAdvice
public class Advice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return false;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return null;
}
}
supports():这个方法用来决定是否执行beforeBodyWrite()方法,如果返回true表示执行,否则返回false。通过该方法可以选择哪些类或哪些方法的响应要进行处理,其他的不进行处理;beforeBodyWrite():对响应进行具体操作处理。
先创建两个用于测试的接口:
@RestController
@RequestMapping("/test")
public class Test {
@RequestMapping("/fun1")
public boolean fun1() {
return false;
}
@RequestMapping("/fun2")
public Integer fun2() {
return 34;
}
}
接下来将fun1和fun2方法的返回值进行“打包”。
@ControllerAdvice
public class Advice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Resp.seccess(body);
}
}
但是实际应用时还有几个问题:
问题一:重复打包
如果此时fun1返回的就是Resp类型的值那么就会出现重复打包的问题。
@RequestMapping("/fun1")
public Resp<Boolean> fun1() {
return Resp.seccess(false);
}
解决方法就是可以在打包前进行一次判断,如果已经被打包了就直接返回,否则进行打包。
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Resp) {
return body;
}
return Resp.seccess(body);
}
问题二:ClassCastException: com.example.Spring_demo.Resp cannot be cast to java.lang.String
这个问题的出现场景是在返回值为String类型时出现的。假设fun1现在返回值是String类型:
@RequestMapping("/fun1")
public String fun1() {
return "hahaha";
}
解决方法就是如果返回结果为String类型, 使用SpringBoot内置提供的Jackson来实现信息的序列化。
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
return new ObjectMapper().writeValueAsString(Resp.seccess(body));
}
if (body instanceof Resp) {
return body;
}
return Resp.seccess(body);
}
注意:此时返回的是字符串。
使用这个就可以解决:@RequestMapping(value = "/fun1", produces = "application/json")
优点
- 方便前端程序员更好的接收和解析后端数据接口返回的数据;
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的;
- 有利于项目统一数据的维护和修改;
- 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。
统一异常处理
如果fun1方法在执行时出现异常也会出现不合理的返回信息:
@RequestMapping(value = "/fun1", produces = "application/json")
public String fun1() {
System.out.println(3/0);
return "hahaha";
}
此时就需要用到统一异常处理来对程序执行过程中发生的异常进行捕获和处理。统一异常处理需要使用两个注解@ExceptionHandler@ControllerAdvice,如果返回的是数据还需要加上@ResponseBody注解。
下面代码表示,如果代码出现Exception异常(包括Exception的子类)才会被捕获,然后执行该方法。
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Resp.fail("Exception: 程序出现异常");
}
}
当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配。如此时有两个,继续访问fun1方法。
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Resp.fail("Exception: 程序出现异常");
}
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Resp.fail("ArithmeticException: 程序出现算数异常");
}
}