.NET 4 實踐 - 使用dynamic和MEF實現輕量級的AOP組件 (4)
借花獻佛
前面我們介紹了構成DynamicAspect絕大部分的類,為了讓Aspect能夠自動實施到目標對象,需要提供一種動態裝載Aspect機制,在設計DynamicAspect的最初版本,筆者使用配置文件,通過在配置文件提供一個自定義的DynamicAspect節,然后在那里用XML元素來描述每個Aspect的類型以及要編織的目標類型,程序在運行過程中,通過配置文件獲取Aspect的信息,然后將相應的Aspect裝入我們設計的對象鏈。配置文件有著其它方法所沒有的優勢,那就是真正的做到動態的編織,也就是說當需求發生變化的時候,只要修改配置文件,不需要重新編譯主程序或者原有的源代碼文件,甚至不用暫停正在運行的程序。但是采用配置文件也有它的缺點,由于配置文件采用XML,不便檢查配置文件中的值是否正確,錯誤傾向性比較大,而且要求應用程序開發人員和維護人員都必須熟悉DynamicAspect。經過仔細的評估,后決定改用MEF來實現自動查找和自動匹配功能,而且MEF內建的對象生命期管理機制還可以簡化DynamicAspect的架構,下面我們也簡要的介紹一下MEF(詳細的介紹見MEF官方文檔):
MEF的全稱是 Managed Extensibility Framework(托管的可擴展框架),最初看到它的時候,那還是在兩年前MSDN站點的Code Gallery,一年前它移居Codeplex,一不留神它成了.NET類庫的永久公民。據說是MS內部各個開發團隊在開發各種的項目中各自為政,每個團隊都有自己一套擴展框架,這些擴展框架都特定于各自的應用,很難被其他團隊重用,于是就有了MEF出面來一統混亂的天下,這也算是一種自然吧?,F如今各團隊是不是正忙著把整合MEF呢?
不知道有多少人知道MAF(Managed Add-in Framework),MAF在.NET 3.5的時候被加入到類庫中,如果你在項目中添加System.AddIn.dll的引用,你就可以看到System.AddIn的名稱空間,在VS2008的MSDN文檔中你可以找到它的介紹以及使用MAF的例子。MAF和MEF都是為支持開發可擴展應用程序而被加入到類庫的,MAF著重處理應用程序的隔離和組件的裝載和卸載,而MEF則更多的關注可發現、可擴展和可移植方面。MEF的工作原理可以總結成三個詞:Export (導出)、Import (導入)和Composite(組合)。在MEF中,參與導入和導出的對象成為Part(部件),在應用中,MEF需要一個宿主(Host)來存放它的根容器,為了能夠發現導出和導入的部件,MEF設計了幾個不同的Catalog類來指定要查找的部件的范圍,比如說TypeCatalog類指定要查找的部件的類型,而AssemblyCatalog則表示要查找的部件所在的程序集,DirectoryCatalog指定要查找的部件所在的文件目錄,這些Catalog可以通過一個AggregateCatalog類組合在一起。除了MEF可以通過catalog來自動發現部件之外,在具體的應用中,也可以顯式的調用容器的CompositePart擴展方法把部件加入到容器中。讓我們結合代碼來看看DynamicAspect是如何借用MEF這朵花的。
首先是宿主的選擇,從目前能夠找到的MEF例子,MEF的宿主都是放在主運行程序這一邊,考慮到主程序未必是一個采用了MEF應用,所以決定在DynamicAspect中創建一個自己的容器對象,該容器既可以作為根容器,也可以看做是一個子容器被嵌入到一個父容器中(如果有的話),在DynamicAspect中,要處理部件的類型就是IAspect接口,無論是導入還是導出。實例化一個容器部件的過程是在WeaverExtensiong這個靜態類中進行的:
public static class WeaverExtension
{
public static CompositionContainer Container = null;
static WeaverExtension()
{
AggregateCatalog catalog =new AggregateCatalog(
new DirectoryCatalog(@".\"),
new DirectoryCatalog(@".\", "*.exe"));
Container = new CompositionContainer(catalog);
}
…
}
這是借用了靜態類的特性,一個靜態類在程序集被裝入是就會被初始化,且靜態成員是全局唯一的。在這里,我們定義了一個MEF的CompositionContainer容器對象,該對象在程序集初始化的時候被實例化,我們在容器中指定MEF搜索程序集所在目錄的全部dll文件和exe文件,DirectoryCatalog的構造器第二個參數用來指定要匹配的文件,缺省情況下是*.dll。有朋友問了,MEF如何知道要查找的部件是IAspect類型呢?好問題,其實MEF不知道,它只是很忠實地查找所有施加了ExportAttribute或者ImportAttribute的類,并按照指定的匹配規則來進行匹配,這里的匹配不是指DyanmaicAspect中的Aspect匹配目標類型,而是指一個Export和一個Import部件的匹配。
讓我們回頭看一下AspectBase類的定義:
[InheritedExport(typeof(IAspect))]
public abstract class AspectBase : IAspect
{ … }
這里施加的特性是InheritedExport,如果是ExportAttribute的話,由于ExportAttribute被設計成不能施加到派生類,所有的派生類都需要聲明ExportAttribute,MEF的設計者充分考慮到這一點,為我們準備了InheritedExportAttribute,顧名思義InheritedExport就是可以沿著繼承鏈傳遞所施加的特性。
我們的Aspect就這樣被導出了。導入在哪里?不要著急,導入在WeavableObject類中定義:
[ImportMany(typeof(IAspect),
AllowRecomposition = true,
RequiredCreationPolicy= CreationPolicy.Any)]
public IEnumerable<IAspect> Aspects { get; set; }
看到了,這里也不是ImportAttribute,而是ImportManyAttribute,這是因為Import只是導入一個對象,而我們的IAspect可能有多個,所以就要用到ImportMany了,特性中的兩個參數都是自解釋的,AllowRecomposition表示在有新的類型導入時,重新對容器中的部件進行匹配。為了每次新的Aspect對象被導入的時候,及時裝配到AspectChain,我們為WeavableObject實現一個IPartImportsSatisfiedNotification,該接口只有一個方法:
public void OnImportsSatisfied()
{
var query = Aspects
.Where(a => a.IsMatch(typeof(T).FullName) && a.Enabled == true)
.OrderBy(a => a.Sequence);
foreach (var asp in query)
AddAspect(asp);
}
OnImportsSatisfied在導入滿足一次匹配的時候被觸發,方法代碼使用LINQ查詢過濾掉不能和當前目標相匹配或者Enabled等于false的Aspect。
AddAspect的方法如下:
public void AddAspect(IAspect aspect)
{
if (aspect != null)
{
lock (new object())
{
if (chain.FirstOrDefault(c =>
aspect.GetType().IsAssignableFrom(c.GetType())) == null)
chain.AddLast(aspect);
}
}
}
一個LINQ語句用來防止相同對象的重復裝配。上鎖是為了線程安全(該語句在新的更新中已經各改為:lock(((ICollection)chain).AynxRoot) )
DynamicAspect的故事到這里,似乎可以告一段落了,但故事并沒有結束。DynamicAspect還是一棵幼苗,需要在大家的關懷和呵護中成長。同時,筆者也鼓勵朋友們試著把DynamicAspect用在不同的場合,如果有什么問題和建議的話可以在這里回帖或者通過電子郵件反饋給我,先行謝過了。
最后要說明的是,DynamicAspect還在開發之中,還需要經受更多的考驗來證明它的價值,筆者會在適當的時候續寫DynamicAspect的成長歷程,希望屆時有更多的精彩奉獻個大家。
(全文完)
浙公網安備 33010602011771號