.net 上下文

22.15 .NET上下文
22.15.1 簡介
我們已經看到AppDomain能夠在運行期間實現類 型、安全和異常層面的隔離。然而還存在着比AppDomain更加細粒度的用於存儲.NET對象的實體。一個.NET AppDomain能夠包含多個這種叫做.NET上下文的實體。在本章,只要不會與COM+上下文概念發生混淆,我們就把它們稱作上下文,這兩個概念是完 全不同的(COM+上下文在8.8節介紹)。某些作者使用託管上下文(managed context)或者執行上下文(execution context)來指代.NET上下文,而非託管上下文則指代COM+上下文。所有.NET對象都存在於上下文中,每個AppDomain中至少存在一個 上下文。這個上下文稱爲AppDomain的默認上下文,它在AppDomain創建的時候就創建了。圖22-7總結了它們之間的關係。

圖22-7 進程、AppDomain和上下文
上下文的概念使得攔截對象的調用成爲可能。攔截調用意味着我們可以對每個輸入或輸出參數做一個或多個轉換。這些轉換是通過消息接收器來完成的。這樣做主要是爲了讓客戶端在調用對象的方法時不會察覺到調用被攔截了以及在方法處理的前後對參數做了轉換。
22.15.2 上下文綁定和上下文靈活對象
根 據上一節的內容,上下文可以看作AppDomain中一個包含對象和消息接收器的區域。對上下文裏的對象的調用會轉換成可以被消息接收器攔截和處理的消 息。我們知道要把調用轉換成消息,必須通過透明代理這個中介。而且,僅當對象是MarshalByRefObject的子類的實例並被其所在的 AppDomain以外的實體調用時,CLR纔會爲它創建透明代理。這裏,我們希望對所有調用使用消息接收器機制,即使是那些同一個AppDomain中 的實體所執行的調用。這個時候我們就需要用到System.ContextBoundObject類了。繼承自ContextBoundObject的類 的實例同樣僅能由透明代理訪問。此時,甚至這個類的方法中所使用的this引用也是透明代理而不是對這個對象的直接引用。讓 ContextBoundObject類繼承自MarshalByRefObject是合理的,因爲它很好地強調了該類的特性——它告訴CLR這個類將會 通過透明代理使用。
ContextBoundObject的子類的實例被視爲是上下文綁定的。沒有繼承自 ContextBoundObject的類的實例則被視爲是上下文靈活的。上下文綁定的對象永遠在其上下文中執行。只要不是遠程對象,上下文靈活的對象總 是在執行這個調用的上下文中執行。圖22-8說明了以上內容。

圖22-8 上下文綁定的對象與上下文靈活的對象
22.15.3 上下文attribute和上下文屬性
下面我們將介紹用於在上下文層面注入消息接收器的技術。首先介紹上下文attribute和上下文屬性的概念。
1.上下文attribute
上 下文attribute是指應用在上下文綁定類上的.NET attribute。上下文attribute類實現了System. Runtime.Remoting.Contexts.IContextAttribute接口。上下文綁定的類可以應用多個上下文attribute。 在這個類的對象創建期間,這個類的每個上下文attribute都要判斷這個對象的創建者所在的上下文是否適用。下面這個方法執行了這個操作。

只要其中一個上下文attribute返回false,CLR就必須創建一個新的上下文來容納這個新的對象。這樣,每個上下文attribute都可以在這個新的上下文中注入一個或多個上下文屬性。這些注入可以通過以下方法實現。

2.上下文屬性
上 下文屬性是實現System.Runtime.Remoting.Contexts.IContextProperty接口的類的實例。每個上下文可以包 含多個屬性。上下文屬性在上下文創建的時候通過上下文attribute注入。一旦每個上下文attribute都注入了它的屬性,那麼就會爲每個屬性調 用下面的方法。此後就無法在這個上下文中注入另外的屬性了。

然後,CLR通過調用下面的方法判斷新的上下文能否滿足每個屬性。

每個上下文屬性都有一個通過Name屬性定義的名稱,如下所示:

上下文中承載的對象的方法可以通過調用下面的方法訪問上下文屬性。

這一點很有意思,上下文中的對象通過它們所在的上下文的屬性可以共享信息並訪問服務。不過,上下文屬性的主要作用並不在於此。上下文屬性的主要作用在於向相關上下文中的消息接收器區域注入消息接收器。
對 消息接收器區域的介紹是下一小節的主題。在此之前,讓我們先通過示例演示上下文attribute和上下文屬性的概念。對於那些讀過信道那節的讀者,上下 文attribute的角色相當於在信道中注入提供程序的配置文件。同樣地,上下文屬性的角色相當於消息接收器提供程序。
3.使用上下文attribute和屬性的示例
下 面的程序定義了LogContextAttribute類和LogContextProperty類。應用了LogContext- Attribute的類的所有實例都將承載在具有LogContextProperty類型的屬性的上下文中。於是,該實例就可以訪問這個屬性提供的服務 了。這個服務允許通過調用LogContextProperty. Log(string)方法向文件寫入一個字符串。文件名是LogContextAttribute的參數。這樣,我們就可以讓每個類都擁有一個配置文 件。當應用了LogContextAttribute的類的新實例創建好後,boolLogContextAttribute. IsContextOK(Context)方法將判斷調用構造函數的實體所在的上下文是否包含帶有相同文件名的LogContextAttribute的 實例。如果不是,則會創建一個新的上下文。LogContextAttribute.GetPro- pertiesForNewContext(IConstructionCallMessage ctor)方法創建一個LogContextProperty的實例。在這個方法返回時,CLR自動把新的屬性注入新的上下文。下面是程序的代碼。
例22-33



該程序輸出:

obj1和obj3這兩個對象位於同一個上下文中,因爲obj3是在obj1所在的上下文中創建的。
22.15.4 消息接收器區域
消 息接收器區域有以下四種:服務器區域、對象區域、信使區域和客戶端區域。要理解區域概念,必須考慮上下文綁定的對象是否被位於另一個上下文中的實體調用。 這個實體可以是一個靜態方法或者另一個對象。在我們關於區域的討論中,我們把這個實體所在的上下文稱爲調用方上下文,而把被調用對象所在的上下文稱爲目標 上下文。目標上下文中的每個屬性都可以在這些區域中注入消息接收器。
• 注入服務器區域的消息接收器攔截所有從另一個上下文發往目標上下文中所有對象的調用消息。於是,每個目標上下文有一個服務器區域。
• 注入對象區域的消息接收器攔截所有從另一個上下文發往目標對象中特定對象的調用消息。於是,上下文中每個對象會有一個對象上下文。
• 注 入信使區域的消息接收器攔截所有從另一個上下文發往目標對象中特定對象的調用消息。於是,上下文中的每個對象都有一個信使區域。信使區域和對象區域的不同 點是信使區域位於調用方上下文而不是包含對象的目標上下文。我們使用信使區域把調用方上下文的信息傳遞給目標上下文的消息接收器。
• 注入客戶端區域的消息接收器攔截所有從目標上下文發往位於其他上下文的對象的調用消息。於是,每個目標上下文有一個客戶端區域。
圖22-9說明了區域的概念。目標上下文包含名爲OBJ1和OBJ2的兩個對象。我們選擇在目標上下文中放置兩個對象而不是一個是爲了更好地說明對象區域和信使區域是在對象層面與消息的攔截關聯起來的,而服務器區域和客戶端區域則是在上下文層面與消息的攔截關聯起來的。
我 們在每個區域中放置了兩個自定義消息接收器是爲了更好地說明一個區域能包含零個、一個或多個消息接收器。具體地說,所有自定義消息接收器都通過目標上下文 的屬性注入區域,即使這個區域不屬於目標上下文。因爲上下文屬性類是可以自定義的,所以我們可以選擇哪個區域必須注入消息接收器。
你可能注意到,每個區域包含一個用於通知CLR退出區域的系統終結器接收器,不過不需要太在意它。
當調用方上下文和目標上下文處在同一個AppDomain中時,CLR會使用mscorlib.dll中CrossContextChannel內部類的實例作爲信道。這個實例會使得當前線程的Context屬性發生切換。圖22-9展示了這些實例。

圖22-9 上下文和區域
22.15.5 使用區域的示例
我們想通過例22-34的程序說明以下內容。
• 自 定義上下文attribute(CustomDisplayContextAttribute類)的代碼在上下文中注入了自定義上下文屬性 (CustomDisplayContextProperty類)。這個上下文屬性在目標上下文的對象區域、服務器區域和客戶端區域以及調用方上下文的信 使區域中注入了自定義消息接收器(CustomDisplayMessageSink類)。
• 消息接收器的行爲可以通過傳遞給上下文attribute的參數修改。這裏,這個參數是一個布爾值,它指示消息接收器是否必須在控制檯上輸出某些東西。
• 消 息接收器通過上下文屬性注入這四個區域。CustomDisplayContextAttribute上下文attribute確保爲Foo類的每個實例 創建上下文。我們首先創建Foo的實例,然後我們在這個實例上調用一個方法並穿越信使區域、服務器區域和對象區域的消息接收器。要穿越客戶端區域的消息接 收器,我們創建了Foo的第二個實例,然後讓第一個實例調用它。於是,這裏有了三個上下文:執行Main()方法的上下文(ContextID = 0)、包含Foo的第一個實例的上下文(ContextID = 1)和包含Foo的第二個實例的上下文(ContextID = 2)。
• 上下文內部調用不會觸發所有這些消息接收器的調用。這在Foo的第一個實例調用自身時可以看到。
• 在區域(這裏是客戶端區域)中注入了多個消息接收器。
• CLR在區域中注入消息接收器的時間。我們很清楚的看到這個時間依賴於區域的類型。
程序如下:
例22-34






執行程序後的輸出如下所示。


22.15.6 調用上下文
可 以在運行在調用方上下文中的消息接收器與運行在目標上下文中的消息接收器之間傳遞信息。我們使用這個技術主要是爲了把調用方上下文的信息傳遞給目標上下文 (例如調用方上下文支持某些屬性)。我們把這項功能稱爲調用上下文。儘管它的名字中含有“上下文”這三個字,但是這項功能可以用在任何消息接收器中,包括 那些不在上下文中的。
爲此,必須首先定義一個實現 System.Runtime.Remoting.Messaging.ILogicalThreadAffinative接口的類來描述要傳遞的信息。 在運行在調用方上下文(在信使區域或者客戶端區域裏)中的消息接收器的代碼中,我們必須把這個類的實例附加到表示調用的消息上。在運行在目標上下文(在對 象區域或者服務器區域裏)中的消息接收器的代碼中,必須把這個類的實例從表示調用的消息中分離出來。這些操作是通過 IMethodMessage.LogicalCallContext屬性完成的。
下面的代碼演示如何運用該技術。
例22-35


我 們在前一節看到,在客戶端區域和信使區域中注入消息接收器是發生在調用對象的構造函數之後。客戶端區域和信使區域中的消息接收器無法把信息附加到表示構造 函數調用的消息上,而服務器區域的消息接收器卻可能依然試圖從表示構造函數調用的消息中分離出這個信息。在這種情況下,我們可以在上下文 attribute的GetPropertiesForNewContext()方法中附加這個信息。
例22-36

22.16 小結
22.16.1 激活對象的四種方式
我們已經看到,激活對象的方式有四種:WKO single-call模式、WKO singleton模式、CAO和對象發佈。下面這張表總結了這些模式之間的主要差異。

22.16.2 截獲消息
我們用了本章的一半專門來介紹如何攔截表示調用的消息以及爲何這樣做。圖22-10簡要總結了各種可能的攔截層面。

圖22-10 攔截消息
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章