【框架學習與探究之AOP--Castle DynamicProxy】

聲明

本文歡迎轉載,原始地址:http://www.cnblogs.com/DjlNet/p/7603654.htmlhtml


前言

先說一點廢話,在此以前博主也在早期就接觸了或者看了些許AOP相關的文章,而後再去作了一些相關的實驗,可是始終沒有將AOP內化到本身的內功心法當中去,包括從概念仍是應用環境,以及當前生態當中的AOP工具等等,因此這裏博主仍是按照以往的套路,在前人的基礎之上學習而後吸取到集成到系統當中去。git


什麼是AOP

仍是先看官方解釋AOP(Aspect-Oriented Programming,面向切面的編程),它是能夠經過預編譯方式和運行期動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的一種技術。它是一種新的方法論,它是對傳統OOP編程的一種補充。OOP是關注將需求功能劃分爲不一樣的而且相對獨立,封裝良好的類,並讓它們有着屬於本身的行爲,依靠繼承和多態等來定義彼此的關係;AOP是但願可以將通用需求功能從不相關的類當中分離出來,可以使得不少類共享一個行爲,一旦發生變化,沒必要修改不少類,而只須要修改這個行爲便可AOP是使用切面(aspect)將橫切關注點模塊化,OOP是使用類將狀態和行爲模塊化。在OOP的世界中,程序都是經過類和接口組織的,使用它們實現程序的核心業務邏輯是十分合適。可是對於實現橫切關注點(跨越應用程序多個模塊的功能需求)則十分吃力,好比日誌記錄,權限驗證,異常攔截等。這裏能夠看到當咱們在應用程序中使用AOP來編程的話,是大大的節約個人代碼量並且作到了職責依然分明,因此有這麼多好處的狀況下,咱們就應該勇敢的嘗試來構建個人應用程序...github


.NET當中的AOP

根據上文關於AOP的兩種方式來實現了面向切面編程,而在.NET領域當中有些比較出名的,例如在動態代理方面就是castle.core(原名:castle dynamic proxy)經過Reflect.Emit的方式在原始基礎上面實現的動態代理方式,加上緩衝機制提高性能,該項目很好的屏蔽了底層使用emit+opcode的方式帶來的複雜性和難度,使用友好的API調用方式提供外部程序訪問castle.coree nuget地址,小吐槽:.net core已經2.0這麼久了,castle家族雖然也還在努力,可是依然還沒支持.net standrad2.0,多是由於castle.core如今打包了不止dynamicproxy一個包的緣由,須要一些時間,不過在@Lemon努力下率先提供了基於.net core的aop框架,這裏博主也是對此框架目前也是淺嘗輒止的狀態尚未應用到項目中,因此很差對比評價哈,哈哈!),因此基於咱們當前的項目而已動態中的castle不失爲咱們的一個好的選擇;而後在.net靜態編織方面當屬postsharp首當其衝了,固然這玩意兒是收費不過你可使用express免費版可是和企業版對dll的修改差別就不得而知了,當初博主實驗安裝的企業版也是在免費期內使用後面作了一些小手腳才得以繼續使用,固然這裏不推薦破解使用了,有錢的主兒仍是支持正版吧注意使用postsharp須要vs安裝插件(須要插件支持才能夠編譯後期對dll動手腳)以及nuget package支持,因此團隊裏面若是使用須要你們統一安裝,還有就是目前許多都是基於Jenkins來作持續集成的,因此還得注意使用了postsharp的項目編譯問題(使用了postsharp若是沒有插件支持會報錯)!!固然我這裏的指出相對比較計較的性能問題,靜態編織是比動態代理來的更快,由於dll在編譯以後都已經造成了AOP之勢,動態代理畢竟是運行時才得以去構建各類代理對象不過還好有緩衝,因此相對通常狀況動態代理足以知足咱們的需求,仍是那個句話:在已經解決顯而易見的瓶頸以後,才考慮一些錙銖必較的性能缺失!!!最後.NET周邊其餘AOP框架或者產品無論是動態仍是靜態,你們可自行搜索是有的,不過使用率以及可用性、穩定性、性能等等就很差說了!express


Castle DynamicProxy文檔帶來了什麼

首先Castle DynamicProxy是一個在運行時即時生成輕量級.NET代理的庫,而後除了介紹AOP使用場景就是當前一些流行項目對於castle.core的依賴,側面也反應除了此AOP的地位,好吧,233333,官方文檔是要捧一下本身的。咱們會發現 moq(mock的實現)、NHibernate(延遲加載virtual+getter攔截實現等)等都依賴了castle.core,說明了上面的框架功能提供確定是須要動態代理的支撐的,而後根據官方說明在版本2.5以後原先獨立的castle.dynamicproxy.dll就合併了,官方高能若是您有一個長時間運行的進程(網站,Windows服務等),而且必須建立許多動態代理,那麼您應該確保重用相同的ProxyGenerator實例。若是沒有,請注意,您將繞過緩存機制,反作用是CPU使用率高,內存消耗不斷增長。編程

這裏引用官方對於catle dynamicproxy的工做原理及流程的理解,顯示給出執行流程圖:

咱們這裏外部的藍色框就是代理區域,黃色箭頭將會層層進入各級代理對象,接着代理執行PreAction邏輯,而後調用invocation.Proceed()(每一個代理只能調用一次否則出爆發異常)進入下一個代理或者原始對象邏輯,就這樣一直進入到最下層也就是被代理的對象,執行原始邏輯以後,再按照層層代理執行PostAction邏輯彈出也就是綠色箭頭所表達的意思,就是一條完成的傳遞鏈就造成了多級代理模式。注意點:每一層代理對象均可以拿到目標的對象也就是被代理對象IInvocation,該接口包含了一些重要屬性和方法,例如:invocation.MethodInfo、invocation.ResultValue 等對象,咱們接下來對API實驗詳細看看這些對象的真實面目,至此根據官方文檔就這樣沒了,還有些stackoverflow的參考代碼,咱們同時翻看園中其餘使用代碼和對API的探究完善對castle dynamicproxy的瞭解....設計模式


Castle DynmaicProxy API提供了什麼

首先這裏要說明框架當中幾個重要的對象:
一、IInterceptor 攔截器該接口提供了能夠自定義攔截器邏輯的入口,實現接口的 Intercept 方法便可,在後面建立代理對象須要接口實例來控制代理對象行爲,這裏框架也提供了StandardInterceptor標準的攔截器繼承MarshalByRefObject對象以及實現了IInterceptor接口,它包含了protected virtual void PerformProceed(IInvocation invocation);protected virtual void PostProceed(IInvocation invocation);protected virtual void PreProceed(IInvocation invocation);三個經常使用的接口方法....api

public interface IInterceptor
{
    void Intercept(IInvocation invocation);
}

二、IProxyGenerator 代理對象的建立者,包含了兩個屬性:LoggerProxyBuilder(只讀,具體由它來建立代理類型),包含以下幾個重要的API方法:
(1)動態建立類代理 proxyGenerator.CreateClassProxy 包含重載:Creates proxy object intercepting calls to virtual members of type TClass on newly created instance of that type with given interceptors. (建立一個新的代理對象subclass,經過配置的攔截器攔截原始對象標記了公開public的虛方法virtual的method產生效果,包含使用方法傳遞進來的代理配置項);
(2)動態建立類代理經過既有實例 proxyGenerator.CreateClassProxyWithTarget 包含重載:Creates proxy object intercepting calls to virtual members of type TClass on newly created instance of that type with given interceptors.(建立一個新的代理對象subclass,經過配置的攔截器攔截原始對象標記了公開public的虛方法virtual的method產生效果,提供了方法參數傳遞一個既有的目標實例對象,包含使用方法傳遞進來的代理配置項);
(3)動態建立接口代理不須要實現接口的實例對象 proxyGenerator.CreateInterfaceProxyWithoutTarget:Creates proxy object intercepting calls to members of interface TInterface on target object generated at runtime with given interceptor.(動態建立接口對象實現的實例且不須要實現了接口實例參數,經過攔截器湊效於接口方法實現攔截,注意這裏若是接口方法要求了返回值,就須要在攔截器中指定返回值,相似於:invocation.ReturnValue=2;
(4)動態建立接口代理經過實現接口的實例對象 proxyGenerator.CreateInterfaceProxyWithTarget:Creates proxy object intercepting calls to members of interface TInterface on target object with given interceptors.(動態建立接口代理對象,經過傳遞實現了接口的對象實例,使用配置的攔截器對象做用於接口的每一個方法,這裏實現接口的對象實例的實現方法就能夠不須要配置爲vritual了,由於在接口代理對象中已經包裹住了原始對象是採用了相似於注入的方式而不是繼承,能夠參考下面的示例代碼
(5)動態建立接口代理經過實現了接口的實例對象 proxyGenerator.CreateInterfaceProxyWithTargetInterface:Creates proxy object intercepting calls to members of interface TInterface on target object with given interceptors. Interceptors can use Castle.DynamicProxy.IChangeProxyTarget interface to provide other target for method invocation than default target.(與上述CreateInterfaceProxyWithTarget類似,那麼它們的區別在於哪裏吶,這裏博主本着研究的精神找了一下,不至於翻看源碼了....,找到相似代碼提供者的一點描述以下:http://kozmic.net/2009/11/13/interfaceproxywithtarget-interfaceproxywithtargetinterface-ndash-whatrsquos-the-difference/,大致總結就是:通常狀況下調用者須要的就是CreateInterfaceProxyWithTargetInterface這個API的調用,其中它提供了兩個優勢:當使用InterfaceProxyWithTargetInterface時,它的調用實現了IChangeProxyTarget接口,該接口容許攔截途中更改目標對象。而後最重要的是 InterfaceProxyWithTargetInterface更好地使用緩存,詳情參考連接代碼驗證過程,固然博主也親自測試如同文中做者一模一樣!!。)博主實驗參考以下:數組

IMyCompare oneCompare = proxyGenerator.CreateInterfaceProxyWithTarget<IMyCompare>(new MyCompareOne(), new MyStandradInterceptor());
            IMyCompare twoCompare = proxyGenerator.CreateInterfaceProxyWithTarget<IMyCompare>(new MyCompareTwo(), new MyStandradInterceptor());

            Type oneCompareType = oneCompare.GetType();
            Type twoCompareType = twoCompare.GetType();

            Console.WriteLine("{0}", object.ReferenceEquals(oneCompareType, twoCompareType)); // false

            IMyCompare oneCompare1 = proxyGenerator.CreateInterfaceProxyWithTargetInterface<IMyCompare>(new MyCompareOne(), new MyStandradInterceptor());
            IMyCompare twoCompare1 = proxyGenerator.CreateInterfaceProxyWithTargetInterface<IMyCompare>(new MyCompareTwo(), new MyStandradInterceptor());
            Type oneCompare1Type = oneCompare1.GetType();
            Type twoCompare1Type = twoCompare1.GetType();
            Console.WriteLine("{0}", object.ReferenceEquals(oneCompare1Type, twoCompare1Type)); // ture

關於castle dynamicproxy動態代理中的對與class與interface的處理方式大體原理探究關於class的代理,相信不少同窗都應該直到,也就是設計模式當中的代理模式的利用,具體就是繼承原始類對象實現對原始對象虛方法的重寫實現注入攔截的邏輯,不過這一切都是動態的不須要咱們去構建了,參考代碼以下(這裏只考慮一級代理,多級也就是多層繼承關係):緩存

public class Caller
    {
        public virtual void Call()
        {
            Console.WriteLine("calling...");
        }
    }

    public class CallerProxy : Caller
    {
        public override void Call()
        {
            // 執行前
            Console.WriteLine("pre action");
            base.Call();
            // 執行後
            Console.WriteLine("post action");
        }
    }

    static void Main(string[] args)
        {
            Caller caller = new CallerProxy();
            caller.Call(); Console.ReadKey();
        }

接着關於interface的代理的大體原型是,經過實現接口產生一個包含了傳遞的接口實習實例對象的接口代理對象,可能這裏有點繞,不過依然仍是代理模式,關係從繼承變成了包含,同理這些東西castle已經幫我用動態的方式構建好了,咱們看一示例代碼就知道了,這裏展現一層代理多級代理就是層層包含了架構

public interface IService
    {
        void Process();
    }

    public class Service : IService
    {
        public void Process()
        {
            // do something
            Console.WriteLine("processing...");
        }
    }

    public class ServiceProxy : IService
    {
        private readonly Service _service;
        public ServiceProxy(Service service)
        {
            _service = service;
        }

        public void Process()
        {
            // pre action
            Console.WriteLine("pre action");
            _service.Process();
            // post action
            Console.WriteLine("post action");
        }
    }

IService service = new ServiceProxy(new Service());
            service.Process();
            Console.ReadKey();

相信看到這裏你也以爲,我去,動態代理這麼簡單麼,其實否則,雖然道理你們一看就懂是簡單就是設計模式的代理模式的運用嘛,可是將這個動做泛化爲一個通用的API支持可變攔截器數量配置以及各類代理配置將是一項繁雜而當心的工做,既要考慮友好的API還有重中之重的性能保證,也就是使用上面提到的 Reflect.Emit + OpCode 來實現接近於元數據編程....,可怕!!!因此,這裏給寫AOP的同窗點贊!@Lemon
三、代理生成配置對象 ProxyGenerationOptions,在生成代理對象是可傳遞自定義配置對象,實現可控的攔截,該對象主要配置項:

public IProxyGenerationHook Hook { get; set; } (決定了該方法是否受攔截器攔截,能夠實現自定義Hook)
public IInterceptorSelector Selector { get; set; } (決定了該方法受那些攔截器攔截,能夠實現自定義Selector)
public Type BaseTypeForInterfaceProxy { get; set; } (決定了接口代理的基礎類型,詳情使用參考連接:https://stackoverflow.com/questions/2969274/castle-dynamicproxy-how-to-proxy-equals-when-proxying-an-interface

具體例子參考以下:

public class MyInterceptorSelector : IInterceptorSelector
    {
        public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
        {
            if (method.Name.EndsWith("Repository"))
            {
                return interceptors.Where(x => x is TestInterceptor).ToArray();
            }
            return interceptors.Where(x => x is TestInterceptor2 || x is MyStandradInterceptor).ToArray();
        }
    }
 public class MyGenerationProxyHook : IProxyGenerationHook
    {
        public void MethodsInspected()
        {
        }

        public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
        {
        }

        public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
        {
            return methodInfo.Name.EndsWith("Service");
        }
    }

這裏相信你們一看就明白了,就很少說了.....這裏注意點就是: 攔截器的植入順序與生效順序是一致的....


從其餘框架覆盤認識castle與集成

瞭解過[abp]:(https://aspnetboilerplate.com/) 的同窗,確定就知道此框架強制把castle家族的castle.core+Castle.Windsor(依賴前者)融入進abp當中了,採用了接口代理實現了日誌記錄、異常處理、權限審查、審計日誌等等,固然abp框架當中確實不止一處是值得咱們學習,不過此框架構建在一個及集衆家之所長的狀況下就顯得複雜,從知名度能夠看出選擇caslte來做爲aop+ioc的集成是個不錯的選擇,接着從moq的部分實例代碼中能夠看出,就是利用了proxyGenerator.CreateInterfaceProxyWithoutTarget等等,之類的細節就能夠回想起原始API作起底層的支撐做用,再者castle與許多第三方ioc有着比較好的集成,例如比較出名的ioc框架autofac+castle也是不少人的選擇,參考連接:[AOP系列]Autofac+Castle實現AOP事務:http://www.cnblogs.com/jianxuanbing/p/7199457.html
這裏博主爲什麼要把ioc扯進來一塊兒說吶?
從castle的api來看你們以爲有木有點覺着好像有着一點ioc的功能,可是爲什麼咱們要明確概念說aop是aop,ioc是ioc吶,這裏博主的理解就是,它們各自的職責是不一樣,它們各自只須要各司其職就好了,也不要越界也是編程開發當中的單一職責的體現了,雖然多多少少你們都有些動態建立對象那麼回事兒(無論是emit仍是activor.CreateInstance)!可是咱們從ioc的職能分析獲得的是:一、負責對象的存儲(註冊)與建立(解析) 二、負責對象的依賴關係維護 三、負責對象的生命週期,這三點就能夠看出與AOP功能不一致,可是咱們看到對象建立這個時候,想一下是否能夠在對象建立的時植入Interceptor???回答是:確定是能夠的,因此這就是爲什麼咱們經常把ioc與aop一塊兒來食用了,據說這樣用更配哦!!


總結

咱們從AOP的定義到AOP的使用場景,而後.net下面的AOP的介紹,接着重點介紹了動態代理中的castle.core的官方說明與文檔,後面尤爲重要的詳解了框架當中重要的一些對象和API以及原理和實踐,也途中參考一些文章和stackoverflow,也請教AOP相關人士,這裏感謝!好了,時間也不早了,相信學無止境,那麼就保持持續學習,持續內化知識,就像修煉內功心法同樣,半途而廢還容易走火入魔,只知其一;不知其二說出去的東西本身都沒搞明白,豈不是笑話了!!加油吧,騷年

這裏引用知乎大大的一段話以此激勵:成長必須經歷一個步驟,就是把知識內化成能力。知識是用腦記住的,能力是用手練習出來的。在工做的幾年裏,咱們可能看過不少書,聽過不少技術講座和視頻,可是經過聽和看只是讓你能記住這些知識,這些知識還不能轉換成你的能力。聽和看只是第一步,更重要的是實踐,經過刻意練習把聽到和看到的知識內化成你的能力。刻意練習,就是有目的的練習,先規劃好,再去練習。首先給本身定一個目標,目標能夠有效的引導你學習,而後使用3F練習法:1: 專一(Focus),專一在眼前的任務上,在學習過程當中保持專一,能夠嘗試使用番茄工做法。2:反饋(Feedback),意識到本身的不足,學習完以後進行反思,思考下本身哪些方面不足,爲何不足,3: 修正(Fix),改進本身的不足。不停的練習和思考能夠改變大腦結構,大腦像肌肉同樣,挑戰越大,影響越大,學習更高效,而且也會產生突破性。 -- 原始連接: https://www.zhihu.com/question/26572626/answer/246901769?utm_medium=social&utm_source=qq


收集備註園中相關AOP好文

一、Asp.Net Core輕量級Aop解決方案:AspectCore http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html
二、C# 實現AOP 的幾種常見方式http://www.cnblogs.com/zuowj/p/7501896.html
三、.Net基於RealProxy實現AOP : http://www.cnblogs.com/lflyq/p/6286925.html
四、Aspect-Oriented Programming : 使用 RealProxy 類進行面向方面的編程 : https://msdn.microsoft.com/zh-cn/magazine/dn574804.aspx


補充更新(2017年10月20日15:07:33)

關於 castle api 當中對於 IInvocation對象的解釋少了一些,這裏補充一下:
一、invocation.Arguments: 方法執行被攔截時的方法參數數組
二、invocation.GenericArguments: 被攔截方法的泛型參數類型數組,若是沒有就是null
三、invocation.InvocationTarget: 獲取當前執行的目標對象,例如:若是是class代理就是YourClassProxy,接口代理就是實現接口的對象實例,若是沒有則爲null,也就是當使用xxxWithoutTarget的時候
四、invocation.Method:獲取代理對象的方法信息,例如:若是是class代理的時候就是YourClass.YourMethod對象的MethodInfo且這個時候invocation.Method == invocation.MethodInvocationTarget;若是是interface代理就是接口對象的方法信息,例如:ICall.Call 這個方法的MethodInfo信息且這個時候invocation.Method != invocation.MethodInvocationTarget,由於invocation.MethodInvocationTarget是接口對應實現的目標對象的方法信息,也就是例如:MyCall.Call 方法對應上面的 ICall 接口來講,固然也可使用 WithoutTarget方式,這樣就會致使 invocation.MethodInvocationTarget==null的狀況
五、invocation.MethodInvocationTarget: 指向真正的目標對象的方法信息MethodInfo,大體能夠根據第四點給出了說明
六、invocation.Proxy : 獲取攔截時的代理對象,例如:YourClassProxy(類代理) 或者 ICallProxy(接口代理) 對象
七、invocation.ResultValue: 獲取或者設置代理方法的返回值
八、invocation.TargetType: 獲取真實目標對象的類型
九、invocation.GetArgumentValue(int index);: 經過index獲取參數值
十、invocation.GetConcreteMethod();: 同理第四點
十一、invocation.GetConcreteMethodInvocationTarget();: 同理第五點
十二、invocation.Proceed();: 調用下一個攔截器直到目標方法
1三、invocation.SetArgumentValue(int index, object value);: 設置更改參數值經過下標
這裏博主列舉除了攔截途中對象IInvocation的全部成員,你們在使用攔截器的時候可根據本身邏輯使用以上API到達要求!


若是以爲閱讀了博主的小文以爲對您有幫助,您的點贊和評論都是對博主最大的支持!!!

相關文章
相關標籤/搜索