備注學院

          LuLu

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            5 隨筆 :: 50 文章 :: 16 評論 :: 0 Trackbacks

          有時候我們要跟蹤方法的執(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 目錄下

          1. log4j.rootLogger=DEBUG,stdout   
          2.   
          3. log4j.appender.stdout=org.apache.log4j.ConsoleAppender    
          4. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout    
          5.   
          6. log4j.appender.stdout.layout.ConversionPattern=%d [%5p] %c{1}.%M(%L) %n%m%n   

          2) 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   
          5. http://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 記錄時間的類

          1. package com.unmi.util;   
          2.   
          3. import org.aopalliance.intercept.MethodInterceptor;   
          4. import org.aopalliance.intercept.MethodInvocation;   
          5. import org.apache.commons.lang.StringUtils;   
          6. import org.apache.commons.lang.time.StopWatch;   
          7. import org.apache.commons.logging.Log;   
          8. import org.apache.commons.logging.LogFactory;   
          9.   
          10. /**  
          11.  * 記錄方法的執(zhí)行時間  
          12.  * @author Unmi  
          13.  */  
          14. public class MethodTimeAdvice implements MethodInterceptor {   
          15.     protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);   
          16.   
          17.     /**  
          18.      * 攔截要執(zhí)行的目標方法  
          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();  //計時結(jié)束   
          26.            
          27.         //方法參數(shù)類型,轉(zhuǎn)換成簡單類型   
          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 被攔截的業(yè)務(wù)類

          1. package com.unmi.service;   
          2.   
          3. /**  
          4.  * @author Unmi  
          5.  */  
          6. public 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 主程序類

          1. package com.unmi;   
          2.   
          3. import org.springframework.beans.factory.BeanFactory;   
          4. import org.springframework.context.support.ClassPathXmlApplicationContext;   
          5.   
          6. import com.unmi.service.HelloService;   
          7.   
          8. /**  
          9.  * 測試主類  
          10.  * @author Unmi  
          11.  */  
          12. public 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. }  

          執(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)容如下:

          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>  


          上面的配置方式需為每個應用方法執(zhí)行時間記錄的 Bean 在外層包一個 ProxyFactoryBean,原來的 Bean 設(shè)為一個 Target 實在時麻煩了。

          下面用一種應用自動代理的配置方式,指定 BeanNameAutoProxyCreator 的 beanNames 匹配模式即可,如果寫成 <value>*Service,*Manager</value>,逗號分隔開,以 Service 或 Manager 結(jié)層類的方法都被攔截,這樣方便許多。

          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.     <!-- 根據(jù) Bean 的名字自動實現(xiàn)代理攔截 -->  
          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 實現(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 就派不上用場了,可刪去)

          1. package com.unmi.util;   
          2. import org.apache.commons.lang.time.StopWatch;   
          3. import org.apache.commons.logging.Log;   
          4. import org.apache.commons.logging.LogFactory;   
          5.   
          6.   
          7. /**  
          8.  * 記錄方法的執(zhí)行時間  
          9.  * @author Unmi  
          10.  */  
          11. public aspect MethodTimeAdviceRecipe {   
          12.        
          13.     protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);   
          14.        
          15.     //攔截所有以 Service 結(jié)尾類的方法   
          16.     pointcut callServicePointCut() : call(* *..*Service.*(..))   
          17.                                         && !within(MethodTimeAdviceRecipe +);   
          18.        
          19.     /**  
          20.      * 在方連接點(業(yè)務(wù)類方法)周圍執(zhí)行的通知  
          21.      */  
          22.     Object around() : callServicePointCut(){   
          23.         //用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch   
          24.         StopWatch clock = new StopWatch();   
          25.         clock.start(); //計時開始   
          26.         Object result = proceed();   
          27.         clock.stop();  //計時結(jié)束   
          28.            
          29.         //顯示出方法原型及耗時   
          30.         log.debug("Takes: " + clock.getTime() + " ms ["+thisJoinPoint.getSignature()+"("+   
          31.                 thisJoinPoint.getSourceLocation().getLine()+")]");   
          32.            
          33.         return result;   
          34.     }   
          35. }  

          再就是因為無需用到 Spring 了,不需要 IOC 了,相應的 Main.java 的內(nèi)容如下:

          Main.java (以前如何創(chuàng)建 HelloService,現(xiàn)在還是怎么做)

          1. package com.unmi;   
          2.   
          3. import com.unmi.service.HelloService;   
          4.   
          5. /**  
          6.  * 測試主類  
          7.  * @author Unmi  
          8.  */  
          9. public 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,現(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
          posted on 2008-02-04 22:56 smildlzj 閱讀(697) 評論(0)  編輯  收藏 所屬分類: JavaWeb開發(fā)
          主站蜘蛛池模板: 宁都县| 峡江县| 平阴县| 花莲市| 上饶县| 郴州市| 黎川县| 搜索| 尼勒克县| 礼泉县| 绥阳县| 四平市| 吴旗县| 庆安县| 清原| 静安区| 景宁| 白山市| 靖西县| 赫章县| 屏边| 霸州市| 太仓市| 东平县| 沙洋县| 四川省| 富民县| 延川县| 陇南市| 尖扎县| 永靖县| 施秉县| 天峨县| 九江市| 磐安县| 乌鲁木齐市| 孟村| 商河县| 昌图县| 秦安县| 泰州市|