Spring--统一数据返回格式与统一异常处理

1.统一数据返回格式

强制登录案例中, 我们共做了两部分⼯作

  1. 通过Session来判断⽤户是否登录

  2. 对后端返回数据进⾏封装, 告知前端处理的结果(设置status401等)

回想我们在给前端返回时进行了一个统一的数据包装

复制代码
@Data
public class Result<T> {
    private int status;//1.  200成功   2. -1未登录 3. -2  异常
    private String errorMsg;//错误信息
    private T data;

    /**
     * 登录成功
     * @param data
     * @param <T>
     * @return
     */
    public static <T> Result success(T data) {
        Result result = new Result();
        result.setStatus(ResultStatus.SUCCESS.getCode());
        result.setErrorMsg("");
        result.setData(data);
        return result;
    }

    /**
     * 未登录
     * @return
     */
    public static Result unLogin() {
        Result result = new Result();
        result.setErrorMsg("未登录,请登录后访问");
        result.setStatus(ResultStatus.UNLOGIN.getCode());
        return result;
    }

    /**
     * 登录失败
     * @param data
     * @param <T>
     * @return
     */
    public static <T> Result fail(String msg) {
        Result result = new Result();
        result.setErrorMsg(msg);
        result.setStatus(ResultStatus.FAIL.getCode());
        result.setData("");
        return result;
    }
}

如果能将返回的数据全部都包装为Result那么代码就更加健壮了(方便后续的添加修改)

参考拦截器,我们肯定不会对原来的方法返回结果直接一个一个修改。

SpringBoot也对此功能进行了支持

使用方法:

统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现

复制代码
@ControllerAdvice
public class ResponseAdvice 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 Result.success(body);
 }
}

@ControllerAdvice注解已经包含五大注解,会交给Spring进行管理

这个类有两个方法:supports(),beforeBodyWrite()

supports():判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏

可以通过参数获取类及方法书写逻辑:

复制代码
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //获取执行方法的类
        Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
        //获取执行的方法
        Method method = returnType.getMethod();
        //判断是否执行beforeBodyWrite方法   true--执行  false--不执行
        return true;
    }

beforeBodyWrite(): 对response⽅法进⾏具体操作处理

在这里是将返回数据包装后返回给前端

但是这里如果直接对String进行包装返回会出现错误:

写一个测试类:

复制代码
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public Integer t2() {
        return 2;
    }

    @RequestMapping("/t3")
    public Boolean t3() {
        return true;
    }
}

而其他类型不会出现这种情况。出现这种情况是在源码阶段的类型不匹配(这里不进行深究)

解决方法是将返回的Result转换成Spring类型。

复制代码
         private static ObjectMapper mapper = new ObjectMapper();        
         if(body instanceof String) {
            //将result类型转化为String类型   否则在源码阶段会出现类型不匹配
            return  mapper.writeValueAsString(Result.success(body));
        }

如果返回的类型就是Result则不用转换,直接返回

复制代码
        //如果body是Result类型就直接返回
        if(body instanceof Result) {
            return body;
        }

最终代码:

复制代码
/**
 * 统一结果返回
 */
@Slf4j
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    private static ObjectMapper mapper = new ObjectMapper();

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //获取执行方法的类
        Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
        //获取执行的方法
        Method method = returnType.getMethod();
        //判断是否执行beforeBodyWrite方法   true--执行  false--不执行
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType
            , MediaType selectedContentType, Class selectedConverterType
            , ServerHttpRequest request, ServerHttpResponse response) {
        //判断body是否是String类型
        if(body instanceof String) {
            //将result类型转化为String类型   否则在源码阶段会出现类型不匹配
            return  mapper.writeValueAsString(Result.success(body));
        }
        //如果body是Result类型就直接返回
        if(body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }
}

2.统一异常处理

在上一步统一结果返回时,如果类型不匹配,并没有爆出异常(最外层显示200),这是我们不能接收的。

我们当然可以对每一块代码进行trycacth环绕,但同样面临冗余的情况。

解决方法是:统一异常处理

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的

@ControllerAdvice 控制器通知类(标识作用), @ExceptionHandler 是异常处理器,两个结合表⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件

记得加: @ResponseBody

复制代码
/**
 * 统一异常处理
 */
//接⼝返回为数据时, 需要加 @ResponseBody 注解
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
    @ExceptionHandler
    public Object handler(Exception e) {
        return Result.fail(e.getMessage());
    }
}

如果代码出现Exception异常(包括Exception的⼦类), 就返回⼀个 Result的对象, Result 对象的设置参考 Result.fail(e.getMessage())

复制代码
    public static <T> Result fail(String msg) {
        Result result = new Result();
        result.setErrorMsg(msg);
        result.setStatus(ResultStatus.FAIL.getCode());
        result.setData("");
        return result;
    }

当然,只能爆Exception这么宽泛的异常就没有意义了

我们可以针对不同的异常, 返回不同的结果

复制代码
@ExceptionHandler
    public Object handler(Exception e) {
        return Result.fail(e.getMessage());
    }

    //空指针异常
    @ExceptionHandler
    public Object handler(NullPointerException e) {
        return Result.fail(e.getMessage());
    }

    //算数异常
    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.fail(e.getMessage());
    }

现在我们在测试类中添加两个异常观察一下:

复制代码
    @RequestMapping("/t1")
    public String t1() {
        String s = "1";
        char a = s.charAt(2);
        return "t1";
    }

    @RequestMapping("/t2")
    public Integer t2() {
        int s = 10/0;
        return 2;
    }

它会抛出距离最近的异常

针对不同的异常, 返回不同的结果还有一种写法:

复制代码
    //算数异常
    @ExceptionHandler(ArithmeticException.class)
    public Object handler1(Exception e) {
        return Result.fail(e.getMessage());
    }
    @ExceptionHandler(NullPointerException.class)
    public Object handler2(Exception e) {
        return Result.fail(e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Object handler3(Exception e) {
        return Result.fail(e.getMessage());
    }

将异常加在注解中

相关推荐
朝新_1 小时前
【多线程初阶】阻塞队列 & 生产者消费者模型
java·开发语言·javaee
立莹Sir1 小时前
Calendar类日期设置进位问题
java·开发语言
季鸢3 小时前
Java设计模式之状态模式详解
java·设计模式·状态模式
@yanyu6663 小时前
springboot实现查询学生
java·spring boot·后端
ascarl20103 小时前
准确--k8s cgroup问题排查
java·开发语言
magic 2453 小时前
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
java
爱敲代码的憨仔3 小时前
分布式协同自动化办公系统-工作流引擎-流程设计
java·flowable·oa
纪元A梦4 小时前
分布式拜占庭容错算法——PBFT算法深度解析
java·分布式·算法
卿着飞翔4 小时前
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
java·rabbitmq·java-rabbitmq
陈阿土i4 小时前
SpringAI 1.0.0 正式版——利用Redis存储会话(ChatMemory)
java·redis·ai·springai