2023年11月29日发(作者:)

解决spring@ControllerAdvice处理异常⽆法正确匹配⾃定

义异常

⾸先说结论,使⽤@ControllerAdvice配合@ExceptionHandler处理全局controller的异常时,如果想要正确匹配⾃⼰的⾃定义

异常,需要在controller的⽅法上抛出相应的⾃定义异常,或者⾃定义异常继承RuntimeException类。

问题描述:

1、在使⽤@ControllerAdvice配合@ExceptionHandler处理全局异常时,⾃定义了⼀个AppExceptionextends

Exception),由于有些全局的参数需要统⼀验证,所以在所有controller的⽅法上加⼀层AOP校验,如果参数校验没通过也抛

AppException

2、在@ControllerAdvice标记的类上,主要有两个@ExceptionHandler,分别匹配

3、在测试时,由于全局AOP的参数校验没通过,抛出了AppException,但是发现这个AppException匹配

到了,⽽不是我们想要的匹配上。

分析过程:

⼀阶段

开始由于⼀直测试的两个不同的请求(⼀个通过swagger,⼀个通过游览器地址输⼊,两个请求⽐较相似,我以为是同⼀个请

求),⼀个⽅法上抛出了AppException,⼀个没有,然后发现这个问题时现时不现,因为⽆法稳定复现问题,我猜测可能是

AppException出了问题,所以我修改了AppException,将其⽗类改为了RuntimeException,然后发现问题解决了

⼆阶段

问题解决后,我⼜思考了下为啥会出现这种情况,根据java的异常体系来说,⽆论是继承Exception还是RuntimeException

都不应该会匹配到上去。

我再次跟踪了异常的执⾏过程,粗略的过了⼀遍,发现在下⾯这个位置出现了差别:

catch (InvocationTargetException ex) {

// Unwrap for HandlerExceptionResolvers ...

Throwable targetException = getException();

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,⽽错误的那个异常类型时

aredThrowableException,内部包着AppException

JDKjava doc是这么解释UndeclaredThrowableException的:如果代理实例的调⽤处理程序的 invoke ⽅法抛出⼀个经过检

查的异常(不可分配给 RuntimeException Error Throwable),且该异常不可分配给该⽅法的throws⼦局声明的任何异

常类,则由代理实例上的⽅法调⽤抛出此异常。

因为AppException继承于Exception,所以代理抛出的异常就是包着AppExceptionUndeclaredThrowableException,在

@ControllerAdvice匹配的时候⾃然就匹配不上了。

⽽当AppException继承于RuntimeException时,抛出的异常依旧是AppException,所以能够被匹配上。

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

Spring@ExceptionHandler@ControllerAdvice统⼀处理异常

之前敲代码的时候,避免不了各种try…catch,如果业务复杂⼀点,就会发现全都是try…catch

try{

..........

}catch(Exception1 e){

..........

}catch(Exception2 e){

...........

}catch(Exception3 e){

...........

}

这样其实代码既不简洁好看 ,我们敲着也烦, ⼀般我们可能想到⽤拦截器去处理, 但是既然现在Spring这么⽕,AOP⼤家也不陌

, 那么Spring⼀定为我们想好了这个解决办法.果然:

@ExceptionHandler

源码

//该注解作⽤对象为⽅法

@Target({})

//在运⾏时有效

@Retention(E)

@Documented

public @interface ExceptionHandler {

//value()可以指定异常类

Class[] value() default {};

}

@ControllerAdvice

源码

@Target({})

@Retention(E)

@Documented

//bean对象交给spring管理⽣成

@Component

public @interface ControllerAdvice {

@AliasFor("basePackages")

String[] value() default {};

@AliasFor("value")

String[] basePackages() default {};

Class[] basePackageClasses() default {};

Class[] assignableTypes() default {};

Class[] annotations() default {};

}

从名字上可以看出⼤体意思是控制器增强

所以结合上⾯我们可以知道,使⽤@ExceptionHandler,可以处理异常, 但是仅限于当前Controller中处理异常,

@ControllerAdvice可以配置basePackage下的所有controller. 所以结合两者使⽤,就可以处理全局的异常了.

⼀、代码

这⾥需要声明的是,这个统⼀异常处理类,也是基于ControllerAdvice,也就是控制层切⾯的,如果是过滤器抛出的异常,不

会被捕获

@ControllerAdvice注解下的类,⾥⾯的⽅法⽤@ExceptionHandler注解修饰的⽅法,会将对应的异常交给对应的⽅法处

理。

@ExceptionHandler({})

public Result handleException(IOExceptione) {

("[handleException] ", e);

return eDefaultError();

}

⽐如这个,就是捕获IO异常并处理。

废话不多说,代码:

package ion;

import ache;

import ;

import Util;

import 4j;

import Utils;

if (BASE_PARAM_ERR_IgnoreCase(errorCode)) {

innerErrMsg = "参数校验不通过:" + msg;

outerErrMsg = BASE_PARAM_ERR_MSG;

} else if (rError()) {

innerErrMsg = ernalMsg(errorCode);

outerErrMsg = (errorCode);

if (lank(msg)) {

innerErrMsg = innerErrMsg + "" + msg;

outerErrMsg = outerErrMsg + "" + msg;

}

} else {

innerErrMsg = msg;

outerErrMsg = msg;

}

("【错误码】:{},【错误码内部描述】:{},【错误码外部描述】:{}", errorCode, innerErrMsg, outerErrMsg);

return e(errorCode, outerErrMsg);

}

/**

* 缺少servlet请求参数抛出的异常

*

* @param e

* @return

*/

@ResponseStatus(_REQUEST)

@ExceptionHandler({})

public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {

("[handleMissingServletRequestParameterException] 参数错误: " + ameterName());

return e(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);

}

/**

* @param e

* @return

*/

@ResponseStatus(_REQUEST)

@ExceptionHandler({})

public Result handleHttpMessageNotReadableException(BindException e) {

BindingResult result = dingResult();

String message = getBindResultMessage(result);

("[handleHttpMessageNotReadableException] 参数绑定失败:" + message);

return e(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);

}

/**

* tion:validation-api 校验参数抛出的异常

*

* @param e

* @return

*/

@ResponseStatus(_REQUEST)

@ExceptionHandler({})

public Result handleServiceException(ConstraintViolationException e) {

Set> violations = straintViolations();

ConstraintViolation violation = or().next();

* @author zgd