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 实现它?
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 实现它?
为了
您需要使用 LTW(加载时编织)从 Spring AOP(基于代理,有很多限制,速度慢)切换到 AspectJ,如 Spring 手册中所述。
这是一个纯 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!
您还可以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 手册。
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),您可以使用cflow()
.