Spring AOP对象内部方法间如何嵌套调用

发布时间:2021-08-30 09:37:36 作者:小新
来源:亿速云 阅读:465

这篇文章主要介绍了Spring AOP对象内部方法间如何嵌套调用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

之前面试官问了一个问题,大概意思就是一个类有两个成员方法 A 和 B,两者都加了事务处理注解,定义了事务传播级别为 REQUIRE_NEW,问 A 方法内部直接调用 B 方法时能否触发事务处理机制。

答案有点复杂,Spring 的事务处理其实是通过AOP实现的,而实现AOP的方法有好几种,对于通过 Jdk 和 cglib 实现的 aop 处理,上述问题的答案为否,对于通过AspectJ实现的,上述问题答案为是。

本文就结合具体例子来看一下

我们先定义一个接口

public interface AopActionInf {
    void doSomething_01();
    void doSomething_02();
}

以及此接口的一个实现类

public class AopActionImpl implements AopActionInf{
    public void doSomething_01() {
        System.out.println("AopActionImpl.doSomething_01()");
        //内部调用方法 doSomething_02
        this.doSomething_02();
    }
    public void doSomething_02() {
        System.out.println("AopActionImpl.doSomething_02()");
    }
}

增加AOP处理

public class ActionAspectXML {
    public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("进入环绕通知");
        Object object = pjp.proceed();//执行该方法
        System.out.println("退出方法");
        return object;
    }
}
<aop:aspectj-autoproxy/>
<bean id="actionImpl" class="com.maowei.learning.aop.AopActionImpl"/>
<bean id="actionAspectXML" class="com.maowei.learning.aop.ActionAspectXML"/>
<aop:config>
    <aop:aspect id = "aspectXML" ref="actionAspectXML">
        <aop:pointcut id="anyMethod" expression="execution(* com.maowei.learning.aop.AopActionImpl.*(..))"/>
        <aop:around method="aroundMethod" pointcut-ref="anyMethod"/>
    </aop:aspect>
</aop:config>

运行结果如下:

Spring AOP对象内部方法间如何嵌套调用

下图是断点分析在调用方法doSomething_02时的线程栈,很明显在调用doSomething_02时并没有对其进行AOP处理。

Spring AOP对象内部方法间如何嵌套调用

默认情况下,Spring AOP使用Jdk的动态代理机制实现,当然也可以通过如下配置更改为cglib实现,但是运行结果相同,此处不再赘述。

<aop:aspectj-autoproxy proxy-target-class="true"/>

那有没有办法能够触发AOP处理呢?答案是有的,考虑到AOP是通过动态生成目标对象的代理对象而实现的,那么只要在调用方法时改为调用代理对象的目标方法即可。

我们将调用 doSomething_02 的那行代码改成如下,并修改相应配置信息:

public void doSomething_01() {
    System.out.println("AopActionImpl.doSomething_01()");
    ((AopActionInf) AopContext.currentProxy()).doSomething_02();
}
<aop:aspectj-autoproxy expose-proxy="true"/>

先来看一下运行结果,

Spring AOP对象内部方法间如何嵌套调用

从运行结果可以看出,嵌套调用方法已经能够实现AOP处理了,同样我们看一下线程调用栈信息,显然 doSomething_02 方法被增强处理了(红框中内容)。

Spring AOP对象内部方法间如何嵌套调用

同一对象内的嵌套方法调用AOP失效原因分析

举一个同一对象内的嵌套方法调用拦截失效的例子

首先定义一个目标对象:

/**
 * @description: 目标对象与方法
 * @create: 2020-12-20 17:10
 */
public class TargetClassDefinition {
    public void method1(){
        method2();
        System.out.println("method1 执行了……");
    }
    public void method2(){
        System.out.println("method2 执行了……");
    }
}

在这个类定义中,method1()方法会调用同一对象上的method2()方法。

现在,我们使用Spring AOP拦截该类定义的method1()和method2()方法,比如一个简单的性能检测逻辑,定义如下Aspect:

/**
 * @description: 性能检测Aspect定义
 * @create: 2020-12-20 17:13
 */
@Aspect
public class AspectDefinition {
    @Pointcut("execution(public void *.method1())")
    public void method1(){}
    @Pointcut("execution(public void *.method2())")
    public void method2(){}
    @Pointcut("method1() || method2()")
    public void pointcutCombine(){}
    @Around("pointcutCombine()")
    public Object aroundAdviceDef(ProceedingJoinPoint pjp) throws Throwable{
        StopWatch stopWatch = new StopWatch();
        try{
            stopWatch.start();
            return pjp.proceed();
        }finally {
            stopWatch.stop();
            System.out.println("PT in method [" + pjp.getSignature().getName() + "]>>>>>>"+stopWatch.toString());
        }
    }
}

由AspectDefinition定义可知,我们的Around Advice会拦截pointcutCombine()所指定的JoinPoint,即method1()或method2()的执行。

接下来将AspectDefinition中定义的横切逻辑织入TargetClassDefinition并运行,其代码如下:

/**
 * @description: 启动方法
 * @create: 2020-12-20 17:23
 */
public class StartUpDefinition {
    public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
        weaver.setProxyTargetClass(true);
        weaver.addAspect(AspectDefinition.class);
        Object proxy = weaver.getProxy();
        ((TargetClassDefinition) proxy).method1();
        System.out.println("-------------------");
        ((TargetClassDefinition) proxy).method2();
    }
}

执行之后,得到如下结果:

method2 执行了……
method1 执行了……
PT in method [method1]>>>>>>StopWatch '': running time = 20855400 ns; [] took 20855400 ns = 100%
-------------------
method2 执行了……
PT in method [method2]>>>>>>StopWatch '': running time = 71200 ns; [] took 71200 ns = 100%

不难发现,从外部直接调用TargetClassDefinition的method2()方法的时候,因为该方法签名匹配AspectDefinition中的Around Advice所对应的Pointcut定义,所以Around Advice逻辑得以执行,也就是说AspectDefinition拦截method2()成功了。但是,当调用method1()时,只有method1()方法执行拦截成功,而method1()方法内部的method2()方法没有执行却没有被拦截。

原因分析

这种结果的出现,归根结底是Spring AOP的实现机制造成的。众所周知Spring AOP使用代理模式实现AOP,具体的横切逻辑会被添加到动态生成的代理对象中,只要调用的是目标对象的代理对象上的方法,通常就可以保证目标对象上的方法执行可以被拦截。就像TargetClassDefinition的method2()方法执行一样。

不过,代理模式的实现机制在处理方法调用的时序方面,会给使用这种机制实现的AOP产品造成一个遗憾,一般的代理对象方法与目标对象方法的调用时序如下所示:

proxy.method2(){
        记录方法调用开始时间;
        target.method2();
        记录方法调用结束时间;
        计算消耗的时间并记录到日志;
    }

在代理对象方法中,无论如何添加横切逻辑,不管添加多少横切逻辑,最终还是需要调用目标对象上的同一方法来执行最初所定义的方法逻辑。

如果目标对象中原始方法调用依赖于其他对象,我们可以为目标对象注入所需依赖对象的代理,并且可以保证想用的JoinPoint被拦截并织入横切逻辑。而一旦目标对象中的原始方法直接调用自身方法的时候,也就是说依赖于自身定义的其他方法时,就会出现如下图所示问题:

Spring AOP对象内部方法间如何嵌套调用

在代理对象的method1()方法执行经历了层层拦截器后,最终会将调用转向目标对象上的method1(),之后的调用流程全部都是在TargetClassDefinition中,当method1()调用method2()时,它调用的是TargetObject上的method2()而不是ProxyObject上的method2()。而针对method2()的横切逻辑,只织入到了ProxyObject上的method2()方法中。所以,在method1()中调用的method2()没有能够被拦截成功。

解决方案

当目标对象依赖于其他对象时,我们可以通过为目标对象注入依赖对象的代理对象,来解决相应的拦截问题。

当目标对象依赖于自身时,我们可以尝试将目标对象的代理对象公开给它,只要让目标对象调用自身代理对象上的相应方法,就可以解决内部调用的方法没有被拦截的问题。

Spring AOP提供了AopContext来公开当前目标对象的代理对象,我们只要在目标对象中使用AopContext.currentProxy()就可以取得当前目标对象所对应的代理对象。重构目标对象,如下所示:

import org.springframework.aop.framework.AopContext;
/**
 * @description: 目标对象与方法
 * @create: 2020-12-20 17:10
 */
public class TargetClassDefinition {
    public void method1(){
        ((TargetClassDefinition) AopContext.currentProxy()).method2();
//        method2();
        System.out.println("method1 执行了……");
    }
    public void method2(){
        System.out.println("method2 执行了……");
    }
}

要使AopContext.currentProxy()生效,需要在生成目标对象的代理对象时,将ProxyConfig或者它相应的子类的exposeProxy属性设置为true,如下所示:

/**
 * @description: 启动方法
 * @create: 2020-12-20 17:23
 */
public class StartUpDefinition {
    public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory(new TargetClassDefinition());
        weaver.setProxyTargetClass(true);
        weaver.setExposeProxy(true);
        weaver.addAspect(AspectDefinition.class);
        Object proxy = weaver.getProxy();
        ((TargetClassDefinition) proxy).method1();
        System.out.println("-------------------");
        ((TargetClassDefinition) proxy).method2();
    }
}
<!-- 在XML文件中的开启方式 -->
<aop:aspectj-autoproxy expose-proxy="true" />

再次执行代码,即可实现所需效果:

method2 执行了……
PT in method [method2]>>>>>>StopWatch '': running time = 180400 ns; [] took 180400 ns = 100%
method1 执行了……
PT in method [method1]>>>>>>StopWatch '': running time = 24027700 ns; [] took 24027700 ns = 100%
-------------------
method2 执行了……
PT in method [method2]>>>>>>StopWatch '': running time = 64200 ns; [] took 64200 ns = 100%

感谢你能够认真阅读完这篇文章,希望小编分享的“Spring AOP对象内部方法间如何嵌套调用”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

推荐阅读:
  1. Spring AOP之简单实践
  2. 分析动态代理给Spring事务埋下的坑

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring aop

上一篇:MySQL中的join语句算法及优化方法

下一篇:C#中ThreadPool之QueueUserWorkItem怎么用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》