Spring+AspectJ+ 简单方式来拦截方法,监测性能

还是在很久以前,作过一篇 用 AOP 来记录每个方法的执行时间(Spring 或直接 AspectJ), 其中例示了三种方法来拦截方法,用以监测方法调用时间它们分别是:


1. Spring 2.0 用 AspectJ 实现 AOP
2. Spring 通用的方法拦截
3. 直接用 AspectJ 实现


在这里再次使用 <aop:aspect-autoproxy/> 再 @Aspect 注解的方式来写个新的例子。原理与前面基本一致,只是在类里用 @Aspect, @Pointcut, @Before, @After, @Around, @AfterReturning, @AfterThrowing 来写拦截类。

局限仍然是必须通过 Spring 的 BeanFactory 获得的实例才能被拦截到,除非是在 Eclipse 里安装 AJDT 或是使用 Maven-AspectJ Plugin 来编译工程。

好,我们来看完整的例子,下面列出所有的项目文件,这是一个 Maven 的项目,所以从 pom.xml 开始。

1. pom.xml
 1<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 3    <modelVersion>4.0.0</modelVersion>
 4    <groupId>cc.unmi</groupId>
 5    <artifactId>TestSpring</artifactId>
 6    <version>0.0.1-SNAPSHOT</version>
 7    <name>TestSpring</name>
 8    <dependencies>
 9        <dependency>
10            <groupId>org.springframework</groupId>
11            <artifactId>spring-aspects</artifactId>
12            <version>3.1.2.RELEASE</version>
13        </dependency>
14        <dependency>
15            <groupId>aspectj</groupId>
16            <artifactId>aspectjrt</artifactId>
17            <version>1.5.4</version>
18        </dependency>
19        <dependency>
20            <groupId>aspectj</groupId>
21            <artifactId>aspectjweaver</artifactId>
22            <version>1.5.4</version>
23        </dependency>
24        <dependency>
25            <groupId>cglib</groupId>
26            <artifactId>cglib</artifactId>
27            <version>2.2</version>
28        </dependency>
29    </dependencies>
30</project>

如果这是一个 Eclipse 中的 AJDT 项目,只要 spring-aspects 一个依赖即可。

2. applicatinContext.xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2
 3<beans xmlns="http://www.springframework.org/schema/beans"
 4       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5       xmlns:aop="http://www.springframework.org/schema/aop"
 6       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 7         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
 8
 9    <bean class="cc.unmi.testspringaspectj.MethodExecutionTime"/>
10    <aop:aspectj-autoproxy/>
11
12    <bean id="stockService" class="cc.unmi.testspringaspectj.StockService"/>
13    <bean id="fundService" class="cc.unmi.testspringaspectj.FundService"/>
14
15</beans>

spring 从 2.0 起就可支持 aop 标签。<aop:aspectj-autoproxy/> 的作用其实是声明了一个 AnnotationAwareAspectJAutoProxyCreator 自动代理创建器实例,它会根据 MethodExecutionTime 中声明的 @Pointcut 注解中的条件去代理符合条件的实例,这些被代理的实例必须在配置到 spring 中去。

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

这个类完全是 AspectJ 的用法范畴了。这个类必须用 @Aspect 标注,@Pointcut 注明拦截条件,支持逻辑操作,例如这里用 || 操作符配置了拦截多个方法。有关 AspectJ 的 Pointcut 语法请参考:http://www.eclipse.org/aspectj/doc/released/progguide/language.html

如果你的 Eclipse 安装了  AJDT 插件,并且是一个 AspectJ 项目,那么我们现在的这个例子可以完全不需要 spring,即可以不需要 pom.xml 文件,可以不用 applicationContext.xml,也不用在乎你的 StockService/FundService 如何得来的。

并且你在 Eclipse 中可以看到如下景象:



4. StockService.java 和 FundService

它们都有个 public String getBaseInfo(String ticker) 方法。

5. AspectJTestClient.java
 1package cc.unmi.testspringaspectj;
 2
 3import org.springframework.beans.factory.BeanFactory;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6/**
 7 * @author Unmi
 8 */
 9public class AspectJTestClient {
10
11    /**
12     * @param args
13     */
14    public static void main(String[] args) {
15        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
16        
17        StockService stockService = factory.getBean(StockService.class);
18        FundService fundService = factory.getBean(FundService.class);
19        
20        stockService.getBaseInfo("IBM");
21        fundService.getBaseInfo("BBBIX");
22        
23        //cannot be intercepted
24        new StockService().getBaseInfo("MSFT");
25    }
26}

这是一个测试类,验证了必须通过 Spring BeanFactory 得到的实例,调用方法时才能被拦截 。执行上面代码的输出如下:
 1StopWatch 'MethodExecutionTime': running time (millis) = 512
 2-----------------------------------------
 3ms     %     Task name
 4-----------------------------------------
 500512  100%  StockService.getBaseInfo(..)
 6
 7StopWatch 'MethodExecutionTime': running time (millis) = 316
 8-----------------------------------------
 9ms     %     Task name
10-----------------------------------------
1100316  100%  FundService.getBaseInfo(..)

可以看出通过 Spring BeanFactory 获得的实例被拦截了,直接初始化的实例不受影响。

但如果你的 Eclipse 安装了 AJDT 插件,并且你的项目还是一个 AspectJ 项目,那么不管你是怎么获得的 StockService/FundService 实例,在调用它们的方法时都会被 MethodExecutionTime 拦截到。并且此时在  pom.xml 中只需要保留 spring-aspects 一个依赖即可。 永久链接 https://yanbin.blog/spring-aspectj-intercept-method/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。