【问题标题】:Do some action after a method call inside another method在另一个方法内部的方法调用之后执行一些操作
【发布时间】:2017-09-04 08:01:24
【问题描述】:

我想在调用非公共方法(bar)后执行一些特定操作。此方法在另一个方法 (foo) 中调用。请注意,“bar”和“foo”都是在第三方 jar 文件中定义的。

我尝试在使用 spring 的面向方面的编程中使用 @before 注释来做到这一点。 However, I could not do that

在调用 jar 文件中的特定函数后,谁能告诉我如何做特定的事情(调用特定函数)?

【问题讨论】:

  • 简单:在 foo() 方法中调用 bar() 之后,执行您的操作。只需修改 foo() 的代码即可。
  • 我编辑问题。 foo 和 bar 都定义在一个 jar 文件中,我想在调用 bar(私有方法)时执行特定的操作。

标签: java spring aop aspectj


【解决方案1】:

正如 Gervasio Amy 所建议的,您需要使用 AspectJ,而不是 Spring AOP。如果是Spring环境,可以use AspectJ within Spring代替Spring AOP,这个没问题。如果您还没有使用 Spring,AOP 不是开始使用它的理由,AspectJ 可以在没有 Spring 的简单 Java SE(或 EE)版本中工作。

你需要做的是:

  • 使用 AspectJ 编译器ajc 编译 Aspect 代码。 (您也可以用它编译整个应用程序,因为它也是 Java 编译器 javac 的替代品。)
  • 创建加载时编织配置aop.xml,以便让您的应用程序在类加载期间将方面代码动态编织到第 3 方库中。我让你来决定如何做到这一点,只需查看LTW documentation
  • 通过-javaagent:/path/to/aspectjweaver.jar 开关在命令行上使用 AspectJ 编织代理启动您的 JVM 或应用程序服务器。

现在你想要的外观是什么样的?让我们尝试一些变体并细化切入点以使其匹配。但首先让我们用一些示例 3rd 方类(FooBar)和一个小驱动程序应用程序(Application)为我们的实验做准备:

示例应用程序和第 3 方代码:

package my.thirdparty.application;

public class Foo {
    void blah() {
        zot();
    }

    void foo() {}

    void zot() {
        foo();
    }
}
package my.thirdparty.application;

public class Bar {
    Foo foo = new Foo();

    public void doSomething() {
        someMethod();
        bar();
        anotherMethod();
    }

    private void someMethod() {
        foo.blah();
        foo.foo();
        foo.zot();
    }

    private void bar() {
        foo.blah();
        // This is the only call we want to intercept, 'foo' called by 'bar'
        foo.foo();
        foo.zot();
        anotherMethod();
    }

    private void anotherMethod() {
        foo.blah();
        foo.foo();
        foo.zot();
    }
}
package de.scrum_master.app;

import my.thirdparty.application.Bar;

public class Application {
    public static void main(String[] args) {
        new Bar().doSomething();
    }
}

如您所见,Application.main 创建了一个Bar 对象并调用了一个公共方法Bar.doSomething。这个方法触发了一系列其他方法调用,其中一些最终以Foo.foo 被间接调用,但只有一个从Bar.barFoo.foo 的直接调用(这是我们感兴趣的根据你的问题)。

方面,第 1 部分:拦截对 Foo.foo 的所有调用

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
    pointcut allCalls() :
        call(* Foo.foo(..));

    Object around(Foo fooObject) : allCalls() && target(fooObject) {
        System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
        //new Exception("printing stack trace").printStackTrace(System.out);
        //System.out.println();
        return proceed(fooObject);
    }
}

控制台日志:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.someMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())

这是一个好的开始,因为现在我们已经可以拦截所有对Foo.foo 的调用。但是,如何将拦截限制在Bar.bar 的控制流 (cflow) 中进行的那些调用?

方面,第 2 部分:拦截由 Bar.bar 直接(内)发出的对 Foo.foo 的调用

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
    pointcut indirectCalls() :
        call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..)));

    Object around(Foo fooObject) : indirectCalls() && target(fooObject) {
        System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
        //new Exception("printing stack trace").printStackTrace(System.out);
        //System.out.println();
        return proceed(fooObject);
    }
}

控制台日志:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())

现在这看起来比以前好多了,我们将之前截获的 12 个调用的结果缩小到 6 个。但是为什么我们在结果列表中有像 Foo.zotBar.anotherMethod 这样的调用者,即使我们说我们想将控制流限制为Bar.bar?答案很简单:这两个方法也被Bar.bar 直接或间接调用,因此在控制流中。如果我们检查调用堆栈,我们会更清楚地看到这一点(只需取消注释代码中的两个日志语句):

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
    at my.thirdparty.application.Foo.zot(Foo.java:11)
    at my.thirdparty.application.Foo.blah(Foo.java:5)
    at my.thirdparty.application.Bar.bar(Bar.java:19)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
    at my.thirdparty.application.Bar.bar(Bar.java:21)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
    at my.thirdparty.application.Foo.zot(Foo.java:11)
    at my.thirdparty.application.Bar.bar(Bar.java:22)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
    at my.thirdparty.application.Foo.zot(Foo.java:11)
    at my.thirdparty.application.Foo.blah(Foo.java:5)
    at my.thirdparty.application.Bar.anotherMethod(Bar.java:27)
    at my.thirdparty.application.Bar.bar(Bar.java:23)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Bar.foo_aroundBody5$advice(Bar.java:22)
    at my.thirdparty.application.Bar.anotherMethod(Bar.java:28)
    at my.thirdparty.application.Bar.bar(Bar.java:23)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
    at my.thirdparty.application.Foo.zot(Foo.java:11)
    at my.thirdparty.application.Bar.anotherMethod(Bar.java:29)
    at my.thirdparty.application.Bar.bar(Bar.java:23)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

如果您检查 6 个调用堆栈,您会在每个调用堆栈中找到 Bar.bar。所以cflow 切入点已经完成了我们告诉它的事情。

我们还能变得更好吗?如何告诉方面不仅将被调用者(目标)对象限制为Foo,还将调用者(this)对象限制为Bar

方面,第 3 部分:拦截由Bar.bar 直接(内)发出的对Foo.foo 的调用,但绝对来自Bar 对象

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
    pointcut callsFromBar(Bar barObject) :
        call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..))) && this(barObject);

    Object around(Foo fooObject, Bar barObject) : callsFromBar(barObject) && target(fooObject) {
        System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
        new Exception("printing stack trace").printStackTrace(System.out);
        System.out.println();
        return proceed(fooObject, barObject);
    }
}

控制台日志:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
    at my.thirdparty.application.Bar.bar(Bar.java:21)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Bar.foo_aroundBody5$advice(Bar.java:22)
    at my.thirdparty.application.Bar.anotherMethod(Bar.java:28)
    at my.thirdparty.application.Bar.bar(Bar.java:23)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

我们变得越来越好:从 6 次拦截减少到 2 次。来自Bar.anotherMethod 的拦截仍然不需要,因为它只是由 Bar.bar 间接触发,我们的目标是只拦截直接呼叫。好的,那么让我们更精确一点:

方面,第 4 部分:拦截由 Bar.bar 直接发出的对 Foo.foo 的调用,不允许间接调用

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
    pointcut directCalls(Bar barObject) :
        call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..))) && this(barObject) &&
        if("bar".equals(thisEnclosingJoinPointStaticPart.getSignature().getName()));

    Object around(Foo fooObject, Bar barObject) : directCalls(barObject) && target(fooObject) {
        System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
        new Exception("printing stack trace").printStackTrace(System.out);
        System.out.println();
        return proceed(fooObject, barObject);
    }
}

控制台日志:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
    at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
    at my.thirdparty.application.Bar.bar(Bar.java:21)
    at my.thirdparty.application.Bar.doSomething(Bar.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

等等!这是我们最初想要的。让我们回顾一下我们刚刚为缩小切入点所做的工作:

  • call(* Foo.foo(..)) - 只调用Foo.foo
  • cflow(execution(* Bar.bar(..))) - 仅在控制流中执行Bar.bar
  • this(barObject) - 调用者必须是 Bar 对象
  • target(fooObject) - 被调用者必须是 Foo 对象
  • if("bar".equals(thisEnclosingJoinPointStaticPart.getSignature().getName())) - 动态运行时条件检查直接调用者的方法名称是否真的是 bar

我希望这可以解决您的问题并且不会太冗长。我想做教程式的,以便让您了解如何解决像这样的高级 AOP 问题。享受吧!

【讨论】:

    【解决方案2】:

    避免使用 Spring AOP。它不允许你这样做,因为 Spring 创建了一个代理来对 bean 进行切面化,因此,只有对“代理”的调用才会被切面化。这意味着,如果你有一个第三方类的 bean(我们称之为 fooBean),当你执行 fooBean.foo() 时,你实际上是通过具有方面逻辑的代理,但是一旦执行了 foo() 方法,那么内部调用,例如对 bar() 的调用将不再考虑代理,因此,那里不会有任何方面。

    Mybe 遇到更复杂的解决方案,因为使用纯 AspectJ 可能会对您有所帮助,因为它不是基于代理,它只是增强编译后的字节码

    【讨论】:

      猜你喜欢
      • 2014-03-21
      • 2016-12-27
      • 2018-10-30
      • 1970-01-01
      • 1970-01-01
      • 2020-10-11
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      相关资源
      最近更新 更多