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());
    }

将异常加在注解中

相关推荐
不当菜虚困6 分钟前
JAVA设计模式——(二)组合模式
java·设计模式·组合模式
jack_xu1 小时前
经典大厂面试题——缓存穿透、缓存击穿、缓存雪崩
java·redis·后端
CHQIUU2 小时前
Java 设计模式心法之第4篇 - 单例 (Singleton) 的正确打开方式与避坑指南
java·单例模式·设计模式
碎梦归途2 小时前
23种设计模式-结构型模式之享元模式(Java版本)
java·开发语言·jvm·设计模式·享元模式
lozhyf3 小时前
Eureka搭建
java·spring cloud
幽络源小助理3 小时前
SpringBoot民宿管理系统开发实现
java·spring boot·springboot·民宿系统
东阳马生架构3 小时前
Nacos简介—1.Nacos使用简介
java
爱发飙的蜗牛3 小时前
springboot--web开发请求参数接收注解
java·spring boot·后端
码熔burning3 小时前
【MQ篇】RabbitMQ之工作队列模式!
java·分布式·rabbitmq·mq