Spring AOP 对象内部方法间的嵌套调用方式

目录
  • Spring AOP 对象内部方法间的嵌套调用
    • 我们先定义一个接口
    • 以及此接口的一个实现类
    • 增加AOP处理
  • 同一对象内的嵌套方法调用AOP失效原因分析
    • 举一个同一对象内的嵌套方法调用拦截失效的例子
    • 原因分析
    • 解决方案

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>

运行结果如下:

下图是断点分析在调用方法doSomething_02时的线程栈,很明显在调用doSomething_02时并没有对其进行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"/>

先来看一下运行结果,

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

同一对象内的嵌套方法调用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被拦截并织入横切逻辑。而一旦目标对象中的原始方法直接调用自身方法的时候,也就是说依赖于自身定义的其他方法时,就会出现如下图所示问题:

在代理对象的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的具体API上了。因此,在开发中应该尽量避免“自调用”的情况。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 谈谈Spring AOP中@Aspect的高级用法示例

    前言 本文主要跟大家分享介绍了关于Spring AOP中@Aspect的高级用法,下面话不多说了,来随着小编一起看看详细的介绍吧. 1 切点复合运算 支持在切点定义中加入以下运算符进行复合运算: 运算符 说明 && 与运算. ! 非运算. || 或运算. 2 切点命名 一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 . 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它. public cl

  • spring aop之链式调用的实现

    概述 AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理.日志.缓存等等. Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持.本章我们不关注aop代理类的实现,我简单实现一个指定次序的链式调用. 实现链式调用的 MethodInterceptor定义拦截器链,Metho

  • spring aop的简单使用方法详解

    AOP:[动态代理] 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式: 1.导入aop模块:Spring AOP:(spring-aspects) 2.定义一个业务逻辑类(MathCalculator):在业务逻辑运行的时候将日志进行打印(方法之前.方法运行结束.方法出现异常,xxx) 3.定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行: 通知方法: 前置通知(@Before):logSta

  • Spring AOP注解失效的坑及JDK动态代理

    @Transactional @Async等注解不起作用 之前很多人在使用Spring中的@Transactional, @Async等注解时,都多少碰到过注解不起作用的情况. 为什么会出现这些情况呢?因为这些注解的功能实际上都是Spring AOP实现的,而其实现原理是通过代理实现的. JDK动态代理 以一个简单的例子理解一下JDK动态代理的基本原理: //目标类接口 public interface JDKProxyTestService { void run(); } //目标类 publ

  • Spring AOP 对象内部方法间的嵌套调用方式

    目录 Spring AOP 对象内部方法间的嵌套调用 我们先定义一个接口 以及此接口的一个实现类 增加AOP处理 同一对象内的嵌套方法调用AOP失效原因分析 举一个同一对象内的嵌套方法调用拦截失效的例子 原因分析 解决方案 Spring AOP 对象内部方法间的嵌套调用 前两天面试的时候,面试官问了一个问题,大概意思就是一个类有两个成员方法 A 和 B,两者都加了事务处理注解,定义了事务传播级别为 REQUIRE_NEW,问 A 方法内部直接调用 B 方法时能否触发事务处理机制. 答案有点复杂,

  • 解决spring AOP中自身方法调用无法应用代理的问题

    目录 spring AOP中自身方法调用无法应用代理 如下例 可以使用如下两种方式修改代码以应用事务 (1)在MyServiceImpl中声明一个MyService对象 (2)使用AopContext类 spring aop 内部方法调用事务不生效 方法1: 方法2: spring AOP中自身方法调用无法应用代理 如下例 public class MyServiceImpl implements MyService { public void do(){ //the transaction a

  • Spring AOP访问目标方法的参数操作示例

    本文实例讲述了Spring AOP访问目标方法的参数操作.分享给大家供大家参考,具体如下: 一 配置 <?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns

  • spring aop 拦截业务方法,实现权限控制示例

    难点:aop类是普通的java类,session是无法注入的,那么在有状态的系统中如何获取用户相关信息呢,session是必经之路啊,获取session就变的很重要.思索很久没有办法,后来在网上看到了解决办法. 思路是: i. SysContext  成员变量 request,session,response ii. Filter 目的是给 SysContext 中的成员赋值 iii.然后在AOP中使用这个SysContext的值 要用好,需要理解  ThreadLocal和  和Filter

  • spring data jpa 创建方法名进行简单查询方式

    目录 最常见的做法是 按照规范创建查询方法 支持的规范表达式 spring data jpa 可以通过在接口中按照规定语法创建一个方法进行查询,spring data jpa 基础接口中,如CrudRepository中findOne,save,delete等,那么我们自己怎么按照需要创建一个方法进行查询呢? 最常见的做法是 声明一个接口继承于CrudRepository 或者 PagingAndSortingRepository,JpaRepository,Repository public

  • Spring使用ThreadPoolTaskExecutor自定义线程池及异步调用方式

    目录 一.ThreadPoolTaskExecutor 1.将线程池用到的参数定义到配置文件中 2.Executors的工厂配置 2.1.配置详情 2.2.注解说明 2.3.线程池配置说明 2.4.线程池配置个人理解 二.异步调用线程 三.多线程使用场景 1.定时任务@Scheduled 2.程序一启动就异步执行多线程 3.定义一个http接口 4.测试类 四.总结 多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实

  • SpringBoot使用AOP,内部方法失效的解决方案

    目录 SpringBoot使用AOP,内部方法失效 AOP切面 现在有两个方法 写一个简单的动态代理的例子 SpringBoot使用AOP,内部方法失效 最近在使用AOP的时候,发现一个问题,普通的方法AOP就能够有用,而内部调用的方法AOP就会失效,下面重现下问题 AOP切面 @Aspect @Component public class AuthorityAspect { @Pointcut("execution(* authority.service.AuthorityService.ge

  • Aspectj与Spring AOP的对比分析

    1.简介 今天有多个可用的 AOP 库, 它们需要能够回答许多问题: 1.是否与用户现有的或新的应用程序兼容? 2.在哪里可以实现 AOP? 3.与自己的应用程序集成多快? 4.性能开销是多少? 在本文中, 我们将研究如何回答这些问题, 并介绍 Spring aop 和 AspectJ, 这是 Java 的两个最受欢迎的 aop 框架. 2.AOP概念 在开始之前, 让我们对术语和核心概念进行快速.高层次的审查: Aspect -- 一种标准代码/功能, 分散在应用程序中的多个位置, 通常与实际

  • JavaScript中Object基础内部方法图

    对于JavaScript对象的操作基本上都会调用底层的对象内部方法,我们可以看出在ES6标准中定了14种内部方法. 双 [[]] 代表内部方法,在一般的JS代码中不可见,你可以调用.删除或覆写(通过Proxy对象)普通方法,但是无法操作内部方法. 下面通过一个思维导图来展示这14种基础的内部方法 以上就是这张非常详细的用法图,基本语法都做了高亮显示,大家在学习的时候如果还有任何不明白的地方,可以在下方的留言区讨论,也可以参考我们之前对这个知识点的总结内容.

  • Spring AOP对嵌套方法不起作用的解决

    目录 Spring AOP对嵌套方法不起作用 要解决这个问题 Spring AOP.嵌套调用失效及解决 加入注解 获取当前代理的接口 需要嵌套调用的Service实现它 调用的时候改写代码 Spring AOP对嵌套方法不起作用 今天在调研系统操作记录日志时,好多教程都是借助于Spring AOP机制来实现.于是也采用这种方法来实现.在Service中的删除日志方法上注解自定义的切点,但是执行没有生效. 代码如下: //尝试删除溢出日志     public synchronized void

随机推荐