Spring 统一异常处理的方式

    最近帮公司面试的时候,问的最多的问题就是Spring统一异常处理的方式你知道哪几种?这本身并不是一个很难的问题,说实话,会一种方式在工作中完全就可以了。毕竟每种的方式其实都是八九不离十的。

   1:AOP处理

    因为现在Spring Boot的流行,所以很多人第一个想到的都是AOP。这里不做过多的介绍,之前的一篇博客中有说过关于AOP的一些运行机制  Spring AOP @After,@Around,@Before执行的顺序以及可能遇到的问题  。基本就是基于注解的方式,而后通过 @Around 中   catch Exception  或者 @AfterThrowing 等等。

    2:通过过滤器实现

    过滤器实现其实和AOP并没有差距太大,AOP是基于注解的方式,需要匹配到切点以后才可以对方法进行处理,而过滤器的话是基于URL而已,最本质的区别应该就在于此。对于异常的捕获方式,AOP是可以返回JSON格式的,而过滤器需要我们手动设定返回的格式,否则一般返回的格式都是 HTML   

void sendErrorResponse(HttpServletResponse resp, Exception e) throws IOException {
        if (!resp.isCommitted()) {
            logger.warn("process ApiException: {}", e.getMessage());
            resp.setStatus(400);
            resp.setContentType("application/json");
            PrintWriter pw = resp.getWriter();
            //设定返回格式
            JsonUtil.OBJECT_MAPPER.writeValue(pw, ResponseWrapper.fail(e));
            pw.flush();
        } else {
            logger.warn("Cannot send fail response for response is already committed.", e);
        }
    }

     3:基于注解 @ControllerAdvice

    这里的基于注解和AOP的基于注解的方式还是有略微的一点不同的地方的。    

    了解新东西最快的方式就是追踪源码了,通过追踪源码可以看到 Spring 在启动的时候会像 BeanFactory 中注册bean。

    ExceptionHandlerExceptionResolver 中会初始化所有的ExceptionHandler   

private void initExceptionHandlerAdviceCache {}

     在执行  findAnnotatedBeans 方法时,会获取当前的 @ControllerAdvice 的一些参数,其中最为重要的我认为应该是获取@Order注解的值了

private static int initOrderFromBeanType(@Nullable Class<?> beanType) {
		Integer order = null;
		if (beanType != null) {
			order = OrderUtils.getOrder(beanType);
		}
		return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
	}

从上述代码可以看到,如果在类上添加了@Order注解,那么我们会获取@Order相对应的值,否则的话就返回他的最低等级

int LOWEST_PRECEDENCE = 2147483647;

PS:  @Order 注解是值越小,越先执行;

 在同一个Java服务中,我们可以有多个@ControlerAdvice注解对应的类,@Order在此时有了他的作用,如果两个类中都处理接收了某一种异常的时候,那么会根据Order的加载顺序,谁先加载那么就用谁的异常接收处理;

// 获取到所有的异常处理类
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 根据Order 排序,继承了 OrderCompartor
		AnnotationAwareOrderComparator.sort(adviceBeans);


// 排序的方法重写在 OrderCompartor中
 private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderComparator.OrderSourceProvider sourceProvider) {
        boolean p1 = o1 instanceof PriorityOrdered;
        boolean p2 = o2 instanceof PriorityOrdered;
        if (p1 && !p2) {
            return -1;
        } else if (p2 && !p1) {
            return 1;
        } else {
            int i1 = this.getOrder(o1, sourceProvider);
            int i2 = this.getOrder(o2, sourceProvider);
            return Integer.compare(i1, i2);
        }
    }

   在接收到异常的时候,Spring也非常智能的说明了,Spring异常的处理方式和你所声明的位置无关,和是否最匹配有关。

   意思就是:我们知道Exception是所有异常的父类,如果我们抛出了一个NullPointerException,在Spring的ControllerAdvice中如果我们定义了 Handler Exception 以及 Handler NullPointerException,那么Spring会在哪一个Handler里面做处理呢?

   答案是会在NullPointerException里面处理   

 @ExceptionHandler(Exception.class)
    public ResponseWrapper handleException(Exception e) {
        return returnResult(e, e.getMessage(), ApiError.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(FeignException.class)
    public ResponseWrapper handleFeignException(Exception e) {
        return returnResult(e, e.getMessage(), ApiError.SYSTEM_MAINTAIN);
    }

在代码中,我们分别对Exception 和 FeignException 做了拦截处理,此时我们对服务抛出了一个FeignException异常,代码会在 ExceptionHandlerExceptionResolver 中的  getExceptionHandlerMethod  获取到异常方法以及所报的异常

@Nullable
	protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
			@Nullable HandlerMethod handlerMethod, Exception exception) {}

 PS:如果是第二次仍然是这个方法报这个异常的话,Spring会有自己的缓存机制,会从 exceptionHandlerCacbe中直接获取该方法的处理对象。   

 第一次报此异常时,Spring会遍历  exceptionHandlerAdviceCache,而这个Map就是我们刚才所说的,如果我们有多个 @ControllerAdvice的话,那么Spring 会将其都保存在这个LinkedHashMap之中,并根据Order排序。


		for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
			ControllerAdviceBean advice = entry.getKey();
			if (advice.isApplicableToBeanType(handlerType)) {
				ExceptionHandlerMethodResolver resolver = entry.getValue();
				Method method = resolver.resolveMethod(exception);
				if (method != null) {
					return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
				}
			}
		}

  遍历这个bean下的所有已经注册的Handler方法,往下深入发现,在以下代码的时候出现了一个match的数组。

@Nullable
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<>();
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
		if (!matches.isEmpty()) {
			matches.sort(new ExceptionDepthComparator(exceptionType));
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return null;
		}
	}

 这个match数组用来存储我们刚才所说的,如果该 bean 下注册的Handler方法能匹配上当前报错的方法,那么就会加到当前的 match 数组中。

而后看到,match根据一个排序规则进行排序了,这就是我们所说的内部最优排序规则

 public ExceptionDepthComparator(Class<? extends Throwable> exceptionType) {
        Assert.notNull(exceptionType, "Target exception type must not be null");
        this.targetException = exceptionType;
    }

    public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
        int depth1 = this.getDepth(o1, this.targetException, 0);
        int depth2 = this.getDepth(o2, this.targetException, 0);
        return depth1 - depth2;
    }
 private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
        if (exceptionToMatch.equals(declaredException)) {
            return depth;
        } else {
            return exceptionToMatch == Throwable.class ? 2147483647 : this.getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
        }
    }

  首先将目标 Exception 赋值给 targetException 。然后通过递归找到当前和他有关系的Exception和目标Exception的关系,并且返回其深度关系,每递归一次寻找那么深度就 +1;如果是Throwable,则深度最大,返回   2147483647;而后根据深度从小到大排序,再获取 matches.get(0) 即是最优Handler了。而当找到最优的以后,会直接返回结果,因此排在此@ControllerAdvice之后的就无法执行到,所以@Order注解的用处在此处就体现出来了。

总结: 

/**
 * ControllerAdvice 可以指定多个,如果指定多个会按照最后加载的那个才有效,因此如果有多个 ControllerAdvice,@Order 注解可以加在类上规定执行顺序
 * ControllerAdvice 指定多个的情况下,如果有 @ExceptionHandler 相同的,那么会根据 @Order排序,只使用最先加载的那个
 * 不需要指定方法级别顺序,指定了也无效,因为ExceptionHandler是根据最匹配原则。
 * 根据排序,如果是最匹配顺序,那么返回0,否则是按递归顺序寻找当前匹配的深度,如果是Throwable.class,那么是最不匹配原则,排名在最后,深度为 2147483647
 * 否则递归查找当前异常的父类,直到找到为止,按照Depth深度获取第一个异常类返回
 */

  相比之下,使用@ControllerAdvice相对于AOP更灵活简单,但是能掌控的代码才是好代码~

 

 研究不是很深入,如果有问题请指出,谢谢~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值