【问题标题】:How to intercept each method call within given method using Spring AOP or AspectJ如何使用 Spring AOP 或 AspectJ 拦截给定方法中的每个方法调用
【发布时间】:2018-03-07 19:17:35
【问题描述】:
 class Test {

@override
public String a(){
b();
d();
}


private String b() {
c();
}

private String c(){
d();
}
private String d(){}

}

我想拦截从重写方法 A() 调用的 Test 类的每个方法,并想知道每个方法(如 b()、c())在分别处理某些业务逻辑时花费了多少时间。

如何使用 Spring AOP 或 Aspectj 实现它?

【问题讨论】:

  • 你试过什么?你目前的方面是什么样的?你遇到了什么问题?
  • @kriegaex 我尝试使用 Spring AOP(为给定包编写@Around)来实现这个目标,并且能够成功拦截方法 a() 但无法找出如何拦截和记录时间由代码重构的结果 b()、c() 和 d() 采用。我对 AspectJ 的想法很少,也不知道如何更好地做到这一点。请帮助我
  • 我发布了一个答案,但不太清楚的是,您是否还要检查该方法是否由运行时调用?在这种情况下,您可以拦截我的回答中提到的方法,然后使用Thread.currentThread().getStackTrace() 检查堆栈跟踪以查看 a() 是否是调用方方法。
  • José,如果需要的话,这不是去这里的方式。您可以将cflow() 和AspectJ 与LTW 一起使用。无论如何,您已经建议了后者。但我们在猜测。 AspectJ 可以解决所有这些技术问题,不仅是自调用,还可以拦截私有方法,而这两者都是 Spring AOP 所不能做到的。 Spring AOP 也不知道cflow()。方法首先是否应该是私有的问题是另一个问题。我们不能说他们是否有假名,如a()b() 等。@Crazy-Coder:你试过 AspectJ 教程吗?
  • 这与你的工作有关不是借口。就像我在回答中编造了一个 MCVE 一样,您可以编造一个完全反映您的问题的内容,而无需透露任何工作细节。您让我们猜测,没有明确说明问题出在哪里,而是让我们完成您的工作,即为您编写可编译和可执行的示例代码,而不仅仅是修复您的。因此,请接受并投票赞成一个答案。如果重构为多个 bean 并使所有方法公开或包范围是一个选项,请选择 José's,否则选择我的。

标签: java aspectj spring-aop spring-aspects


【解决方案1】:

为了

  • 织入私有方法,
  • 在一个类中处理自调用,
  • 动态确定控制流并将拦截限制为仅由您的接口方法直接或间接调用的方法

您需要按照 Spring 手册中的说明从 Spring AOP(基于代理,限制很多,速度慢)切换到 AspectJ using LTW (load-time weaving)

这是一个纯 AspectJ(没有 Spring,只有 Java SE)的示例,您可以轻松地适应您的需求:

示例界面

package de.scrum_master.app;

public interface TextTransformer {
  String transform(String text);
}

类实现接口,包括。 main方法:

如您所见,我制作了一个像您这样的示例,并且还让这些方法花费时间以便稍后在方面进行测量:

package de.scrum_master.app;

public class Application implements TextTransformer {
  @Override
  public String transform(String text) {
    String geekSpelling;
    try {
      geekSpelling = toGeekSpelling(text);
      return toUpperCase(geekSpelling);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }

  }

  private String toGeekSpelling(String text) throws InterruptedException {
    Thread.sleep(100);
    return replaceVovels(text).replaceAll("[lL]", "1");
  }

  private String replaceVovels(String text) throws InterruptedException {
    Thread.sleep(75);
    return text.replaceAll("[oO]", "0").replaceAll("[eE]", "Ɛ");
  }

  private String toUpperCase(String text) throws InterruptedException {
    Thread.sleep(50);
    return text.toUpperCase();
  }

  public static void main(String[] args) throws InterruptedException {
    System.out.println(new Application().transform("Hello world!"));
  }
}

方面:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import static java.lang.System.currentTimeMillis;

@Aspect
public class TimingAspect {
  @Around("execution(* *(..)) && cflow(execution(* de.scrum_master.app.TextTransformer.*(..)))")
  public Object measureExecutionTime(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    long startTime = currentTimeMillis();
    Object result = thisJoinPoint.proceed();
    System.out.println(thisJoinPoint + " -> " + (currentTimeMillis() - startTime) + " ms");
    return result;
  }
}

控制台日志:

execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 75 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 189 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 63 ms
execution(String de.scrum_master.app.Application.transform(String)) -> 252 ms
HƐ110 W0R1D!

您也可以通过将切入点从cflow() 更改为cflowbelow() 来排除transform(..) 方法:

@Around("execution(* *(..)) && cflowbelow(execution(* de.scrum_master.app.TextTransformer.*(..)))")

那么控制台日志就是:

execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 77 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 179 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 62 ms
HƐ110 W0R1D!

顺便说一句,请务必阅读 AspectJ 和/或 Spring AOP 手册。

【讨论】:

  • 非常感谢先生为我指路,是的,这就是我想知道的。我将按照建议的方式研究和实施这个概念
  • 不客气。但无论经验水平如何,我们都是开发人员,因此也是同行。所以请不要叫我或这里的其他人“先生”。 :-)
  • @kriegaex 在使用 cflow 时遇到异常。请帮助 org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException: Pointcut expression 'execution(* (..)) && cflow(execution( com.Org.pts.api.service.impl.CreateServiceImpl.*(..) ))' 在 org.aspectj.weaver.tools.PointcutParser.validateAgainstSupportedPrimitives(PointcutParser.java:425) [aspectjweaver-1.8.10.jar:1.8.1 包含不受支持的切入点原语“cflow”
  • 请再看一遍我的回答,尤其是前言。我明确说过你需要从 Spring AOP 切换到 AspectJ,甚至添加了一个指向 Spring 手册的链接,描述了它需要如何配置。我真的很感激没有浪费我的时间把它写得那么详细。
  • @StefanRendevski,我很乐意回答你的新问题。请像我在此处的回答中所做的那样包含MCVE,然后准确解释您想要实现的目标。然后我可以写一个答案而不是评论。首先你让你的代码说话,然后我会用我的回答。在 cmets 内部用松散相关的后续问题劫持旧问题对你我来说都不是很方便。
【解决方案2】:

Spring AOP 是通过代理应用的,当你从外部调用 bean 的方法时,使用代理并且可以拦截方法,但是当你从类内部调用方法时,不使用代理,并且类直接使用。


您有三个选择


如果使用公共方法没有问题,第一个也是简单的方法是将函数 b()c()d() 移动到另一个 bean。这样每次调用这个方法都会被拦截。

public class Test {
    public String a() { ... }
}

public class Test2 {
    public String b() { ... }
    public String c() { ... }
    public String d() { ... }
}

如果您想将所有内容保存在同一个文件中,也可以将其用作内部静态类。

public class Test {
    public String a() { ... }
    public static class Test2 {
        public String b() { ... }
        public String c() { ... }
        public String d() { ... }
    }
}

您应该在 Test 的构造函数中自动装配 Test2。

public class Test {
    private final Test2 test2;    
    @Autowired public Test(final Test2 test2) {
        this.test2 = test2;
    }
    public String a() { 
        test2.b();
        test2.c();
        test2.d();
    }
}

最后创建 around 方法。

@Around(value = "execution(* package.of.the.class.Test.*(..))")
public Object aroundA(ProceedingJoinPoint pjp) throws Throwable { ... }

@Around(value = "execution(* package.of.the.class.Test2.*(..))")
public Object aroundBCD(ProceedingJoinPoint pjp) throws Throwable { 
    long start = System.currentTimeMillis();
    Object output = pjp.proceed();
    long elapsedTime = System.currentTimeMillis() - start;
    // perform side efects with elapsed time e.g. print, store...
    return output;
}

或者类似的东西

@Around(value = "execution(* package.of.the.class.Test.*(..)) || " +
                "execution(* package.of.the.class.Test2.*(..))")
public Object aroundABCD(ProceedingJoinPoint pjp) throws Throwable { ... }

第二种选择是使用CGLIB bean,封装私有方法和自注入。

您只使用范围注释声明一个 CGLIB bean

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Bean public Test test() {
    return new Test();
}

自注入和封装私有方法如下

public class Test {
    @Autowired private Test test;
    // ...
    public String a() {
        test.b(); // call through proxy (it is intercepted)
    }
    String b() { ... } // package private method
    // ...
}

第三种解决方案是使用 LWT Load-Time weaving,即使用 aspectj 而不是 spring aop。这允许即使在同一个类中也可以拦截方法调用。你可以使用spring官方文档来实现它,但是你必须使用java代理来运行。


调用方法

如果你想知道一个特定的方法是否调用了被拦截的函数,你可以使用Thread.currentThread().getStackTrace()作为选项1或2。如果你使用aspectj(选项3),你可以使用@987654333拦截方法@。

【讨论】:

    猜你喜欢
    • 2019-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多