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

将异常加在注解中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值