在另一个方法中调用方法后执行一些操作

Do some action after a method call inside another method

我想在调用非 public 方法 (bar) 后执行一些特定操作。此方法在另一个方法 (foo) 中调用。请注意,"bar" 和 "foo" 均在第三方 jar 文件中定义。

我尝试使用 spring 在面向方面的编程中使用 @before 注释来做到这一点。

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

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

Mybe 运行 变成一个更复杂的解决方案,因为使用纯 AspectJ 可能会对你有所帮助,因为它不基于代理,它只是增强编译的字节码

正如 Gervasio Amy 所建议的,您需要使用 AspectJ,而不是 Spring AOP。如果你在Spring环境下,你可以use AspectJ within Spring代替SpringAOP,这个没问题。如果您还没有使用 Spring,AOP 不是开始使用它的理由,AspectJ 在没有 Spring.

的简单 Java SE(或 EE)版本中工作

您需要做的是:

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

现在你想要的方面是什么样的?让我们尝试一些变体并改进切入点以使其匹配。但首先让我们用一些示例第 3 方 classes(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 对象并调用了一个 public 方法 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 问题。享受吧!