[AOP] Monitor Legacy System

最近要在一個舊的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
    28
    package 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;

    @Aspect
    public class MonitorAspect {
    private long startTime;
    private Logger logger = LoggerFactory.getLogger(MonitorAspect.class);

    @Before(value = "execution (* *(..))", argNames = "joinPoint")
    public void before(JoinPoint joinPoint) {
    startTime = System.currentTimeMillis();
    }

    @After(value = "execution (* *(..))", argNames = "joinPoint")
    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
@Before("execution (* *(..))")

後來改成以下寫法就沒事了

1
@Before(value = "execution (* *(..))", argNames = "joinPoint")

  • 第二個坑:

為了可以動態修改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.xmlweaver tag中加入MonitorAspect的位置,又噴錯
java.lang.NoSuchMethodError: aspectj.MonitorAspect.aspectOf()Laspectj/MonitorAspect
加入MonitorAspect的位置就正常了!
詳細可參考:AspectJ java.lang.NoSuchMethodError: aspectOf

  • 第四個坑:

為了可以在aop.xml動態指定要waving的type,千萬不能在annotation的value欄位寫死.

例如:

1
@Before(value = "execution (* xxx.yyy.*(..))", argNames = "joinPoint")

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>

結果:只能waving到xxx.yyy.*,不能waving xxx.zzz.*

改成以下:

1
@Before(value = "execution (* *(..))", argNames = "joinPoint")

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>

結果:可以waving到xxx.yyy.*xxx.zzz.*

心得:

AOP的概念真的很強大,可以讓你不費吹灰之力去監控Legacy System.不用更改舊有程式,也不會有多餘且重複的程式碼,之後可以來想看看有什麼地方可以利用AOP.

因為scala版本太舊了(2.9.1, jdk 6),利用compile weaving方式失敗了,只好用load time weaving方式.
假如採用較新的scala版本或許可以考慮 sbt-aspectj

參考: