Spring中类内部调用方法AOP失效的原因

AOP原理

我们都知道,面向切面编程(AOP)的作用是:非侵入的增强代码片段。
使用的是代理的思想。
spring中使用了两种代理方式

  1. JDK动态代理
  2. cglib代理

在spring中,如果需要代理的类实现了接口,那么就使用JDK动态代理。
反之,就使用cglib。
两者的具体区别在这里不做讨论,如果不清楚两者的原理和区别,可以先了解一下。

AOP失效

@Async注解为例。
当在方法上使用了这个注解,那么在调用此方法时,将会异步执行。这里就是spring使用AOP提供给我们的功能。
但是当代码如下所示时,在外部调用inDoSomeTing()时,方法doSomeThing()并不会以异步的方式执行。此时,AOP失效了

@Component
public class TestAop implements ITestAop {

    @Async
    @Override
    public void doSomeThing() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSomething");
    }

    @Override
    public void inDoSomeTing() {
        this.doSomeThing();
    }
}

思考

我百度了一下,没有讲的特别清楚的。
于是想打断点找点线索。
在这里插入图片描述
这里确实注入了正确的代理类,但增强代码确实是没有生效。

是不是这个问题根本就与Spring无关呢?

仅使用JDK动态代理尝试

排除spring的影响,直接通过javaSE实现一个代理逻辑。

先来一个接口

public interface ITest {
    void doSomething();

    void inDoSomething();
}

随便实现一下, 加一个内部调用

public class Test implements ITest {

    @Override
    public void doSomething() {
        System.out.println("Do something");
    }

    @Override
    public void inDoSomething() {
        this.doSomething();
    }
}

实现InvocationHandler

public class TestProxy<T> implements InvocationHandler {

    private T target;

    public TestProxy(T target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("doSomething")) {
            System.out.println("执行方法前记录日志");
            Object res = method.invoke(target, args);
            System.out.println("执行方法后记录日志");
            return res;
        } else {
            return method.invoke(target, args);
        }
    }
}

测试一下

public class DoProxy {
    public static void main(String[] args) {
        ITest proxyTest = (ITest) Proxy.newProxyInstance(ITest.class.getClassLoader(), Test.class.getInterfaces(), new TestProxy(new Test()));
        proxyTest.doSomething();
        proxyTest.inDoSomething();
    }
}

在这里插入图片描述
果然, 内部调用的方法同样没有被增强。这个问题不使用spring同样会出现。现在只需要搞清楚JDK动态代理的原理就可以了。

我将动态代理生成的类,反编译后保存了下来。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.tianqi.code.read.proxy.ITest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements ITest {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void inDoSomething() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.tianqi.code.read.proxy.ITest").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.tianqi.code.read.proxy.ITest").getMethod("inDoSomething");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到,生成的代理类继承了Proxy类,同时实现了需要代理的接口。
接口的实现逻辑,将具体逻辑委托给了 InvocationHandlerinvoke方法。
在这里插入图片描述
也就是说,在代理类中,所有方法都会运行invoke中的逻辑。
回过头来再看一眼InvocationHandler类的逻辑
在这里插入图片描述
可以看到,最终调用方法,还是在被代理对象中执行的。也就是说,代理对象并非将增强代码插入了原有的对象中,只是在调用原方法的前后做增强。方法inDoSomething并不是目标方法,自然就无法被增强了。

结论

非被代理方法在类内部调用被代理方法,此时被代理方法并不会被增强。而spring-aop底层又使用了代理,所以当然会失效了。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP 失效原因可能有很多,以下是一些可能的原因: 1. 未配置正确的切面表达式:切面表达式定义了应该在哪些方法上应用切面。如果切面表达式不正确,就无法正确地匹配到目标方法,并且切面将无法生效。 2. 目标方法没有被代理:Spring AOP 使用动态代理来实现切面,在运行时生成代理对象,并将切面应用于代理对象上的目标方法。如果目标方法没有被代理,切面将无法生效。 3. 目标方法被直接调用而不是通过代理调用:如果目标对象的方法被其他部分直接调用,而不是通过生成的代理对象调用,那么切面将无法生效。 4. 切面的顺序问题:在一个应用中可能存在多个切面,每个切面都可以定义相同或不同的切面表达式。如果切面的顺序不正确,可能会导致切面失效,例如一个切面将目标方法拦截之后,另一个切面再次拦截该方法。 5. 使用错误的切面类型:Spring AOP 支持多种类型的切面,如前置通知、后置通知、异常通知等。如果使用了错误的切面类型,或者切面类型不匹配目标方法,切面将无法生效。 6. 目标方法没有被正确地扫描到:Spring AOP 使用自动扫描来发现目标方法,并将切面应用于这些方法上。如果目标方法没有被正确地扫描到,切面将无法生效。 总结来说,Spring AOP 失效原因可能是由于切面表达式的问题、代理问题、调用方式问题、切面顺序问题、切面类型问题、以及扫描问题。为了确保切面生效,我们需要仔细检查配置,并理解Spring AOP的工作原理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值