AOP跨模块捕获异常遭CGLIB拦截而继续向上抛出异常

简介: 最近,在开发过程中,我遇到一个不易察觉的小bug。这个bug并没有直接给出报错信息,使得排查问题的根源变得困难。我希望通过分享这个经验,帮助大家避免重蹈覆辙,以免浪费不必要的时间和精力。为了避免类似的困境,我们应当时刻保持警惕,对开发过程中的每一个细节都进行严格的检查。同时,利用调试工具和日志输出等功能,可以帮助我们更快速地定位和解决问题。此外,定期进行代码审查和测试也是非常必要的,这有助于发现潜在的问题并及时解决。

其他系列文章导航

Java基础合集

数据结构与算法合集

设计模式合集

多线程合集

分布式合集

ES合集


文章目录

其他系列文章导航

文章目录

前言

一、BUG详情

1.1 报错信息

1.2 接口响应信息

1.3 全局异常处理器的定义

二、排查过程

三、解决方案

四、总结


前言

最近,在开发过程中,我遇到一个不易察觉的小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: 无权限访问

image.gif

1.2 接口响应信息

预期是抛出无权限访问异常,但是没有被aop捕获,被上层UndeclaredThrowableException异常捕获。

image.gif编辑

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);
    }

image.gif


二、排查过程

找到最上面一层报错,发现错误在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);
        }
      }
    }

image.gif

从源码可以看出咱们最后抛出的异常就是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;
  }

image.gif

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);
    }

image.gif

所以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);
    }

image.gif

这样的话Class<?>[] declaredExceptions = method.getExceptionTypes();可以读到NoAuthorityException 异常,并和拦截到的异常ex.getClass()得到也是NoAuthorityException异常做对比,满足isAssignableFrom方法,所以成功捕获。

由此可见,我们把ex.getClass(),也就是AOP里要捕获的异常设置为Exception也是可以满足需求的。

附一张成功响应图:

image.gif编辑


四、总结

在本次博客中,我们讨论了AOP跨模块捕获异常时,CGLIB拦截导致异常继续向上抛出的问题。通过分析问题原因和解决方案,我们了解到CGLIB拦截异常是由于代理对象与目标对象继承关系导致的问题。通过使用AspectJ的解决方案,我们可以避免该问题的发生,从而更好地实现AOP功能。

通过分析CGLIB拦截异常的原因和提出解决方案,我们更好地了解了AOP的实现方式和如何解决跨模块异常处理的问题。这对于在实际开发中更好地应用AOP技术具有重要的指导意义。

目录
相关文章
|
7月前
|
缓存 Java Sentinel
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
|
1月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
54 4
|
5月前
|
容器
springboot-自定义注解拦截ip aop和ioc
springboot-自定义注解拦截ip aop和ioc
|
7月前
|
Java 测试技术 开发者
【亮剑】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
220 1
|
7月前
|
设计模式 安全 Java
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
1960 1
|
缓存 Java Spring
Spring AOP中CGLIB代理对象增强通知执行原理
Spring AOP中CGLIB代理对象增强通知执行原理
89 0
|
Java Spring
手动实现aop使用的动态代理和cglib代理
spring的aop使用的是动态代理和cglib代理,在对象有实现接口的情况下使用动态代理,没有实现接口的情况下使用cglib代理。 cglib代理是继承目标对象来创建代理,所以目标对象不能使用final修饰。
|
Java Maven Spring
如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
110 0