最近要在一個舊的scala系統加入一些監控的程式碼,找出影響效能的地方.
想了兩個做法可以解決:
方法一: 在懷疑的程式碼地方上下夾log,計算出執行時間.
方法二: 利用AOP方式攔截每個function執行時開始點和結束點,計算出執行時間.
來分析一下這兩個方法的優缺點:
方法一:
優點:不用大腦就可以解決
缺點:需要在所有的點插入log,造成程式充滿一堆log
方法二:
優點:可以不用動到原本的程式碼
缺點:需要寫一些程式碼
後來採用Aspectj來解決這個問題.
新增一個Monitor Aspect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MonitorAspect {
private long startTime;
private Logger logger = LoggerFactory.getLogger(MonitorAspect.class);
"execution (* *(..))", argNames = "joinPoint") (value =
public void before(JoinPoint joinPoint) {
startTime = System.currentTimeMillis();
}
"execution (* *(..))", argNames = "joinPoint") (value =
public void after(JoinPoint joinPoint) {
long executionTime = System.currentTimeMillis() - startTime;
logger.debug("Signature : {}, Source Line : {}, Execute Time : {}",
joinPoint.getSignature(),
joinPoint.getSourceLocation(),
executionTime);
}
}加入aspectj jar
1
2"org.aspectj" % "aspectjweaver" % "1.8.7",
"org.aspectj" % "aspectjrt" % "1.8.7"利用load time weaving方式.需要在resource資料夾下新增
META-INF/aop.xml
1
2
3
4
5
6
7
8
9
10
11<aspectj>
<aspects>
<aspect name="aspectj.MonitorAspect"/>
</aspects>
<weaver options="-verbose -showWeaveInfo">
<include within="xxx.yyy.*"/>
<include within="xxx.zzz.*"/>
<include within="aspectj.*"/>
</weaver>
</aspectj>
詳細參數可以參考:Chapter 5. Load-Time Weaving
- 因為舊系統是用
java
啟動,所以需要加入javaagent
參數,指定aspectjweaver.jar
的位置,例如:java -javaagent=/jars/aspectjweaver-1.8.7.jar ...
在實作過程中遇到了不少的坑,紀錄一下:
- 第一個坑:
在實作Aspect時,Annotation中可以准許不用對每個參數給值,但是會噴錯…
以下寫法是准許的,但是load time weaving時會噴錯1
"execution (* *(..))") (
後來改成以下寫法就沒事了1
"execution (* *(..))", argNames = "joinPoint") (value =
- 第二個坑:
為了可以動態修改aop.xml,必須將這個檔案從jar拉出來放.可以透過-Dorg.aspectj.weaver.loadtime.configuration=file:{aop.xml位置}
來指定aop位置.
但是啟動程式時卻又噴錯java.lang.RuntimeException: Cannot register non aspect:...
,
原因是javax.management.remote.rmi.NoCallStackClassLoader
不能載入我寫好的aspect.(這邊日後有時間需要來研究一下,應該是執行順序的問題…)
透過-Daj.weaving.loadersToSkip=javax.management.remote.rmi.NoCallStackClassLoader
略過它就可以執行了.
詳細可參考:AspectJ: ClassLoading issue when trying to use external aop.xml file
- 第三個坑:
一開始沒有在aop.xml
的weaver
tag中加入MonitorAspect的位置,又噴錯java.lang.NoSuchMethodError: aspectj.MonitorAspect.aspectOf()Laspectj/MonitorAspect
加入MonitorAspect的位置就正常了!
詳細可參考:AspectJ java.lang.NoSuchMethodError: aspectOf
- 第四個坑:
為了可以在aop.xml
動態指定要waving的type,千萬不能在annotation的value欄位寫死.
例如:1
"execution (* xxx.yyy.*(..))", argNames = "joinPoint") (value =
1 | <aspectj> |
結果:只能waving到xxx.yyy.*
,不能waving xxx.zzz.*
改成以下:1
"execution (* *(..))", argNames = "joinPoint") (value =
1 | <aspectj> |
結果:可以waving到xxx.yyy.*
和xxx.zzz.*
心得:
AOP的概念真的很強大,可以讓你不費吹灰之力去監控Legacy System.不用更改舊有程式,也不會有多餘且重複的程式碼,之後可以來想看看有什麼地方可以利用AOP.
因為scala版本太舊了(2.9.1, jdk 6),利用compile weaving方式失敗了,只好用load time weaving方式.
假如採用較新的scala版本或許可以考慮 sbt-aspectj.