为什么要使用统一功能
- 后端返回格式多样,前端处理起来较麻烦统一数据格式之后前端处理方便,不用每个接口使用不同的格式接收;
- 统一数据返回格式可以减少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~