用Groovy源編程(MOP)動態攔截(AOP)方法(好比記錄String的concat和to...

實現AOP的方式有不少種,像Spring的AOP,它只能攔截Spring託管的bean;Groovy AST Transformations、ASM等在編譯階段經過修改字節碼也能夠作AOP;JAVA HOOK也能夠作,但比較麻煩。函數

Groovy MOP提供了一種很簡單的方法實現AOP。測試

 

下面經過例子試用一下:this

若是想動態攔截某個方法,不想改源代碼(或者不能改源碼,好比String已是final類了),而能跟蹤函數的執行時間(before invoke時記錄開始時間,after invoke時記錄完成時間,從而跟蹤函數執行時間),能夠用MOP實現。下面展現了3種方法:spa

 

方法一:用MOP重寫具體的方法:code

def recordDuration_concat() {
        // 保存原有方法         def savedMethod = String.metaClass.getMetaMethod('concat', [String] as Class[])
        // 開始改變原有方法         String.metaClass.concat = {String arg ->
            long s = System.currentTimeMillis();
            def result = savedMethod.invoke(delegate, arg)
            long e = System.currentTimeMillis();
            long duration = e - s;
            println("MOP耗費時間:" + duration);
            return result;
        }
    }

這種方法須要明確指定參數(String arg -> ),適用於具體明確的方法orm

 

方法二:用MOP重寫invokeMethod:get

def recordDuration_invokeMethod() {
        String.metaClass.invokeMethod = {String mName, mArgs ->
            def m = String.metaClass.getMetaMethod(mName, mArgs)
            if (mName != "concat" && mName != "toUpperCase") return m.invoke(delegate, mArgs)
            long s = System.currentTimeMillis();
            def result = m.invoke(delegate, mArgs)
            long e = System.currentTimeMillis();
            long duration = e - s;
            println("MOP耗費時間:" + duration);
            return result;
        }
    }

這種方法能夠在MOP時動態指定多個方法,沒必要一必定義。可是要當心死循環,它會攔截該類的全部方法。源碼

 

方法三:注入MetaClass:it

先定義MetaCalss:io

public class MyMetaClass extends DelegatingMetaClass {
    MyMetaClass(Class thisClass) {
        super(thisClass)
    }

    Object invokeMethod(Object object, String methodName, Object[] arguments) {
        if (methodName != "concat" && methodName != "toUpperCase")
            return super.invokeMethod(object, methodName, arguments)

        long s = System.currentTimeMillis();
        def result = super.invokeMethod(object, methodName, arguments)
        long e = System.currentTimeMillis();
        long duration = e - s;
        println("MOP耗費時間:${duration}");
        return result
    }
}

而後再註冊:

def amc = new MyMetaClass(String)
        amc.initialize()
        InvokerHelper.metaRegistry.setMetaClass(String, amc)

這種跟方法二實際上是同樣的,可是稍微繁瑣點。

 

ExpandoMetaClass和Category也能夠,能夠自行研究一下。

 

關於效率問題:

使用MOP是否會影響效率呢,我作了個小測試程序試一試

public class TC {
    public void call() {
        sleep(1000)
    }
}

函數執行須要花1秒鐘。

正常執行:

def testNormal() {
        1.upto(10000) {
            long s = System.currentTimeMillis()
            new TC().call()
            long e = System.currentTimeMillis()
            println "正常耗時:${e - s}"
        }
    }

執行結果:

正常耗時:1021
正常耗時:1003
正常耗時:1002
正常耗時:1002
正常耗時:1002
正常耗時:1002
正常耗時:1002
正常耗時:1002
正常耗時:1002
正常耗時:1002

 

用MOP攔截:

def recordDuration_call() {
        TC.metaClass.invokeMethod = {String mName, mArgs ->
            def m = TC.metaClass.getMetaMethod(mName, mArgs)
            long s = System.currentTimeMillis();
            def result = m.invoke(delegate, mArgs)
            long e = System.currentTimeMillis();
            long duration = e - s;
            println("MOP包裹的函數耗費時間:" + duration);
            return result;
        }
    }

    def testAop() {
        recordDuration_call()
        1.upto(10000) {
            long s = System.currentTimeMillis()
            new TC().call()
            long e = System.currentTimeMillis()
            println "aop後耗時:${e - s}"
        }
    }

執行結果:

MOP包裹的函數耗費時間:1014
aop後耗時:1039
MOP包裹的函數耗費時間:1003
aop後耗時:1004
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002
MOP包裹的函數耗費時間:1002
aop後耗時:1002

 

可見除頭兩次調用時間略長點,之後的執行時間是同樣的。

原生的方法的執行時間MOP先後是差很少的,甚至包裹後還略快了點(第一次原生是1021ms,MOP後包裹的原生函數是1014ms),整個AOP的調用頭兩次略高點,後來就正常了(第一次是1039ms,比原生的1021ms慢了一點)。

從這個測試來看,用Groovy MOP實現AOP對效率的影響很小。

相關文章
相關標籤/搜索