有時候我們要跟蹤方法的執(zhí)行時間,來觀察系統(tǒng)的性能、時間分布。特別是要找出那些十分耗時的操作。如果是在每個方法中起始和結(jié)束位置記下時間相減,那是不太現(xiàn)實的,對代碼的侵入性太過份,而且在產(chǎn)品環(huán)境中又得屏閉那部份代碼。
幸好現(xiàn)在有了 AOP,通過配置方式再加上外部輔助代碼就能達到我們的要求,正式上線時只需要簡單改個配置項拆卸下來即可。
下面介紹三種方式來打印每個方法的執(zhí)行時間,分別是:
1. Spring 2.0 用 AspectJ 實現(xiàn) AOP
2. Spring 通用的方法攔截
3. 直接用 AspectJ 實現(xiàn)
1. Spring 2.0 用 AspectJ 實現(xiàn) AOP
這個實例由五個文件構(gòu)成,兩個配置文件和三個類文件。需要在項目中引用 Spring 2.0 以上版本的相關(guān)包,還要日志包。
1) log4j.properties 放在 src 目錄下
- log4j.rootLogger=DEBUG,stdout
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern=%d [%5p] %c{1}.%M(%L) %n%m%n
2) applicationContext.xml 放在 src 目錄下
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.spridngframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
- <aop:config>
- <!-- Spring 2.0 可以用 AspectJ 的語法定義 Pointcut,這里攔截 service 包中的所有方法 -->
- <aop:advisor id="methodTimeLog" advice-ref="methodTimeAdvice" pointcut="execution(* *..service..*(..))"/>
- </aop:config>
- <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice"/>
- <bean id="helloService" class="com.unmi.service.HelloService"/>
- </beans>
3) MethodTimeAdvice.java 記錄時間的類
- package com.unmi.util;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.aopalliance.intercept.MethodInvocation;
- import org.apache.commons.lang.StringUtils;
- import org.apache.commons.lang.time.StopWatch;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- /**
- * 記錄方法的執(zhí)行時間
- * @author Unmi
- */
- public class MethodTimeAdvice implements MethodInterceptor {
- protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
- /**
- * 攔截要執(zhí)行的目標方法
- */
- public Object invoke(MethodInvocation invocation) throws Throwable {
- //用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
- StopWatch clock = new StopWatch();
- clock.start(); //計時開始
- Object result = invocation.proceed();
- clock.stop(); //計時結(jié)束
- //方法參數(shù)類型,轉(zhuǎn)換成簡單類型
- Class[] params = invocation.getMethod().getParameterTypes();
- String[] simpleParams = new String[params.length];
- for (int i = 0; i < params.length; i++) {
- simpleParams[i] = params[i].getSimpleName();
- }
- log.debug("Takes:" + clock.getTime() + " ms ["
- + invocation.getThis().getClass().getName() + "."
- + invocation.getMethod().getName() + "("+StringUtils.join(simpleParams,",")+")] ");
- return result;
- }
- }
4) HelloService.java 被攔截的業(yè)務(wù)類
- package com.unmi.service;
- /**
- * @author Unmi
- */
- public class HelloService {
- public void sayHello(int id,String name){
- try {
- Thread.sleep(512);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Hello "+name+"("+id+")");
- }
- }
5) Main.java 主程序類
- package com.unmi;
- import org.springframework.beans.factory.BeanFactory;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import com.unmi.service.HelloService;
- /**
- * 測試主類
- * @author Unmi
- */
- public class Main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- BeanFactory factory =new ClassPathXmlApplicationContext("applicationContext.xml");
- HelloService helloService = (HelloService)factory.getBean("helloService");
- helloService.sayHello(1,"Unmi");
- }
- }
執(zhí)行 Main 后輸出的結(jié)果是:
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 文件就行,內(nèi)容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
- "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice"/>
- <bean id="helloServiceTarget" class="com.unmi.service.HelloService"/>
- <bean id="methodTimeAdvisor"
- class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <property name="advice">
- <ref bean="methodTimeAdvice"/>
- </property>
- <!--對指定類的任何方法有效-->
- <property name="patterns">
- <value>.*.*</value>
- </property>
- </bean>
- <bean id="helloService" class="org.springframework.aop.framework.ProxyFactoryBean">
- <property name="interceptorNames">
- <list>
- <value>methodTimeAdvisor</value>
- </list>
- </property>
- <property name="target">
- <ref bean="helloServiceTarget"/>
- </property>
- </bean>
- </beans>
上面的配置方式需為每個應用方法執(zhí)行時間記錄的 Bean 在外層包一個 ProxyFactoryBean,原來的 Bean 設(shè)為一個 Target 實在時麻煩了。
下面用一種應用自動代理的配置方式,指定 BeanNameAutoProxyCreator 的 beanNames 匹配模式即可,如果寫成 <value>*Service,*Manager</value>,逗號分隔開,以 Service 或 Manager 結(jié)層類的方法都被攔截,這樣方便許多。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
- "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="methodTimeAdvice" class="com.unmi.util.MethodTimeAdvice" />
- <bean id="helloService" class="com.unmi.service.HelloService" />
- <bean id="methodTimeAdvisor"
- class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- <property name="advice">
- <ref bean="methodTimeAdvice" />
- </property>
- <!--對指定類的任何方法有效-->
- <property name="patterns">
- <value>.*.*</value>
- </property>
- </bean>
- <!-- 根據(jù) Bean 的名字自動實現(xiàn)代理攔截 -->
- <bean
- class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="interceptorNames">
- <list>
- <value>methodTimeAdvisor</value>
- </list>
- </property>
- <property name="beanNames">
- <list>
- <!-- 添加到其中的 Bean 自動就被代理攔截了 -->
- <value>*Service</value>
- </list>
- </property>
- </bean>
- </beans>
3. 直接用 AspectJ 實現(xiàn)
AspectJ 提供了一套語法來定義切面,Spring 2.0 開始引入了 AspectJ 的部分功能,但如果要用上 AspectJ 更多強大完善的功能,諸如實例構(gòu)造時,屬性被訪問時,動態(tài)改變類定義,滋生新的屬性、方法,更強基于流程的控制時,恐怕非得顯式的使用 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 項目,或者把前面項目轉(zhuǎn)換成 AspectJ 項目。從項目的上下文菜單中可以看到 AspectJ Tools -> Convert to Aspectj Project。然后在包 com.unmi.util 中新建一個方面,包 com.unmi.util 的上下文菜單中 New -> Aspect,輸入名字 MethodTimeAdviceRecipe,然后 Finish,這時就會在包 com.unmi.util 中產(chǎn)生一個文件 MethodTimeAdviceRecipe.aj,內(nèi)容如下:
MethodTimeAdviceRecipe.aj (那么 MethodTimeAdvice.java 就派不上用場了,可刪去)
- package com.unmi.util;
- import org.apache.commons.lang.time.StopWatch;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- /**
- * 記錄方法的執(zhí)行時間
- * @author Unmi
- */
- public aspect MethodTimeAdviceRecipe {
- protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
- //攔截所有以 Service 結(jié)尾類的方法
- pointcut callServicePointCut() : call(* *..*Service.*(..))
- && !within(MethodTimeAdviceRecipe +);
- /**
- * 在方連接點(業(yè)務(wù)類方法)周圍執(zhí)行的通知
- */
- Object around() : callServicePointCut(){
- //用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
- StopWatch clock = new StopWatch();
- clock.start(); //計時開始
- Object result = proceed();
- clock.stop(); //計時結(jié)束
- //顯示出方法原型及耗時
- log.debug("Takes: " + clock.getTime() + " ms ["+thisJoinPoint.getSignature()+"("+
- thisJoinPoint.getSourceLocation().getLine()+")]");
- return result;
- }
- }
再就是因為無需用到 Spring 了,不需要 IOC 了,相應的 Main.java 的內(nèi)容如下:
Main.java (以前如何創(chuàng)建 HelloService,現(xiàn)在還是怎么做)
- package com.unmi;
- import com.unmi.service.HelloService;
- /**
- * 測試主類
- * @author Unmi
- */
- public class Main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- HelloService helloService = new HelloService();
- helloService.sayHello(1,"Unmi");
- }
- }
OK,現(xiàn)在就可以在 Eclipse 中開始運行 Main 類了,對 methodTimeAdviceRecipe.aj 的編譯工作由插件 AJDT 自動幫你作了(實質(zhì)是相匹配的類中插入了代碼)。運行 Main 的結(jié)果如下:
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 可以很靈活的定義方面,局限就是對方面的改變須重新編譯相關(guān)類,而非配置方式。
參考:一個用Spring AOP實現(xiàn)異常處理和記錄程序執(zhí)行時間的實例
FORM:http://www.aygfsteel.com/Unmi/archive/2008/01/18/165849.html