AOP 來記錄每個方法的執行時間

[轉載]用 AOP 來記錄每個方法的執行時間
有時候我們要跟蹤方法的執行時間,來觀察系統的性能、時間分佈。特別是要找出那些十分耗時的操作。如果是在每個方法中起始和結束位置記下時間相減,那是不太現實的,對代碼的侵入性太過份,而且在產品環境中又得屏閉那部份代碼。

幸好現在有了 AOP,通過配置方式再加上外部輔助代碼就能達到我們的要求,正式上線時只需要簡單改個配置項拆卸下來即可。

下面介紹三種方式來打印每個方法的執行時間,分別是:

1. Spring 2.0 用 AspectJ 實現 AOP
2. Spring 通用的方法攔截
3. 直接用 AspectJ 實現

1. Spring 2.0 用 AspectJ 實現 AOP

這個實例由五個文件構成,兩個配置文件和三個類文件。需要在項目中引用 Spring 2.0 以上版本的相關包,還要日誌包。

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
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>
<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;

/**
* 記錄方法的執行時間
* @author Unmi
*/
public class MethodTimeAdvice implements MethodInterceptor {
protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);

/**
* 攔截要執行的目標方法
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
//用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
StopWatch clock = new StopWatch();
clock.start(); //計時開始
Object result = invocation.proceed();
clock.stop(); //計時結束

//方法參數類型,轉換成簡單類型
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;
}
}
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;
/**
* 記錄方法的執行時間
* @author Unmi
*/
public class MethodTimeAdvice implements MethodInterceptor {
protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
/**
* 攔截要執行的目標方法
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
//用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
StopWatch clock = new StopWatch();
clock.start(); //計時開始
Object result = invocation.proceed();
clock.stop(); //計時結束
//方法參數類型,轉換成簡單類型
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 被攔截的業務類


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+")");
}
}
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");
}
}
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");
}
}
執行 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 文件就行,內容如下:


<?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>
<?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>

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

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


<?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>

<!-- 根據 Bean 的名字自動實現代理攔截 -->
<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>
<?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>
<!-- 根據 Bean 的名字自動實現代理攔截 -->
<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 實現

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 就派不上用場了,可刪去)


package com.unmi.util;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
* 記錄方法的執行時間
* @author Unmi
*/
public aspect MethodTimeAdviceRecipe {

protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);

//攔截所有以 Service 結尾類的方法
pointcut callServicePointCut() : call(* *..*Service.*(..))
&& !within(MethodTimeAdviceRecipe +);

/**
* 在方連接點(業務類方法)周圍執行的通知
*/
Object around() : callServicePointCut(){
//用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
StopWatch clock = new StopWatch();
clock.start(); //計時開始
Object result = proceed();
clock.stop(); //計時結束

//顯示出方法原型及耗時
log.debug("Takes: " + clock.getTime() + " ms ["+thisJoinPoint.getSignature()+"("+
thisJoinPoint.getSourceLocation().getLine()+")]");

return result;
}
}
package com.unmi.util;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 記錄方法的執行時間
* @author Unmi
*/
public aspect MethodTimeAdviceRecipe {
protected final Log log = LogFactory.getLog(MethodTimeAdvice.class);
//攔截所有以 Service 結尾類的方法
pointcut callServicePointCut() : call(* *..*Service.*(..))
&& !within(MethodTimeAdviceRecipe +);
/**
* 在方連接點(業務類方法)周圍執行的通知
*/
Object around() : callServicePointCut(){
//用 commons-lang 提供的 StopWatch 計時,Spring 也提供了一個 StopWatch
StopWatch clock = new StopWatch();
clock.start(); //計時開始
Object result = proceed();
clock.stop(); //計時結束
//顯示出方法原型及耗時
log.debug("Takes: " + clock.getTime() + " ms ["+thisJoinPoint.getSignature()+"("+
thisJoinPoint.getSourceLocation().getLine()+")]");
return result;
}
}
再就是因爲無需用到 Spring 了,不需要 IOC 了,相應的 Main.java 的內容如下:

Main.java (以前如何創建 HelloService,現在還是怎麼做)


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");
}
}
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,現在就可以在 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實現異常處理和記錄程序執行時間的實例




FORM:http://www.blogjava.net/Unmi/archive/2008/01/18/165849.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章