其他系列文章导航
文章目录
前言
最近,在开发过程中,我遇到一个不易察觉的小bug。这个bug并没有直接给出报错信息,使得排查问题的根源变得困难。我希望通过分享这个经验,帮助大家避免重蹈覆辙,以免浪费不必要的时间和精力。
为了避免类似的困境,我们应当时刻保持警惕,对开发过程中的每一个细节都进行严格的检查。同时,利用调试工具和日志输出等功能,可以帮助我们更快速地定位和解决问题。此外,定期进行代码审查和测试也是非常必要的,这有助于发现潜在的问题并及时解决。
一、BUG详情
1.1 报错信息
如下图所示:
java.lang.reflect.UndeclaredThrowableException: null at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:780) ~[spring-aop-5.3.27.jar:5.3.27] Caused by: exception.NoAuthorityException: 无权限访问
1.2 接口响应信息
预期是抛出无权限访问异常,但是没有被aop捕获,被上层UndeclaredThrowableException异常捕获。
编辑
1.3 全局异常处理器的定义
如下图所示:
@ExceptionHandler(Exception.class) public <T> R<T> handle(Exception exception) { if (exception instanceof BindException) { // Bind错误特殊处理 return R.wrap(() -> { ApiValidationUtil.checkBinding(((BindException) exception).getBindingResult()); return null; }); } return R.errorAndLog(exception); }
二、排查过程
找到最上面一层报错,发现错误在CglibAopProxy.class中。
附上源码逻辑:
@Override @Nullable public Object proceed() throws Throwable { try { return super.proceed(); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { if (ReflectionUtils.declaresException(getMethod(), ex.getClass()) || KotlinDetector.isKotlinType(getMethod().getDeclaringClass())) { // Propagate original exception if declared on the target method // (with callers expecting it). Always propagate it for Kotlin code // since checked exceptions do not have to be explicitly declared there. throw ex; } else { // Checked exception thrown in the interceptor but not declared on the // target method signature -> apply an UndeclaredThrowableException, // aligned with standard JDK dynamic proxy behavior. throw new UndeclaredThrowableException(ex); } } }
从源码可以看出咱们最后抛出的异常就是UndeclaredThrowableException异常,所以说if块里面的逻辑是false。
继续深挖ReflectionUtils.declaresException(getMethod(), ex.getClass())方法的逻辑。
附上declaresException方法源码:
public static boolean declaresException(Method method, Class<?> exceptionType) { Assert.notNull(method, "Method must not be null"); Class<?>[] declaredExceptions = method.getExceptionTypes(); for (Class<?> declaredException : declaredExceptions) { if (declaredException.isAssignableFrom(exceptionType)) { return true; } } return false; }
method就是方法体了,exceptionType就是异常类型了。
method.getExceptionTypes()从controller层读到异常类型存入declaredExceptions中,与传入的exceptionType进行判断。
declaredException.isAssignableFrom(exceptionType)的意思是declaredException是不是exceptionType的父类。只要满足捕获的异常是接口抛出异常的父类就行了。
因为原来的controller层接口是并没有声明异常。
如下所示:
//原先的接口 @Role(400) @Override public R<UserInfoVO> getUserInfo(String loginName) { Assert.notNull(loginName, "请求参数为空"); return sysUserInfoService.getUserInfo(loginName); }
所以declaredExceptions是空的,那当然返回的是false。
所以走了else的逻辑,向上抛出throw new UndeclaredThrowableException(ex)。
三、解决方案
在接口方法上声明错误类型(exceptionType)。
如下所示:
@Role(400) @Override public R<UserInfoVO> getUserInfo(String loginName) throws NoAuthorityException { Assert.notNull(loginName, "请求参数为空"); return sysUserInfoService.getUserInfo(loginName); }
这样的话Class<?>[] declaredExceptions = method.getExceptionTypes();可以读到NoAuthorityException 异常,并和拦截到的异常ex.getClass()得到也是NoAuthorityException异常做对比,满足isAssignableFrom方法,所以成功捕获。
由此可见,我们把ex.getClass(),也就是AOP里要捕获的异常设置为Exception也是可以满足需求的。
附一张成功响应图:
编辑
四、总结
在本次博客中,我们讨论了AOP跨模块捕获异常时,CGLIB拦截导致异常继续向上抛出的问题。通过分析问题原因和解决方案,我们了解到CGLIB拦截异常是由于代理对象与目标对象继承关系导致的问题。通过使用AspectJ的解决方案,我们可以避免该问题的发生,从而更好地实现AOP功能。
通过分析CGLIB拦截异常的原因和提出解决方案,我们更好地了解了AOP的实现方式和如何解决跨模块异常处理的问题。这对于在实际开发中更好地应用AOP技术具有重要的指导意义。