全局异常处理-@ExceptionHandler、ExceptionHandlerMethodResolver

浅尝

  • 自定义全局异常处理

 @ControllerAdvice
public class GlobalExceptionHandler {
 private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

 /**
  * 处理自定义的业务异常
  * @param req
  * @param e
  * @return
  */
    @ExceptionHandler(value = BizException.class)  
    @ResponseBody  
 public  ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
     logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
     return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
    }

 说明:@ControllerAdvice 表示处理全局异常,@ResponseBody 表示返回json格式,如果是@RestControllerAdvice 会自动转换json,则不需要加@ResponseBody,

和restcontrolller 注解一个道理,@ExceptionHandler 定义处理异常类。

方法参数; 具体异常类型、ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等

  • ExceptionHandlerMethodResolver

// 初始化 这里找出所有的异常@ExceptionHandler,将异常处理类和方法放入到map中。
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
   for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
      for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
         addExceptionMapping(exceptionType, method);
      }
   }
}
//AnnotationUtils.findAnnotation(method, ExceptionHandler.class) 找出本类和父类的ExceptionHandler注解。
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
   List<Class<? extends Throwable>> result = new ArrayList<>();
   detectAnnotationExceptionMappings(method, result);
   if (result.isEmpty()) {
      for (Class<?> paramType : method.getParameterTypes()) {
         if (Throwable.class.isAssignableFrom(paramType)) {
            result.add((Class<? extends Throwable>) paramType);
         }
      }
   }
   if (result.isEmpty()) {
      throw new IllegalStateException("No exception types mapped to " + method);
   }
   return result;
}
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
   ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
   Assert.state(ann != null, "No ExceptionHandler annotation");
   result.addAll(Arrays.asList(ann.value()));
}
// 不可以不同的method处理同一个类型的异常
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
        Method oldMethod = this.mappedMethods.put(exceptionType, method);
        if (oldMethod != null && !oldMethod.equals(method)) {
            throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                    exceptionType + "]: {" + oldMethod + ", " + method + "}");
        }
    }
//如果指定的异常匹配上一个method方法处理,如果匹配到多个,使用ExceptionDepthComparator它来排序。若木有匹配的就返回null,
//1、先去exceptionLookupCache找,若匹配上了直接返回
// 2、再去mappedMethods这个缓存里找。很显然可能匹配上多个,那就用ExceptionDepthComparator排序匹配到一个最为合适的
// 3、匹配上后放进缓存`exceptionLookupCache`,所以下次进来就不需要再次匹配了,这就是缓存的效果
// ExceptionDepthComparator的基本理论上:精确匹配优先(按照深度比较)
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        Method method = this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            method = getMappedMethod(exceptionType);
            this.exceptionLookupCache.put(exceptionType, method);
        }
        return method;
    }

 

 

  • ExceptionHandlerExceptionResolver

HandlerExcepion标注的方法支持的参数,@SessionAttribute、@RequestAttribute  ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
   List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

   // Annotation-based argument resolution
   resolvers.add(new SessionAttributeMethodArgumentResolver());
   resolvers.add(new RequestAttributeMethodArgumentResolver());

   // Type-based argument resolution
   resolvers.add(new ServletRequestMethodArgumentResolver());
   resolvers.add(new ServletResponseMethodArgumentResolver());
   resolvers.add(new RedirectAttributesMethodArgumentResolver());
   resolvers.add(new ModelMethodProcessor());

   // Custom arguments
   if (getCustomArgumentResolvers() != null) {
      resolvers.addAll(getCustomArgumentResolvers());
   }

   return resolvers;
}

 

 

 

  • 找到指定Class类(可能是Controller本身,也可能是@ControllerAdvice)里面所有标注有@ExceptionHandler的方法们
  • 同一个Class内,不能出现同一个(注意理解同一个的含义)异常类型被多个Method处理的情况,否则抛出异常:Ambiguous @ExceptionHandler method mapped for ...
  •  相同异常类型处在不同的Class内的方法上是可以的,比如常见的一个在Controller内,一个在@ControllerAdvice内~
  • 提供缓存:mappedMethods:每种异常对应的处理方法(直接映射代码上书写的异常-方法映射)exceptionLookupCache:经过按照深度逻辑精确匹配上的Method方法
  • 既能处理本身的异常,也能够处理getCause()导致的异常
  • ExceptionDepthComparator的匹配逻辑是按照深度匹配。比如发生的是NullPointerException,但是声明的异常有Throwable和Exception,这是它会根据异常的最近继承关系找到继承深度最浅的那个异常,即Exception。
     
  • @ExceptionHandler的处理和执行是由本类完成的,同一个Class上的所有@ExceptionHandler方法对应着同一个ExceptionHandlerExceptionResolver,不同Class上的对应着不同的~
  • 标注有@ExceptionHandler的方法入参上可写:具体异常类型、ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等1. 注意:入参写具体异常类型时只能够写一个类型。(若有多种异常,请写公共父类,你再用instanceof来辨别,而不能直接写多个)返回ModelAndView/Model值/View/HttpEntity          /ModelAttribute/RequestResponseBody/@ResponseStatus等等
  • @ExceptionHandler只能标注在方法上。既能标注在Controller本类内的方法上(只对本类生效),也可配合@ControllerAdvice一起使用(对全局生效)
  • 对步骤4的两种情况,执行时的匹配顺序如下:优先匹配本类(本Controller),再匹配全局的。
  • 有必要再强调一句:@ExceptionHandler方式并不是只能返回JSON串,步骤4也说了,它返回一个ModelAndView也是ok的
     

优先级:

  1. @Controller + @ExceptionHandler优先级最高
  2. @ControllerAdvice + @ExceptionHandler次之
  3. HandlerExceptionResolver最后(一般是DefaultHandlerExceptionResolver

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值