天天看點

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

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

先說一點廢話,在此之前部落客也在早期就接觸了或者看了些許AOP相關的文章,然後再去做了一些相關的實驗,但是始終沒有将AOP内化到自己的内功心法當中去,包括從概念還是應用環境,以及目前生态當中的AOP工具等等,是以這裡部落客還是按照以往的套路,在前人的基礎之上學習然後吸收到內建到系統當中去。

還是先看官方解釋:AOP(Aspect-Oriented Programming,面向切面的程式設計),它是可以通過預編譯方式和運作期動态代理實作在不修改源代碼的情況下給程式動态統一添加功能的一種技術。它是一種新的方法論,它是對傳統OOP程式設計的一種補充。OOP是關注将需求功能劃分為不同的并且相對獨立,封裝良好的類,并讓它們有着屬于自己的行為,依靠繼承和多态等來定義彼此的關系;AOP是希望能夠将通用需求功能從不相關的類當中分離出來,能夠使得很多類共享一個行為,一旦發生變化,不必修改很多類,而隻需要修改這個行為即可。AOP是使用切面(aspect)将橫切關注點子產品化,OOP是使用類将狀态和行為子產品化。在OOP的世界中,程式都是通過類和接口組織的,使用它們實作程式的核心業務邏輯是十分合适。但是對于實作橫切關注點(跨越應用程式多個子產品的功能需求)則十分吃力,比如日志記錄,權限驗證,異常攔截等。這裡可以看到當我們在應用程式中使用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架構或者産品不管是動态還是靜态,大家可自行搜尋是有的,不過使用率以及可用性、穩定性、性能等等就不好說了!

首先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的工作原理及流程的了解,顯示給出執行流程圖:
【架構學習與探究之AOP--Castle DynamicProxy】
我們這裡外部的藍色框就是代理區域,黃色箭頭将會層層進入各級代理對象,接着代理執行PreAction邏輯,然後調用<code>invocation.Proceed()</code>(每個代理隻能調用一次不然出爆發異常)進入下一個代理或者原始對象邏輯,就這樣一直進入到最下層也就是被代理的對象,執行原始邏輯之後,再按照層層代理執行PostAction邏輯彈出也就是綠色箭頭所表達的意思,就是一條完成的傳遞鍊就形成了多級代理模式。注意點:每一層代理對象都可以拿到目标的對象也就是被代理對象<code>IInvocation</code>,該接口包含了一些重要屬性和方法,例如:invocation.MethodInfo、invocation.ResultValue 等對象,我們接下來對API實驗詳細看看這些對象的真實面目,至此根據官方文檔就這樣沒了,還有些stackoverflow的參考代碼,我們同時翻看園中其他使用代碼和對API的探究完善對castle dynamicproxy的了解....

首先這裡要說明架構當中幾個重要的對象:

1、<code>IInterceptor</code> 攔截器,該接口提供了可以自定義攔截器邏輯的入口,實作接口的 Intercept 方法即可,在後面建立代理對象需要接口執行個體來控制代理對象行為,這裡架構也提供了StandardInterceptor标準的攔截器繼承MarshalByRefObject對象以及實作了IInterceptor接口,它包含了<code>protected virtual void PerformProceed(IInvocation invocation);protected virtual void PostProceed(IInvocation invocation);protected virtual void PreProceed(IInvocation invocation);</code>三個常用的接口方法....

2、<code>IProxyGenerator</code> 代理對象的建立者,包含了兩個屬性:<code>Logger</code> 、<code>ProxyBuilder(隻讀,具體由它來建立代理類型)</code>,包含如下幾個重要的API方法:

(1)動态建立類代理 <code>proxyGenerator.CreateClassProxy 包含重載</code>: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)動态建立類代理通過既有執行個體 <code>proxyGenerator.CreateClassProxyWithTarget 包含重載</code>: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)動态建立接口代理不需要實作接口的執行個體對象 <code>proxyGenerator.CreateInterfaceProxyWithoutTarget</code>:Creates proxy object intercepting calls to members of interface TInterface on target object generated at runtime with given interceptor.(動态建立接口對象實作的執行個體且不需要實作了接口執行個體參數,通過攔截器湊效于接口方法實作攔截,注意這裡如果接口方法要求了傳回值,就需要在攔截器中指定傳回值,類似于:<code>invocation.ReturnValue=2;</code>)

(4)動态建立接口代理通過實作接口的執行個體對象 <code>proxyGenerator.CreateInterfaceProxyWithTarget</code>:Creates proxy object intercepting calls to members of interface TInterface on target object with given interceptors.(動态建立接口代理對象,通過傳遞實作了接口的對象執行個體,使用配置的攔截器對象作用于接口的每個方法,這裡實作接口的對象執行個體的實作方法就可以不需要配置為vritual了,因為在接口代理對象中已經包裹住了原始對象是采用了類似于注入的方式而不是繼承,可以參考下面的示例代碼)

(5)動态建立接口代理通過實作了接口的執行個體對象 <code>proxyGenerator.CreateInterfaceProxyWithTargetInterface</code>: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更好地使用緩存,詳情參考連結代碼驗證過程,當然部落客也親自測試如同文中作者如出一轍!!。)部落客實驗參考如下:

關于castle dynamicproxy動态代理中的對與class與interface的處理方式大緻原理探究:關于class的代理,相信很多同學都應該直到,也就是設計模式當中的代理模式的利用,具體就是繼承原始類對象實作對原始對象虛方法的重寫實作注入攔截的邏輯,不過這一切都是動态的不需要我們去建構了,參考代碼如下(這裡隻考慮一級代理,多級也就是多層繼承關系):

接着關于interface的代理的大緻原型是,通過實作接口産生一個包含了傳遞的接口實習執行個體對象的接口代理對象,可能這裡有點繞,不過依然還是代理模式,關系從繼承變成了包含,同理這些東西castle已經幫我用動态的方式建構好了,我們看一示例代碼就知道了,這裡展示一層代理多級代理就是層層包含了:

相信看到這裡你也覺得,我去,動态代理這麼簡單麼,其實不然,雖然道理大家一看就懂是簡單就是設計模式的代理模式的運用嘛,但是将這個動作泛化為一個通用的API支援可變攔截器數量配置以及各種代理配置将是一項繁雜而小心的工作,既要考慮友好的API還有重中之重的性能保證,也就是使用上面提到的 Reflect.Emit + OpCode 來實作接近于中繼資料程式設計....,可怕!!!是以,這裡給寫AOP的同學點贊!@Lemon

3、代理生成配置對象 <code>ProxyGenerationOptions</code>,在生成代理對象是可傳遞自定義配置對象,實作可控的攔截,該對象主要配置項:

<code>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)</code>

具體例子參考如下:

這裡相信大家一看就明白了,就不多說了.....這裡注意點就是: 攔截器的植入順序與生效順序是一緻的....

了解過[abp]:(https://aspnetboilerplate.com/) 的同學,肯定就知道此架構強制把castle家族的castle.core+Castle.Windsor(依賴前者)融入進abp當中了,采用了接口代理實作了日志記錄、異常處理、權限審查、審計日志等等,當然abp架構當中确實不止一處是值得我們學習,不過此架構建構在一個及集衆家之所長的情況下就顯得複雜,從知名度可以看出選擇caslte來作為aop+ioc的內建是個不錯的選擇,接着從<code>moq</code>的部分執行個體代碼中可以看出,就是利用了<code>proxyGenerator.CreateInterfaceProxyWithoutTarget</code>等等,之類的細節就可以回想起原始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的職能分析得到的是:1、負責對象的存儲(注冊)與建立(解析) 2、負責對象的依賴關系維護 3、負責對象的生命周期,這三點就可以看出與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&amp;utm_source=qq

1、Asp.Net Core輕量級Aop解決方案:AspectCore http://www.cnblogs.com/liuhaoyang/p/aspectcore-introduction-1.html

2、C# 實作AOP 的幾種常見方式 : http://www.cnblogs.com/zuowj/p/7501896.html

3、.Net基于RealProxy實作AOP : http://www.cnblogs.com/lflyq/p/6286925.html

4、Aspect-Oriented Programming : 使用 RealProxy 類進行面向方面的程式設計 : https://msdn.microsoft.com/zh-cn/magazine/dn574804.aspx

關于 castle api 當中對于 <code>IInvocation</code>對象的解釋少了一些,這裡補充一下:

1、<code>invocation.Arguments</code>: 方法執行被攔截時的方法參數數組

2、<code>invocation.GenericArguments</code>: 被攔截方法的泛型參數類型數組,如果沒有就是null

3、<code>invocation.InvocationTarget</code>: 擷取目前執行的目标對象,例如:如果是class代理就是YourClassProxy,接口代理就是實作接口的對象執行個體,如果沒有則為null,也就是當使用xxxWithoutTarget的時候

4、<code>invocation.Method</code>:擷取代理對象的方法資訊,例如:如果是class代理的時候就是YourClass.YourMethod對象的MethodInfo且這個時候<code>invocation.Method == invocation.MethodInvocationTarget</code>;如果是interface代理就是接口對象的方法資訊,例如:ICall.Call 這個方法的MethodInfo資訊且這個時候<code>invocation.Method != invocation.MethodInvocationTarget</code>,因為<code>invocation.MethodInvocationTarget</code>是接口對應實作的目标對象的方法資訊,也就是例如:MyCall.Call 方法對應上面的 ICall 接口來說,當然也可以使用 WithoutTarget方式,這樣就會導緻 <code>invocation.MethodInvocationTarget==null</code>的情況

5、<code>invocation.MethodInvocationTarget</code>: 指向真正的目标對象的方法資訊MethodInfo,大緻可以根據第四點給出了說明

6、<code>invocation.Proxy</code> : 擷取攔截時的代理對象,例如:YourClassProxy(類代理) 或者 ICallProxy(接口代理) 對象

7、<code>invocation.ResultValue</code>: 擷取或者設定代理方法的傳回值

8、<code>invocation.TargetType</code>: 擷取真實目标對象的類型

9、<code>invocation.GetArgumentValue(int index);</code>: 通過index擷取參數值

10、<code>invocation.GetConcreteMethod();</code>: 同理第四點

11、<code>invocation.GetConcreteMethodInvocationTarget();</code>: 同理第五點

12、<code>invocation.Proceed();</code>: 調用下一個攔截器直到目标方法

13、<code>invocation.SetArgumentValue(int index, object value);</code>: 設定更改參數值通過下标

這裡部落客列舉除了攔截途中對象<code>IInvocation</code>的所有成員,大家在使用攔截器的時候可根據自己邏輯使用以上API到達要求!

如果覺得閱讀了部落客的小文覺得對您有幫助,您的點贊和評論都是對部落客最大的支援!!!

生活本身是很艱難,但是不能成為你不努力的借口!

Remember, Hope is a good thing, maybe the best of things and no good thing ever dies !