spring @ControllerAdvice处理异常无法正确匹配自定义异常问题

首先说结论,使用@ControllerAdvice配合@ExceptionHandler处理全局controller的异常时,如果想要正确匹配自己的自定义异常,需要在controller的方法上抛出相应的自定义异常,或者自定义异常继承RuntimeException类。

问题描述:
1、在使用@ControllerAdvice配合@ExceptionHandler处理全局异常时,自定义了一个AppException(extends Exception),由于有些全局的参数需要统一验证,所以在所有controller的方法上加一层AOP校验,如果参数校验没通过也抛出AppException
2、在@ControllerAdvice标记的类上,主要有两个@ExceptionHandler,分别匹配AppException.class和Throwable.class。
3、在测试时,由于全局AOP的参数校验没通过,抛出了AppException,但是发现这个AppException被Throwable.class匹配到了,而不是我们想要的AppException.class匹配上。

分析过程:

一阶段

开始由于一直测试的两个不同的请求(一个通过swagger,一个通过游览器地址输入,两个请求比较相似,我以为是同一个请求),一个方法上抛出了AppException,一个没有,然后发现这个问题时现时不现,因为无法稳定复现问题,我猜测可能是AppException出了问题,所以我修改了AppException,将其父类改为了RuntimeException,然后发现问题解决了

二阶段

问题解决后,我又思考了下为啥会出现这种情况,根据java的异常体系来说,无论是继承Exception还是RuntimeException,都不应该会匹配到Throwable.class上去。我再次跟踪了异常的执行过程,粗略的过了一遍,发现在下面这个位置出现了差别:

catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            }
            else if (targetException instanceof Error) {
                throw (Error) targetException;
            }
            else if (targetException instanceof Exception) {
                throw (Exception) targetException;
            }
            else {
                String text = getInvocationErrorMessage("Failed to invoke handler method", args);
                throw new IllegalStateException(text, targetException);
            }
        }

成功的走的是Exception,失败的走的是RuntimeException。
这时候到了@ControllerAdvice标记的类时就会出问题了,因为继承AppException是和RuntimeException是平级,所以如果走runtimeException这个判断条件抛出去的异常注定就不会被AppException匹配上。

这时候再仔细对比下异常类型,可以发现正确的那个异常类型时AppException,而错误的那个异常类型时java.lang.reflect.UndeclaredThrowableException,内部包着AppException。

JDK的java doc是这么解释UndeclaredThrowableException的:如果代理实例的调用处理程序的 invoke 方法抛出一个经过检查的异常(不可分配给 RuntimeException 或 Error 的 Throwable),且该异常不可分配给该方法的throws子局声明的任何异常类,则由代理实例上的方法调用抛出此异常。

因为AppException继承于Exception,所以代理抛出的异常就是包着AppException的UndeclaredThrowableException,在@ControllerAdvice匹配的时候自然就匹配不上了。
而当AppException继承于RuntimeException时,抛出的异常依旧是AppException,所以能够被匹配上。

结论

所以解决方法有两种:AppException继承RuntimeException或者Controller的方法抛出AppException异常。

你可能感兴趣的:(spring-boot)