如何使用Spring AOP或AspectJ拦截给定方法中的每个方法调用

时间:2018-03-07 19:17:35

标签: java aspectj spring-aop spring-aspects

 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实现它?

2 个答案:

答案 0 :(得分:2)

为了

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

如Spring手册中所述,您需要从Spring AOP(基于代理,很多限制,慢速)切换到AspectJ using LTW (load-time weaving)

以下是纯AspectJ(无Spring,Just Java SE)中的一个示例,您可以轻松地根据自己的需求进行调整:

示例界面

package de.scrum_master.app;

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

类实现接口incl。 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!

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

@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手册。

答案 1 :(得分:0)

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

您有三个选项

如果您在使用公共方法时遇到问题,那么第一个也是最简单的方法是将函数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
    // ...
}

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

致电方法

如果您需要知道某个特定方法是否调用了截取的函数,您可以使用Thread.currentThread().getStackTrace()作为选项1或2.如果使用aspectj(选项3),则可以使用方法拦截方法cflow()