AspectJ 基于自定义的方法注解来拦截方法

通常在使用 AspectJ 时都是基于识别方法的规则来进行方法拦截,例如切片里这样写


@Pointcut("execution(* *..StockService.getBaseInfo(..))")

它拦截到的是以 StockService 结尾的,方法名为 getBaseInfo,参数任意,返回值任意的方法。而我这里要说的一种方式是基于自定义注解来拦截方法的,此处的注解不是指 @Aspect, @Pointcut, 或 @Before 那一堆东西,而是指你可以自定义一个注解,如 @cc.unmi.testaspectj.MonitorMethod,被它所注解的方法即被拦截,像:

@cc.unmi.testaspectj.MonitorMethod
public void foo();

这可以给我们很大的自由度来快捷控制哪些方法需要被拦截,加个上面的注解 @MonitorMethod 即可,而不像从前那般要想像用什么规则去匹配某个方法,用 || 连接起来,同时还要防止影响到别的不期望被拦截的方法。

需要的代码并不多,四步,创建自定义注解类 MonitorMethod, 需被拦截的方法加上 @MonitorMethod,方面类,测试类。

1. MonitorMethod.java
 1package cc.unmi.testaspectj;
 2
 3import java.lang.annotation.ElementType;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8@Retention(RetentionPolicy.RUNTIME)
 9@Target(ElementType.METHOD)
10public @interface MonitorMethod {
11    String value() default "";
12}

2. MethodExecutionTime.java
 1package cc.unmi.testaspectj;
 2
 3import org.aspectj.lang.ProceedingJoinPoint;
 4import org.aspectj.lang.annotation.Around;
 5import org.aspectj.lang.annotation.Aspect;
 6import org.springframework.util.StopWatch;
 7
 8/**
 9 * @author Unmi
10 */
11
12@Aspect
13public class MethodExecutionTime {
14          
15    @Around("execution(* *.*(..)) && @annotation(cc.unmi.testaspectj.MonitorMethod)")
16    public Object profile(ProceedingJoinPoint  pjp) throws Throwable {
17        StopWatch sw = new StopWatch(getClass().getSimpleName());
18        try {
19            sw.start(pjp.getSignature().toShortString());
20            return pjp.proceed();
21        } finally {
22            sw.stop();
23            System.out.println(sw.prettyPrint());
24        }
25    }
26}

上面是简单的写法,也可以把 @Pointcut 和 @Around 分开来,如先声明

@Pointcut("execution(* *.*(..)) && @annotation(cc.unmi.testaspectj.MonitorMethod)")
public void methodsToBeProfiled(){}

把用

@Around("methodsToBeProfiled()")

注解到 profile() 方法

3. StockService.java
 1package cc.unmi.testaspectj;
 2
 3/**
 4 * @author Unmi
 5 */
 6public class StockService {
 7    
 8    @MonitorMethod
 9    public String getBaseInfo(String ticker){
10        try {
11            Thread.sleep(500);
12        } catch (InterruptedException e) {
13            e.printStackTrace();
14        }
15        return "";
16    }
17}

4. AspectJTestClient.java
 1package cc.unmi.testaspectj;
 2
 3/**
 4 * @author Unmi
 5 */
 6public class AspectJTestClient {
 7
 8    public static void main(String[] args) {
 9        
10        new StockService().getBaseInfo("MSFT");
11        
12        //new FundService().getBaseInfo("BBBIX");
13    }
14}

这样,因为 StockService 的 getBaseInfo 加上了 @MonitorMethod 注解,所以可被拦截到,假如有个类 FundService 的 getBaseInfo 方法未加上 @MonitorMethod 注解,将不被拦截到。

执行结果如下:
1StopWatch 'MethodExecutionTime': running time (millis) = 500
2-----------------------------------------
3ms     %     Task name
4-----------------------------------------
500500  100%  StockService.getBaseInfo(..)

如果你用 AJDT 的话,可以在 MethodExecutionTime.java 和 StockService.java 编辑器里看到箭头指示拦截到了什么方法,以及 Cross References View 中看到关联关系。


补充(2015-01-21)

因为写此文时是用的 AJDT 插件做的,所以是自动织入的,如果是命令行的话就要用到  ajc 或 aspectjtools.jar 来编译,可以直接起动 aspecttools.jar AspectJ Browser 来处理。这里以 Mac 平台命令行为例,假如项目目录结构如下:
 1├── lib
 2│   ├── aspectjrt.jar
 3│   ├── aspectjtools.jar
 4│   ├── aspectjweaver.jar
 5│   └── spring-core-2.5.6.jar
 6└── src
 7    └── cc
 8        └── unmi
 9            └── testaspectj
10            ├── AspectJTestClient.java
11            ├── MethodExecutionTime.java
12            ├── MonitorMethod.java
13            └── StockService.java

编译:

$ javac -classpath lib/aspectjtools.jar:lib/aspectjrt.jar:lib/spring-core-2.5.6.jar org.aspectj.tools.ajc.Main -d bin -source 1.7  src/cc/unmi/testaspectj/*

执行:

$java -classpath bin:lib/aspectjrt:lib/spring-core-2.5.6.jar:lib/aspectjweaver.jar cc.unmi.testaspectj.AspectJTestClient

这样就输出上面的结果:

StopWatch 'MethodExecutionTime': running time (millis) = 505
-----------------------------------------
ms     %     Task name
-----------------------------------------
00505  100%  StockService.getBaseInfo(..)


由于 Spring 2.0 开始支持 AspectJ,因此你可以把上面的方法应用到 Spring 中去。可参考:

1. Spring+AspectJ+ 简单方式来拦截方法,监测性能
2.用 AOP 来记录每个方法的执行时间(Spring 或直接 AspectJ),

参考:http://www.captaindebug.com/2011/09/using-springs-aspectj-support-and.html




好像在通过 Spring 使用 AspectJ 时私有方法不被拦截到,而单独用 AspectJ 不会有这样的局限。 永久链接 https://yanbin.blog/aspectj-baseon-annotation-method/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。