统一功能:统一返回格式+统一异常处理

为什么要使用统一功能

  • 后端返回格式多样,前端处理起来较麻烦统一数据格式之后前端处理方便,不用每个接口使用不同的格式接收;
  • 统一数据返回格式可以减少controller的重复代码
  • 统一异常处理避免一些异常忘记处理
  • 统一异常处理后前端接收后可根据异常种类进行不同的处理

如何进行统一数据返回

通过**@ControllerAdvice和ResponseBodyAdvice**可进行统一数据返回

@ControllerAdvice:既可以统一数据返回也可以统一异常处理,作用于所有的Controller

@RestControllerAdvice=@ResponseBody+@ControllerAdvice

1.定义返回数据的格式

java 复制代码
@Data
public class Result<T> {
    T data;//返回数据
    private Integer code;//业务码
    private String msg;//返回信息
    public  static<T> Result success(T data){
        Result result=new Result();
        result.setData(data);
        result.setCode(ResultCode.SUCCESS.getCode());
        result.setMsg(ResultCode.SUCCESS.getMessage());
        return result;
    }
    public  static<T> Result fail(String message){
        Result result=new Result();
        result.setMsg(message);
        result.setCode(ResultCode.FAIL.getCode());
        return result;
    }
    public  static<T> Result unLogin(){
        Result result=new Result();
        result.setCode(ResultCode.UNLOGIN.getCode());
        result.setMsg(ResultCode.UNLOGIN.getMessage());
        return result;
    }
}

由于不知道方法具体返回的参数类型是什么,所以这里的data使用泛型表示

2.定义枚举业务码

java 复制代码
public enum ResultCode {
    SUCCESS(200,"操作成功"),
    FAIL(401,"操作失败"),
    UNLOGIN(404,"未登录");

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    private Integer code;
    private String message;
    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }


}

可以枚举出不同的code和对于的message

3.创建ResponseBodyAdvice,重写supports和beforeBodyWrite方法

java 复制代码
@Component
@RestControllerAdvice
public class ResultUndefied implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return false;
    }

    @Nullable
    @Override
    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return null;
    }
}

Boolean supports(MethodParameter returnType, Class converterType):return true表示处理方法的返回结果,return false表示不出来方法的返回结果。所以supports里面可以判断是否要统一数据返回。

Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) :通过body可以拿到方法的放回参数,在里面进行方法的返回结果处理,Object是返回类型,可根据实际更改

举例:将方法返回参数包装成Result

Controller类

java 复制代码
@RestController
public class Controller {
    @RequestMapping("/r1")
    public Integer r1(){
        return 1;
    }
    @RequestMapping("/r2")
    public Boolean r2(){
        return true;
    }
    @RequestMapping("/r3")
    public String r3(){
        return "aa";
    }
}
java 复制代码
@Component
@RestControllerAdvice
public class ResultUndefied implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Nullable
    @Override
    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        return Result.success(body);
       
    }
}

通过返回Result.success(body),把接口方法的返回值都封装起来。为了方便测试,这里我们让supports返回true对所以方法进行处理。

运行SpringBoot,通过postman或浏览器访问接口

测试r1

测试r2

测试r3

我们看到,r1,r2都成功了,为什么r3会失败呢?

那是因为我们r3的返回结果是String类型,ResponseBodyAdvice会对String进行单独处理。简单来讲,返回类型为String,使用的转换器是StringHttpMessageConverter,直接写字符串,不走JSON,所以当我们返回String时,要将结果转成Json序列化。利用objectMapper.writeValueAsString()可以做到

java 复制代码
@Slf4j
@Component
@RestControllerAdvice
public class ResultUndefied implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Nullable
    @Override
    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        ObjectMapper objectMapper=new ObjectMapper();
        if(body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        if(body instanceof Result){
            return body;
        }
            log.info("返回值"+body);
            return Result.success(body);
            
    }
}

如何进行统一异常处理

通过@ControllerAdvice和@ExceptionHandle可以进行统一异常处理

java 复制代码
@RestControllerAdvice
public class errorException {
   @ExceptionHandler(Exception.class)
    public Result handle(Exception e){
       return Result.fail(e.getMessage());
   }
}

另一种写法:将异常类型写在方法参数中

java 复制代码
@RestControllerAdvice
public class errorException {
   @ExceptionHandler
    public Result handle(Exception e){
       return Result.fail(e.getMessage());
   }
}

测试用例

java 复制代码
@RestController
public class Controller {
  @RequestMapping("/r1")
    public Integer r1(){
      int i =10/ 0;
      //除数不能为零
        return 1;
    }
    @RequestMapping("/r2")
    public Boolean r2(){
      int []a={1,2,3,4};
      int b=a[6];
      //数组越界
        return true;
    }
      @RequestMapping("/r3")
    public String r3(){
          UserInfo userInfo=null;
          String password = userInfo.getPassword();       
        //空指针
        return "aa";
}

测试结果

可以同时统一多种异常类型

java 复制代码
@RestControllerAdvice
public class errorException {
   @ExceptionHandler(NullPointerException.class)
    public Result handle1(Exception e){
       return Result.fail(e.getMessage());
   }
    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public Result handle2(Exception e){
        return Result.fail(e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Result handle3(Exception e){
        return Result.fail(e.getMessage());
    }
}

建议设置Exception异常处理当保底,防止有异常未被捕获到。

Exception的捕获顺序

全局异常处理器在捕获到异常的时候,会优先按自定义业务异常处理,如果没有定义到就考虑(参数格式),如果都没有的话,最后再按Exception处理,所以如果没有定义Exception异常处理的话,当发生我们未考虑到的异常的时候,容易没有被处理到。所以统一异常处理器是层层向上处理异常的。

面试考点

  • 1. Q:为什么要做统一返回?标准返回体怎么设计?

A:进行统一返回处理一方面是为了统一接口返回,前端在拿到响应可以不用频繁看接口文档来确定变量名称,减少沟通成本,从而提高开发效率,同时也可以让我们的代码变的更整洁美观,变量的名称定义更加正规。另一方面是统一数据返回可以和统一异常处理搭配使用,当发生异常时,异常处理器可以根据返回体设置返回信息,而不是前端报500页面等错误。这样无论是接口执行成功还是失败,都是返回一模一样的JSON结构。标准返回体一般公司会在接口文档中定义,会根据不同的场景返回不同的响应,一般返回体中会包含业务状态码,错误信息,和要返回的数据······

  • 2.Q:如果接口返回类型是void,ResponseBodyAdvice 还能包装吗?

A:能,即使返回类型是void,只要使用了@ControllerAdvice和ResponseBodyAdvice都可以将响应包装成返回体的形式。

  • 3.Q:过滤器和拦截器中的异常可以被捕获到吗?

A:不能!通过@ControllerAdvice和@ExceptionHandle进行统一异常处理只能解决controller出现的并且是有定义处理器中的异常,简单来讲,拦截器和过滤器在controller层实现之前,即使里面出现在处理器中定义的异常,也不能被统一异常处理!

最后祝大家都可以拿到理想的offer~

相关推荐
今天AI了吗2 小时前
什么是 AI Agent?它与直接调用大模型 API 有何区别
java·网络·人工智能·架构·java-ee
步行cgn2 小时前
Spring 注入 Map 集合详解
数据库·python·spring
隐退山林2 小时前
JavaEE进阶:SpringAOP
java·java-ee
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之方法级安全注解
java·安全·spring
Bs_MoneyMagnet3 小时前
基于springboot+vue的旅游景区点评系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·旅游
captain3764 小时前
网络原理(3)-TCP核心机制连接管理▲▲▲
java·服务器·网络·ide·网络协议·tcp/ip·java-ee
sbjdhjd5 小时前
ThinkPHP 5.0.10 缓存写入型 RCE 复盘:从手动搭建、换行绕过到源码单步验证 | 05
java·后端·安全·spring·网络安全·数据挖掘·php
麻瓜code6 小时前
[Agent]Spring AI 工具调用实战:让大模型真正“干活“(@Tool 六大工具 + 统一注册)
java·人工智能·spring
宸津-代码粉碎机6 小时前
Spring AI 高危CVE漏洞深度复盘|生产禁跑版本汇总+临时防御+修复方案
java·大数据·人工智能·python·spring