一文讲清楚Spring Aop失效场景以及失效背后的原理

Aop是什么

Aop的中文名称是面向切面编程。Aop的核心思想是将分散在多个类中的公共逻辑抽离出来封装在一个新的类中(这个类被称为切面),我的理解就是Aop需要我们重点关注和实现的就是这个切面类,因此被成为面向切面编程(面向切面类编程)。 对于Aop的介绍以及更多细节这里就不做过多赘述,这也不是这篇文章的重点,现在我们要讨论的是Aop在何时会失效,以及失效的原因。

Aop失效的场景

  1. 方法被final,static,private修饰时,这些方法不能通过Aop实现逻辑增强
  2. 在同一个类中在一个需要被Aop进行逻辑增强的方法中调用另一个同样需要被Aop逻辑增强的方法 先看下面这段代码
java 复制代码
//自定义日志收集注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    /**
     * 方法名称
     * @return
     */
    String methodName() default "";
}

//日志切面类
@Aspect
@Component
@Slf4j
public class LogInterceptor {
    @Around("@annotation(myLog)")
    public void doInterceptor(ProceedingJoinPoint joinPoint, Log myLog) throws Throwable {
        // 方法执行前打印方法开始执行日志
        log.info("方法开始执行:{}", joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
        joinPoint.proceed();
        //方法执行结束后打印方法结束执行日志
        long endTime = System.currentTimeMillis();
        log.info("方法结束执行:{},耗时:{}ms", joinPoint.getSignature().getName(), endTime - startTime);
    }
}

//切点
@Service
public class TestService1 {
    @Log
    public void testLog1() {
        System.out.println("我是第一个方法");
        //调用另外一个需要被Aop逻辑增强的方法
        testLog2();
    }
    @Log
    public void testLog2() {
        System.out.println("我是第二个方法");
    }
}

调用testLog1()方法后,控制台输出结果

java 复制代码
    @Resource
    private TestService1 testService1;

    @Test
    public void testLog() {
        testService1.testLog1();
    }

testLog1()成功被Aop拦截到并进行了逻辑增强,但是testLog2为什么同样也加了@Log注解但是没有被拦截到呢?解释这个问题之前,需要先了解一下Aop的实现原理。

Aop原理

我们都知道使用了Aop后会生成一个代理对象,在代理对象中会对代码逻辑进行增强,之前看了很多文章,我自己对这个生成的代理类的理解是下面这样的:

java 复制代码
public class TestService1xxxx {
    public void testLog1() {
        log.info("方法开始执行:{}", joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
        System.out.println("我是第一个方法");
        testLog2();
        long endTime = System.currentTimeMillis();
        log.info("方法结束执行:{},耗时:{}ms", joinPoint.getSignature().getName(), endTime - startTime);
    }
    public void testLog2() {
        System.out.println("我是第二个方法");
    }
}

如果生成的代理类是上面这样的话,没有办法解释为什么在生成代理类的时候同样使用了@Log注解的testLog2没有被逻辑增强。那如果生成的代理类对testLog2进行了逻辑增强,像下面这样:

java 复制代码
public class TestService1xxxx {
    public void testLog1() {
        log.info("方法开始执行:{}", joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
        System.out.println("我是第一个方法");
        testLog2();
        long endTime = System.currentTimeMillis();
        log.info("方法结束执行:{},耗时:{}ms", joinPoint.getSignature().getName(), endTime - startTime);
    }
    public void testLog2() {
        log.info("方法开始执行:{}", joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
        System.out.println("我是第二个方法");
        log.info("方法开始执行:{}", joinPoint.getSignature().getName());
        long startTime = System.currentTimeMillis();
    }
}

那这就无法解释为什么我们上面的输出结果中,只有testLog1()被逻辑增强,而testLog2并没有。所以答案只有一个:上面这两种情况都不是。那Aop生成的代理类究竟是什么样的呢? 通过在启动类中加入下面这段代码,可以将代理类输出到磁盘中

java 复制代码
        //需要在SpringApplication.run(AiCodeMotherApplication.class, args);之前
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/cglib-proxy");
        SpringApplication.run(ApplicationMain.class, args);

运行项目,得到这样一个代理类:

java 复制代码
public class TestService1$$SpringCGLIB$$0 extends TestService1 implements SpringProxy, Advised, Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private MethodInterceptor CGLIB$CALLBACK_1;
    private NoOp CGLIB$CALLBACK_2;
    private Dispatcher CGLIB$CALLBACK_3;
    private Dispatcher CGLIB$CALLBACK_4;
    private MethodInterceptor CGLIB$CALLBACK_5;
    private MethodInterceptor CGLIB$CALLBACK_6;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$testLog2$0$Method;
    private static final MethodProxy CGLIB$testLog2$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$testLog1$1$Method;
    private static final MethodProxy CGLIB$testLog1$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    static void CGLIB$STATICHOOK15() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("service.test.TestService1$$SpringCGLIB$$0");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$2$Method = var10000[0];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[1];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[2];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[3];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        var10000 = ReflectUtils.findMethods(new String[]{"testLog2", "()V", "testLog1", "()V"}, (var1 = Class.forName("com.libo.aicodemother.service.test.TestService1")).getDeclaredMethods());
        CGLIB$testLog2$0$Method = var10000[0];
        CGLIB$testLog2$0$Proxy = MethodProxy.create(var1, var0, "()V", "testLog2", "CGLIB$testLog2$0");
        CGLIB$testLog1$1$Method = var10000[1];
        CGLIB$testLog1$1$Proxy = MethodProxy.create(var1, var0, "()V", "testLog1", "CGLIB$testLog1$1");
    }

    final void CGLIB$testLog2$0() {
        super.testLog2();
    }

    public final void testLog2() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            if (var10000 != null) {
                var10000.intercept(this, CGLIB$testLog2$0$Method, CGLIB$emptyArgs, CGLIB$testLog2$0$Proxy);
            } else {
                super.testLog2();
            }
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

    final void CGLIB$testLog1$1() {
        super.testLog1();
    }

    public final void testLog1() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            if (var10000 != null) {
                var10000.intercept(this, CGLIB$testLog1$1$Method, CGLIB$emptyArgs, CGLIB$testLog1$1$Proxy);
            } else {
                super.testLog1();
            }
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }
    ...
}

生成的代理类名称是TestService1$$SpringCGLIB$$0,这个类继承了TestService1

为什么被final,static,private修饰的方法无法被Aop拦截

被final,static,private修饰的方法是不能够被子类继承的,所以在代理类中无法重写这些方法,自然也就不能进行逻辑增强。

代理类代码分析

在代理类中,有这样一段代码:

java 复制代码
if (var10000 != null) {
     var10000.intercept(this, CGLIB$testLog1$1$Method, CGLIB$emptyArgs, CGLIB$testLog1$1$Proxy);
} else {
     super.testLog1();
}

这段代码的含义是,如果存在方法拦截器,则执行方法拦截器的逻辑(Aop切面中的增强逻辑),如果不存在则执行父类中的原方法。当我们使用Aop对方法进行逻辑增强时if的条件就为true就会执行方法拦截器逻辑增强的逻辑。当增强逻辑执行完之后,会调用父类中的原方法,通过super()调用 这就是为什么testLog2没有被逻辑增强的原因,因为通过代理对象调用testLog1时会执行Aop的增强逻辑,然后调用原始类中的testLog1方法,在原始类中调用testLog2只是一次普通调用,这里如果通过代理对象调用testLog2是能够实现testLog2的逻辑增强的。

相关推荐
奋斗的小方1 小时前
Java基础篇09(2):项目实战之基于swing的石头迷阵
java·开发语言
暗夜猎手-大魔王1 小时前
转载--Hermes Agent 08 | Agent 的自我进化:nudge、后台审查与轨迹数据
java·前端·人工智能
宸津-代码粉碎机1 小时前
Spring AI 企业级RAG实战|增量更新+文档去重+定时自动入库生产落地方案
java·大数据·人工智能·后端·python·spring
Raink老师1 小时前
【AI面试临阵磨枪-92】Skill 开发规范:命名、文档、测试、日志、监控、告警?
java·面试·log4j
weixin_408099672 小时前
2026 AI生成图片快速去水印的5种实测方法(附在线工具 + Python/Java/PHP API代码)
java·人工智能·python·api接口·ai去水印·石榴智能·自动去水印
风筝在晴天搁浅2 小时前
快手 CodeTop LeetCode 227.基本计算器Ⅱ
java·开发语言
JAVA面经实录9172 小时前
RabbitMQ全套学习知识手册
java·rabbitmq
0xDevNull2 小时前
Java实战面试题(一)
java·开发语言