面向切面編程(AOP)通過將橫切關注點(如日誌記錄、事務管理、安全檢查)從業務邏輯中分離,能夠顯著提升代碼的整潔度與可維護性。本文將深入探討如何使用AspectWerkz框架構建一個功能完善的日誌記錄與性能監控切面,從基礎概念到進階應用逐步演示。
AspectWerkz是一個輕量級、高性能的AOP框架,其核心由三大要素構成:
首先定義一個基礎切面類,記錄方法進出:
package com.sosnoski.aspectwerkz;
public class TraceAspect {
public void beforeMethod(JoinPoint join) {
MethodSignature signature = (MethodSignature)join.getSignature();
System.out.println("Entering method " + signature.getName());
}
public void afterMethod(JoinPoint join) {
MethodSignature signature = (MethodSignature)join.getSignature();
System.out.println("Leaving method " + signature.getName());
}
}
對應的XML配置針對所有返回long類型的方法:
<aspectwerkz>
<system id="AspectWerkzExample">
<package name="com.sosnoski.aspectwerkz">
<aspect class="TraceAspect">
<pointcut name="longMethod" expression="execution(long *(..))"/>
<advice name="beforeMethod" type="before" bind-to="longMethod"/>
<advice name="afterMethod" type="after" bind-to="longMethod"/>
</aspect>
</package>
</system>
</aspectwerkz>
為了獲得更豐富的上下文信息,改用around advice,可控制目標方法執行時機,並輸出參數與返回值:
public Object traceLong(JoinPoint join) throws Throwable {
// 列印進入信息與參數
indent();
System.out.print("Entering method ");
printCall(join);
// 執行目標方法
m_nestingDepth++;
Object result = join.proceed();
m_nestingDepth--;
// 列印退出信息與返回值
indent();
System.out.print("Leaving method ");
MethodSignature signature = (MethodSignature)join.getSignature();
System.out.print(signature.getName());
System.out.print('<');
System.out.println(result);
return result;
}
配置僅需將advice類型改為around並綁定新方法名。
為避免大量輸出淹沒關鍵信息,引入時間閾值過濾:僅當方法執行時間超過設定值(如1ms)時才輸出。這需要延遲列印「進入」行,直到確認耗時達標,並補打所有祖先方法的進入信息以保持巢狀結構完整。
public Object traceTimed(JoinPoint join) throws Throwable {
m_pendingPrints.add(join); // 記錄待列印調用
long start = System.currentTimeMillis();
m_nestingDepth++;
Object result = join.proceed();
m_nestingDepth--;
// 移除自身並計算耗時
long time = System.currentTimeMillis() - start;
if (time > 1) { // 閾值過濾
// 補打所有未列印的祖先進入行
for (int i = 0; i < m_pendingPrints.size(); i++) {
JoinPoint prior = (JoinPoint)m_pendingPrints.get(i);
indent(m_nestingDepth - m_pendingPrints.size() + i);
System.out.print("Entering ");
printCall(prior);
System.out.println();
}
m_pendingPrints.clear();
// 列印當前方法退出信息含耗時
indent();
System.out.print("Leaving ");
printCall(join);
System.out.print('=');
System.out.print(result);
System.out.print(" took ");
System.out.print(time);
System.out.println(" ms.");
}
return result;
}
測試輸出清晰展示了遞歸調用鏈與熱點方法:
Entering fibo(15)
Entering fibo(14)
Entering fibo(13)
Entering fibo(12)
Leaving fibo(11)=89 took 2 ms.
Leaving fibo(10)=55 took 2 ms.
Leaving fibo(12)=144 took 11 ms.
...
Leaving fibo(15)=610 took 95 ms.
AspectWerkz提供兩種織入方式:編譯時與運行時(類載入器)。運行時模式啟動命令如下:
$ASPECTWERKZ_HOME/bin/aspectwerkz \
-Daspectwerkz.definition.file=aspectwerkz.xml \
-cp classes com.sosnoski.aspectwerkz.FiboTest 15
本文展示了如何利用AspectWerkz從零構建一個具備巢狀顯示、參數輸出、性能過濾功能的日誌切面。下一步將探討如何將此切面應用於真實企業級框架(如自帶日誌的Hibernate、Spring),並分析AOP在實際項目中的優勢與局限。敬請期待後續專欄。