RestControllerAdvice注解与全局异常处理

8,358 阅读4分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。

1、前言

从该注解的名字可以看到,这是一个与切面有关的注解,事实上也是如此,我们都知道切面的注解肯定都有个作用范围,切面类的注解只能对其作用范围内的操作,实现切面操作。那RestCntrollerAdvice的作用范围是什么呢?

2、@RestControllerAdvice注解

上面已经说了,每个切面注解都会有自己的作用范围,这个自然也不例外。RestControllerAdvice的作用范围是:单个项目中所有使用了RequestMapping(像PostMapping底层是使用了RequestMapping注解的也支持)的类都归他管,那归RestControllerAdvice他管了,已经清楚了,RestControllerAdvice这个注解要干什么呢?好像根据他的名字我们只能看到这是一个应用于Controller层的切面注解,其他就看不到了。其实该注解还需要与其他注解配合使用才有意义,单独的使用该注解是没有任何意义的,下面就来介绍下该注解都可以与哪些注解配合使用

2.1 @ExceptionHandler

RestControllerAdvice+ExceptionHandler这两个注解的组合,被用作项目的全局异常处理,笔者目前的项目就是这么用的;一旦项目中发生了异常,就会进入使用了RestControllerAdvice注解类中使用了ExceptionHandler注解的方法,我们可以在这里处理全局异常,将异常信息输出到指定的位置。并对所有的错误信息进行归置,下面是示例代码:


@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    //自定义异常处理方法
    @ExceptionHandler(WinningException.class)
    public Response winningException(WinningException e){
        Response response = Response.getErrResponse(e);
        return response;
    }

    //其他异常返回给前端处理
    @ExceptionHandler(Exception.class)
    public Response otherException(Exception e){
        e.printStackTrace();
        log.error(e.getMessage());
        Response response = Response.getErrResponse(e);
        return response;
    }
}


解释下这段代码的部分内容,slf4j是日志注解,根据不同的错误类型,并根据每种错误类型编辑文案返回前端,这样其实我们就完成了全局异常信息的控制。RestControllerAdvice+ExceptionHandler这么用就对了。

2.2 InitBinder注解、ModelAttribute注解

这两个注解也是可以与RestControllerAdvice配合使用的,那这两个注解的作用是什么呢?

  • InitBinder注解 用于将前端传递的参数进行分别绑定
  • ModelAttribute注解 :获取InitBinder绑定的参数,对他进行属性绑定
@Controller  
@RequestMapping("/test")  
public class TestController {  
// 绑定变量名字和属性,参数封装进类  
    @InitBinder("user")  
    public void initBinderUser(WebDataBinder binder) {  
        binder.setFieldDefaultPrefix("user.");  
    }  
    // 绑定变量名字和属性,参数封装进类  
    @InitBinder("addr")  
    public void initBinderAddr(WebDataBinder binder) {  
        binder.setFieldDefaultPrefix("addr.");  
    }  
      
      
    @RequestMapping("/test")  
    @ResponseBody  
    public Map<String,Object> test(HttpServletRequest request,@ModelAttribute("user") User user,@ModelAttribute("addr") Addr addr){  
        Map<String,Object> map=new HashMap<String,Object>();  
        map.put("user", user);  
        map.put("addr", addr);  
        return map;  
    } 

若是不理解,推荐看下这人的博客说的很好:SpringMvc @InitBinder 表单多对象精准绑定接收

3 ControllerAdvice与RestContollerAdvice

这两个注解猛一看很相似,只是差了一个Rest。那他们有什么相似和区别呢?其实完全可以类比RestController与Controller的区别,RestControllerAdvice=ControllerAdvice+ResponseBody。这样就很清晰了ResponseBody的作用是将返回前端的参数转化成json格式,说白了就是以json数据与前端进行交互。所以ControllerAdvice与RestContollerAdvice的却别就是一个返回给前端的数据是json格式,一个返回的是对象,现在大部分项目前后端交互都是json格式,所以建议都是使用RestControllerAdvice与RestController等注解

4 GlobalExceptionHandler是如何被加载的

ExceptionHandlerExceptionResolver类中实现了InitializingBean接口表面是在启动时初始化ExceptionHandlerExceptionResolver的bean时加载的。

image.png

image.png initExceptionHandlerAdviceCache首先从容器中找到所有的带@ControllerAdvice注解的类(@RestControllerAdvice注解是@ControllerAdvice和@ResponseBody组合的注解)。可以看到这里找到了我们自定义的GlobalExceptionHandler。

image.png

然后遍历所有adviceBeans,将这些adviceBeans都转换为ExceptionHandlerMethodResolver,转换过程中扫描每个ControllerAdvice中的带@ExceptionHandler注解的方法,再取出这些ExceptionHandler所处理的Exception类型,可能会有多个,然后以exceptionType,为key,method为value放入mappedMethods这个map中进行缓存。

image.png

image.png

然后将adviceBean放入ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache这个map中进行缓存。最后exceptionHandlerAdviceCache中的数据如下

image.png