用 AOP 来记录每个方法的执行时间(Spring 或直接 AspectJ)
有时候我们要跟踪方法的执行时间,来观察系统的性能、时间分布。特别是要找出那些十分耗时的操作。如果是在每个方法中起始和结束位置记下时间相减,那是不太现实的,对代码的侵入性太过份,而且在产品环境中又得屏闭那部份代码。
幸好现在有了 AOP,通过配置方式再加上外部辅助代码就能达到我们的要求,正式上线时只需要简单改个配置项拆卸下来即可。
下面介绍三种方式来打印每个方法的执行时间,分别是:
1. Spring 2.0 用 AspectJ 实现 AOP
2. Spring 通用的方法拦截
3. 直接用 AspectJ 实现
1. Spring 2.0 用 AspectJ 实现 AOP
这个实例由五个文件构成,两个配置文件和三个类文件。需要在项目中引用 Spring 2.0 以上版本的相关包,还要日志包。
1) log4j.properties 放在 src 目录下
2) applicationContext.xml 放在 src 目录下
3) MethodTimeAdvice.java 记录时间的类
4) HelloService.java 被拦截的业务类
5) Main.java 主程序类
执行 Main 后输出的结果是:
Hello Unmi(1)
2008-01-18 13:41:25,593 [DEBUG] MethodTimeAdvice.invoke(34)
Takes:516 ms [com.unmi.service.HelloService.sayHello(int,String)]
如果不需要这个功能,只要在 applicationContext.xml 中把 id="methodTimeLog" 和 id="methodTimeAdvice" 配置项注释掉就行了。
2. Spring 通用的方法拦截
Spring 1.x 因为不能用 AspectJ 来对方法进行拦截,要用到 ProxyFactoryBean 使用 AOP,具体操作方法只需要更换以上例子中的 applicationContext.xml 文件就行,内容如下:
上面的配置方式需为每个应用方法执行时间记录的 Bean 在外层包一个 ProxyFactoryBean,原来的 Bean 设为一个 Target 实在时麻烦了。
下面用一种应用自动代理的配置方式,指定 BeanNameAutoProxyCreator 的 beanNames 匹配模式即可,如果写成 <value>*Service,*Manager</value>,逗号分隔开,以 Service 或 Manager 结层类的方法都被拦截,这样方便许多。
3. 直接用 AspectJ 实现
AspectJ 提供了一套语法来定义切面,Spring 2.0 开始引入了 AspectJ 的部分功能,但如果要用上 AspectJ 更多强大完善的功能,诸如实例构造时,属性被访问时,动态改变类定义,滋生新的属性、方法,更强基于流程的控制时,恐怕非得显式的使用 AspectJ。当然,首先你得去 http://www.eclipse.org/aspectj/ 下载到 AspectJ 或者 AspectJ 的 Eclipse 插件。本人强烈建议使用 AspectJ 的 Eclipse 插件,请下载相应 Eclipse 版本的 AspectJ 插件,Eclipse 3.3 要搭配 AJDT: The AspectJ Development Tools 1.5。不要插件的话就得命令行下 ajc 编译你的方面,不容易习惯的。
AJDT 安装成功后,你可以新建 AspectJ 项目,或者把前面项目转换成 AspectJ 项目。从项目的上下文菜单中可以看到 AspectJ Tools -> Convert to Aspectj Project。然后在包 com.unmi.util 中新建一个方面,包 com.unmi.util 的上下文菜单中 New -> Aspect,输入名字 MethodTimeAdviceRecipe,然后 Finish,这时就会在包 com.unmi.util 中产生一个文件 MethodTimeAdviceRecipe.aj,内容如下:
MethodTimeAdviceRecipe.aj (那么 MethodTimeAdvice.java 就派不上用场了,可删去)
再就是因为无需用到 Spring 了,不需要 IOC 了,相应的 Main.java 的内容如下:
Main.java (以前如何创建 HelloService,现在还是怎么做)
OK,现在就可以在 Eclipse 中开始运行 Main 类了,对 methodTimeAdviceRecipe.aj 的编译工作由插件 AJDT 自动帮你作了(实质是相匹配的类中插入了代码)。运行 Main 的结果如下:
Hello Unmi(1)
2008-01-21 13:48:16,171 [DEBUG] MethodTimeAdvice.sayHello_aroundBody1$advice(130)
Takes: 515 ms [void com.unmi.service.HelloService.sayHello(int, String)(16)]
用 AspectJ 可以很灵活的定义方面,局限就是对方面的改变须重新编译相关类,而非配置方式。
参考:一个用Spring AOP实现异常处理和记录程序执行时间的实例 永久链接 https://yanbin.blog/aop-spring-aspectj-method-invoke-time/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
幸好现在有了 AOP,通过配置方式再加上外部辅助代码就能达到我们的要求,正式上线时只需要简单改个配置项拆卸下来即可。
下面介绍三种方式来打印每个方法的执行时间,分别是:
1. Spring 2.0 用 AspectJ 实现 AOP
2. Spring 通用的方法拦截
3. 直接用 AspectJ 实现
1. Spring 2.0 用 AspectJ 实现 AOP
这个实例由五个文件构成,两个配置文件和三个类文件。需要在项目中引用 Spring 2.0 以上版本的相关包,还要日志包。
1) log4j.properties 放在 src 目录下
1log4j.rootLogger=DEBUG,stdout
2
3log4j.appender.stdout=org.apache.log4j.ConsoleAppender
4log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
5
6log4j.appender.stdout.layout.ConversionPattern=%d [%5p] %c{1}.%M(%L) %n%m%n2) applicationContext.xml 放在 src 目录下
1<beans xmlns="http://www.springframework.org/schema/beans"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:aop="http://www.springframework.org/schema/aop"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
5http://www.spridngframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
6
7 <aop:config>
8 <!-- Spring 2.0 可以用 AspectJ 的语法定义 Pointcut,这里拦截 service 包中的所有方法 -->
9 <aop:advisor id="methodTimeLog" advice-ref="methodTimeAdvice" pointcut="execution(* *..service..*(..))"/>
10 </aop:config>
11
12 <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice"/>
13 <bean id="helloService" class="com.unmi.service.HelloService"/>
14</beans>3) MethodTimeAdvice.java 记录时间的类
1package com.unmi.util;
2
3import org.aopalliance.intercept.MethodInterceptor;
4import org.aopalliance.intercept.MethodInvocation;
5import org.apache.commons.lang.StringUtils;
6import org.apache.commons.lang.time.StopWatch;
7import org.apache.commons.logging.Log;
8import org.apache.commons.logging.LogFactory;
9
10/**
11 * 记录方法的执行时间
12 * @author Unmi
13 */
14public class MethodTimeAdvice implements MethodInterceptor {
15 protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
16
17 /**
18 * 拦截要执行的目标方法
19 */
20 public Object invoke(MethodInvocation invocation) throws Throwable {
21 //用 commons-lang 提供的 StopWatch 计时,Spring 也提供了一个 StopWatch
22 StopWatch clock = new StopWatch();
23 clock.start(); //计时开始
24 Object result = invocation.proceed();
25 clock.stop(); //计时结束
26
27 //方法参数类型,转换成简单类型
28 Class[] params = invocation.getMethod().getParameterTypes();
29 String[] simpleParams = new String[params.length];
30 for (int i = 0; i < params.length; i++) {
31 simpleParams[i] = params[i].getSimpleName();
32 }
33
34 log.debug("Takes:" + clock.getTime() + " ms ["
35 + invocation.getThis().getClass().getName() + "."
36 + invocation.getMethod().getName() + "("+StringUtils.join(simpleParams,",")+")] ");
37 return result;
38 }
39}4) HelloService.java 被拦截的业务类
1package com.unmi.service;
2
3/**
4 * @author Unmi
5 */
6public class HelloService {
7 public void sayHello(int id,String name){
8 try {
9 Thread.sleep(512);
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13 System.out.println("Hello "+name+"("+id+")");
14 }
15}5) Main.java 主程序类
1package com.unmi;
2
3import org.springframework.beans.factory.BeanFactory;
4import org.springframework.context.support.ClassPathXmlApplicationContext;
5
6import com.unmi.service.HelloService;
7
8/**
9 * 测试主类
10 * @author Unmi
11 */
12public class Main {
13
14 /**
15 * @param args
16 */
17 public static void main(String[] args) {
18 BeanFactory factory =new ClassPathXmlApplicationContext("applicationContext.xml");
19 HelloService helloService = (HelloService)factory.getBean("helloService");
20 helloService.sayHello(1,"Unmi");
21 }
22}执行 Main 后输出的结果是:
Hello Unmi(1)
2008-01-18 13:41:25,593 [DEBUG] MethodTimeAdvice.invoke(34)
Takes:516 ms [com.unmi.service.HelloService.sayHello(int,String)]
如果不需要这个功能,只要在 applicationContext.xml 中把 id="methodTimeLog" 和 id="methodTimeAdvice" 配置项注释掉就行了。
2. Spring 通用的方法拦截
Spring 1.x 因为不能用 AspectJ 来对方法进行拦截,要用到 ProxyFactoryBean 使用 AOP,具体操作方法只需要更换以上例子中的 applicationContext.xml 文件就行,内容如下:
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
3 "http://www.springframework.org/dtd/spring-beans.dtd">
4<beans>
5
6 <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice"/>
7 <bean id="helloServiceTarget" class="com.unmi.service.HelloService"/>
8
9 <bean id="methodTimeAdvisor"
10 class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
11 <property name="advice">
12 <ref bean="methodTimeAdvice"/>
13 </property>
14 <!--对指定类的任何方法有效-->
15 <property name="patterns">
16 <value>.*.*</value>
17 </property>
18 </bean>
19
20 <bean id="helloService" class="org.springframework.aop.framework.ProxyFactoryBean">
21 <property name="interceptorNames">
22 <list>
23 <value>methodTimeAdvisor</value>
24 </list>
25 </property>
26 <property name="target">
27 <ref bean="helloServiceTarget"/>
28 </property>
29 </bean>
30
31</beans>上面的配置方式需为每个应用方法执行时间记录的 Bean 在外层包一个 ProxyFactoryBean,原来的 Bean 设为一个 Target 实在时麻烦了。
下面用一种应用自动代理的配置方式,指定 BeanNameAutoProxyCreator 的 beanNames 匹配模式即可,如果写成 <value>*Service,*Manager</value>,逗号分隔开,以 Service 或 Manager 结层类的方法都被拦截,这样方便许多。
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
3 "http://www.springframework.org/dtd/spring-beans.dtd">
4<beans>
5
6 <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice" />
7 <bean id="helloService" class="com.unmi.service.HelloService" />
8
9 <bean id="methodTimeAdvisor"
10 class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
11 <property name="advice">
12 <ref bean="methodTimeAdvice" />
13 </property>
14 <!--对指定类的任何方法有效-->
15 <property name="patterns">
16 <value>.*.*</value>
17 </property>
18 </bean>
19
20 <!-- 根据 Bean 的名字自动实现代理拦截 -->
21 <bean
22 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
23 <property name="interceptorNames">
24 <list>
25 <value>methodTimeAdvisor</value>
26 </list>
27 </property>
28 <property name="beanNames">
29 <list>
30 <!-- 添加到其中的 Bean 自动就被代理拦截了 -->
31 <value>*Service</value>
32 </list>
33 </property>
34 </bean>
35</beans>3. 直接用 AspectJ 实现
AspectJ 提供了一套语法来定义切面,Spring 2.0 开始引入了 AspectJ 的部分功能,但如果要用上 AspectJ 更多强大完善的功能,诸如实例构造时,属性被访问时,动态改变类定义,滋生新的属性、方法,更强基于流程的控制时,恐怕非得显式的使用 AspectJ。当然,首先你得去 http://www.eclipse.org/aspectj/ 下载到 AspectJ 或者 AspectJ 的 Eclipse 插件。本人强烈建议使用 AspectJ 的 Eclipse 插件,请下载相应 Eclipse 版本的 AspectJ 插件,Eclipse 3.3 要搭配 AJDT: The AspectJ Development Tools 1.5。不要插件的话就得命令行下 ajc 编译你的方面,不容易习惯的。
AJDT 安装成功后,你可以新建 AspectJ 项目,或者把前面项目转换成 AspectJ 项目。从项目的上下文菜单中可以看到 AspectJ Tools -> Convert to Aspectj Project。然后在包 com.unmi.util 中新建一个方面,包 com.unmi.util 的上下文菜单中 New -> Aspect,输入名字 MethodTimeAdviceRecipe,然后 Finish,这时就会在包 com.unmi.util 中产生一个文件 MethodTimeAdviceRecipe.aj,内容如下:
MethodTimeAdviceRecipe.aj (那么 MethodTimeAdvice.java 就派不上用场了,可删去)
1package com.unmi.util;
2import org.apache.commons.lang.time.StopWatch;
3import org.apache.commons.logging.Log;
4import org.apache.commons.logging.LogFactory;
5
6/**
7 * 记录方法的执行时间
8 * @author Unmi
9 */
10public aspect MethodTimeAdviceRecipe {
11
12 protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
13
14 //拦截所有以 Service 结尾类的方法
15 pointcut callServicePointCut() : call(* *..*Service.*(..))
16 && !within(MethodTimeAdviceRecipe +);
17
18 /**
19 * 在方连接点(业务类方法)周围执行的通知
20 */
21 Object around() : callServicePointCut(){
22 //用 commons-lang 提供的 StopWatch 计时,Spring 也提供了一个 StopWatch
23 StopWatch clock = new StopWatch();
24 clock.start(); //计时开始
25 Object result = proceed();
26 clock.stop(); //计时结束
27
28 //显示出方法原型及耗时
29 log.debug("Takes: " + clock.getTime() + " ms ["+thisJoinPoint.getSignature()+"("+
30 thisJoinPoint.getSourceLocation().getLine()+")]");
31
32 return result;
33 }
34}再就是因为无需用到 Spring 了,不需要 IOC 了,相应的 Main.java 的内容如下:
Main.java (以前如何创建 HelloService,现在还是怎么做)
1package com.unmi;
2
3import com.unmi.service.HelloService;
4
5/**
6 * 测试主类
7 * @author Unmi
8 */
9public class Main {
10
11 /**
12 * @param args
13 */
14 public static void main(String[] args) {
15 HelloService helloService = new HelloService();
16 helloService.sayHello(1,"Unmi");
17 }
18}OK,现在就可以在 Eclipse 中开始运行 Main 类了,对 methodTimeAdviceRecipe.aj 的编译工作由插件 AJDT 自动帮你作了(实质是相匹配的类中插入了代码)。运行 Main 的结果如下:
Hello Unmi(1)
2008-01-21 13:48:16,171 [DEBUG] MethodTimeAdvice.sayHello_aroundBody1$advice(130)
Takes: 515 ms [void com.unmi.service.HelloService.sayHello(int, String)(16)]
用 AspectJ 可以很灵活的定义方面,局限就是对方面的改变须重新编译相关类,而非配置方式。
参考:一个用Spring AOP实现异常处理和记录程序执行时间的实例 永久链接 https://yanbin.blog/aop-spring-aspectj-method-invoke-time/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。