1.统一数据返回格式
强制登录案例中, 我们共做了两部分⼯作
通过Session来判断⽤户是否登录
对后端返回数据进⾏封装, 告知前端处理的结果(设置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());
}
将异常加在注解中