關於spring.net的面向切面編程 (Aspect Oriented Programming with Spring.NET)-使用工廠建立代理(Using the ProxyFactoryObj

本文翻譯自Spring.NET官方文檔Version 1.3.2git

受限於我的知識水平,有些地方翻譯可能不許確,可是我仍是但願個人這些微薄的努力能爲他人提供幫助。spring

侵刪。數組

若是你正在爲你的業務模型使用IoC容器——這是個好主意——你將會想使用某個 Spring.NET's AOP特定的IFactoryObject 的實現(要記住,一個工廠的實例提供了一個間接層,使這個工廠可以建立不一樣類型的對象—5.3.9節,「經過使用其餘類型和實例建立一個對象」)。app

一個基本的建立Spring.NET's AOP代理的方式是使用Spring.Aop.Framework.ProxyFactoryObject類。這種作法可以讓你能靈活配置切入點的順序,切入點的應用和將會應用到你的業務中的通知。然而,若是你不須要這種這種配置,有其餘的更簡便的方法生成代理。框架

代理基礎

和其餘的Spring.NET IFactoryObject 的實現同樣,ProxyFactoryObject引入了一箇中間層。若是你定義了一個ProxyFactoryObject實例,名字叫作foo,那麼對foo並非ProxyFactoryObject 這個代理對象的引用,而是ProxyFactoryObject這個實例的的GetObject() 方法產生的對象。這個方法會包裝目標對象而後產生一個AOP代理。ide

使用ProxyFactoryObject 或者其餘的IoC感知類建立AOP代理的最大好處就是咱們可使用IoC來管理通知和切入點。 這個是一個強大的特性,能夠完成其餘AOP框架不能實現的一些功能。例如,一個通知可能關聯到一個應用程序中全部實體(不只僅是AOP框架中的目標實體),這個得益於依賴注入帶來的可拔插性。post

代理工廠屬性

和Spring.NET中大多的IFactoryObject實現同樣,ProxyFactoryObject 也是一個能夠靈活配置的Spring.NET實體。它的配置主要用來:性能

  • 明確須要被代理的目標對象。
  • 明確哪些通知會被應用在代理中。

一些關鍵的屬性繼承自Spring.Aop.Framework.ProxyConfig 類:這個類是全部Spring.NET中AOP代理工廠的父類。一些關鍵的配置包括:測試

  • 是否直接代理對象(ProxyTargetType):若是目標類被直接代理的話,這個boolean值會設置成true,若是隻是代理這個目標類的接口的話,就設置成false。
  • 是否優化(Optimize):是否使用一種激進的優化方式來建立代理。除非你明白相關的AOP代理如何處理優化過程,否則請不要使用優化。這種優化的真正意義在於它可以經過權衡代理的生成事件和運行時的性能,針對性地產生代理。在某些特定的代理實現中,這個優化會被關閉;也會由於其餘配置屬性,好比說ExposeProxy而靜默關閉。
  • 是否凍結通知的改變(IsFrozen):代理工廠配置後,通知就不容許改變。默認爲false。
  • 是否暴露代理(ExposeProxy):當前的代理(CurrentProxy)能不能經過AopContext獲取,一旦被AopContext獲取就也能被目標對象得到(也能夠經過IMethodInvocation獲取而不須要AopContext )。若是一個目標對象須要得到它的代理而且ExposeProxy 被設置成true,目標對象就能使用AopContext.CurrentProxy屬性來獲取當前的代理。
  • AopProxyFactory:當生成代理的時候指定IAopProxyFactory的實現。提供了一個能夠定製化的生成方法:是否經過遠程代理(remoting proxies),IL代碼生成或者其餘方式生成代理。默認的實現是使用IL代碼來生成代理。

其餘在ProxyFactoryObject 類中定義的屬性包括:優化

    • 被代理接口(ProxyInterfaces):一個咱們要代理的類型名稱的字符串數組。
    • 切面名稱(InterceptorNames):被應用的IAdvisor,切面或者其餘通知名稱的字符串數組。它們的順序是很重要的,排在前面的會優先。列表中的第一個切面的方法調用會被第一個攔截(假設只是配置了通常方法攔截和前置通知)

這些名稱都是存在於當前容器中的對象名稱,同時也包括容器的全部父類包含的對象。你不能夠直接聲明容器外的源對象,由於這麼作會致使ProxyFactoryObject 忽略通知的單例設置。

  • 引入通知名稱(IntroductionNames):容器中能夠被用來看成目標的引入通知的實體名稱。若是經過名字關聯的實體沒有實現IIntroductionAdvisor 接口,它就會被傳入到默認的DefaultIntroductionAdvisor 構造器中,這樣這個實體全部的接口都會被引入的目標實體中。讓實體實現IIntroductionAdvisor 接口主要是爲了提供一個更好的層次讓你去控制是否想暴露特定接口和那些類型想被匹配。
  • 是否單例實現(IsSingleton):指定工廠是否返回一個代理的單例對象,不管GetObject() 被調用多少次。有一些IFactoryObject 提供這這種方法。這個配置的默認值是true。若是你想每個實例都產生一個代理,那麼就把IsSingleton設置成false,同時IsFrozen也要設置成false。若是你想使用一個有狀態的通知(stateful advice)——例如狀態的混入(stateful mixins)——你就須要使用原形通知(prototype advices )而且把IsSingleton設置成false。

代理接口

讓咱們看一個簡單的ProxyFactoryObject 的例子,這個例子包括:

  • 一個被代理目標實體「personTarget」。
  • 用來提供通知的IAdvisor和IInterceptor 接口。
  • 一個AOP代理實體定義,定義了目標對象(personTarget),要被代理的接口和要應用的通知。
 1 <object id="personTarget" type="MyCompany.MyApp.Person, MyCompany">
 2     <property name="name" value="Tony"/>
 3     <property name="age" value="51"/>
 4 </object>
 5 <object id="myCustomInterceptor" type="MyCompany.MyApp.MyCustomInterceptor, MyCompany">
 6     <property name="customProperty" value="configuration string"/>
 7 </object>
 8 <object id="debugInterceptor" type="Spring.Aop.Advice.DebugAdvice, Spring.Aop">
 9 </object>
10 <object id="person" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
11     
12     <property name="proxyInterfaces" value="MyCompany.MyApp.IPerson"/>
13     <property name="target" ref="personTarget"/>
14     <property name="interceptorNames">
15         <list>
16             <value>debugInterceptor</value>
17             <value>myCustomInterceptor</value>
18         </list>
19     </property>
20 </object>

要注意的是,InterceptorNames 屬性是一個字符串列表:位於當前上下文的切面名稱。通知器,攔截器,前置通知,返回後通知和拋出通知實體都能被應用進去。它們的順序是很重要的。

你可能會奇怪爲何這個列表不是實體的引用。其緣由是若是ProxyFactoryObject的singleton屬性設置成false,它必須可以針對每一個被代理的實體都返回獨立的代理實例。若是有任何的通知器是一個原型,一個獨立的實例須要被返回,因此沒有必要再從上下文獲取一個原型的實例,由於僅僅使用同一個引用是明顯不夠的。

上文中「person」對象的定義能夠用一個Iperson接口的實現來代替,以下:

IPerson person = (IPerson) factory.GetObject("person");

其餘在同一個IoC上下文的實體能夠表達一個強類型的依賴注入,和其餘普通的.NET實體同樣:

1 <object id="personUser" type="MyCompany.MyApp.PersonUser, MyCompany">
2     <property name="person" ref="person"/>
3 </object>

這裏例子中的PersonUser 類會暴露IPerson接口的一些屬性。AOP代理會被用來代替「真正的」person的實現。於是這個類型會是一個代理類型。它能夠轉換成一個IAdvised接口的實現(下文會討論)。

可使用內聯對象(inline object)來把目標和目標的代理的區別隱藏起來,就像下面這樣。(更多關於內聯對象的信息可查看5.3.2.3小節, 「Inner objects「) 只有ProxyFactoryObject 的定義是不同的;通知也包含在內,僅僅爲了完整性須要:

 1 <object id="myCustomInterceptor" type="MyCompany.MyApp.MyCustomInterceptor, MyCompany">
 2     <property name="customProperty" value="configuration string"/>
 3 </object>
 4 <object id="debugInterceptor" type="Spring.Aop.Advice.DebugAdvice, Spring.Aop">
 5 </object>
 6 <object id="person" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
 7     
 8     <property name="proxyInterfaces" value="MyCompany.MyApp.IPerson"/>
 9     <property name="target">
10       <!-- Instead of using a reference to target, just use an inline object -->
11       <object type="MyCompany.MyApp.Person, MyCompany">
12         <property name="name" value="Tony"/>
13         <property name="age" value="51"/>
14       </object>
15     </property>
16     <property name="interceptorNames">
17         <list>
18             <value>debugInterceptor</value>
19             <value>myCustomInterceptor</value>
20         </list>
21     </property>
22 </object>

這樣作的一個優勢是僅僅存在一個Person類型的對象:當咱們想防止應用程序上下文的使用者得到一個沒有被通知的對象的引用,或者想避免Spring IoC 的自動裝配形成的模棱兩可的定義的時候是很用用的。還有一個有爭議的優勢是若是這麼作,那麼ProxyFactoryObject 的定義是獨立完整的。然而有些時候可以從工廠中得到沒有被通知的目標對象也許也是一個好處:例如在一些特定的測試環境中。

一個實例一個代理方式實現應用通知

讓咱們來看一個經過ProxyFactoryObject配置代理對象的例子。

1  <!-- create the object to reference -->
2           <object id="RealObjectTarget" type="MyRealObject" singleton="false"/>
3           <!-- create the proxied object for everyone to use-->
4           <object id="MyObject" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
5           <property name="proxyInterfaces" value="MyInterface" />
6           <property name="isSingleton" value="false"/>
7           <property name="targetName" value="RealObjectTarget" />
8           </object>
9  

若是你使用一個原型來做爲目標你必須把TargetName屬性設置成你對象的名字/id而不能使用一個目標引用對象的屬性。這樣作接下來可以經過一個新的目標原型實例生成一個對應的新代理。

考慮上面的Spring.Net配置的時候,要注意ProxyFactoryObject 的IsSingleton 屬性是設置成false的。這就意味着每個代理對象都是獨一無二的。這樣你就可使用下面的方式去配置配置每個代理對象的通知類型:

1 // Will return un-advised instance of proxy object
2 MyInterface myProxyObject1 = (MyInterface)ctx.GetObject("MyObject"); 3 // myProxyObject1 instance now has an advice attached to it.
4 IAdvised advised = (IAdvised)myProxyObject1; 5 advised.AddAdvice( new DebugAdvice() ); 6 // Will return a new, un-advised instance of proxy object
7 MyInterface myProxyObject2 = (MyInterface)ctx.GetObject("MyObject");

代理類

當你想代理一個類,而不是一個或者多個接口的時候,應該怎麼辦呢?

想象一下上面的例子,若是不存在IPerson 接口,咱們要通知一個沒有實現任何接口的Person類。在這種狀況下,若是找不到任何實現的接口的話,ProxyFactoryObject 會代理全部的公共的虛方法和虛屬性。咱們能夠經過設置ProxyTargetType爲true來強制Spring.NET 去代理類而不是接口。

類的代理是經過在運行時產生一個目標的子類來實現的。Spring.NET 配置這個子類來委託對原目標對象方法的調用:這個子類被用來實現這種裝飾器模式,將通知織入。

類的代理應該對使用者透明。然而須要考慮一個重要問題:非虛的方法不能被通知,就像它們不能被重寫同樣。這個可能在一些一般不會把方法聲明成虛方法的類裏面有些限制。

精簡的代理定義

尤爲是在定義一個事務代理的時候,若是你沒有使用事務名稱空間,你最終可能會碰到不少相似的代理定義。使用父子對象定義,再加上一些內聯對象定義,可使得代理定義更加精簡。

首先,須要爲這個代理定義一個父對象模板:

1 <object id="txProxyTemplate"  abstract="true"
2             type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">
3         <property name="PlatformTransactionManager" ref="adoTransactionManager"/>
4         <property name="TransactionAttributes">
5             <name-values>
6                 <add key="*" value="PROPAGATION_REQUIRED"/>
7             </name-values>
8         </property>
9     </object>

這樣它沒法實例化,因此實際上配置還還沒有完成。接下來每一個須要被建立的代理都是一個子對象定義,這個定義把目標代理包裝成一個內聯對象,由於目標對象永遠不會被本身使用:

1 <object name="testObjectManager" parent="txProxyTemplate">
2         <property name="Target">
3             <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
4                 <property name="TestObjectDao" ref="testObjectDao"/>
5             </object>
6         </property> 
7 </object>

固然也能夠在子對象中重寫父類模板的屬性,就像下面的事務傳播配置(transaction propagation settings)案例:

 1 <object name="testObjectManager" parent="txProxyTemplate">
 2         <property name="Target">
 3             <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
 4                 <property name="TestObjectDao" ref="testObjectDao"/>
 5             </object>
 6         </property> 
 7         <property name="TransactionAttributes">
 8             <name-values>
 9                 <add key="Save*" value="PROPAGATION_REQUIRED"/>
10                 <add key="Delete*" value="PROPAGATION_REQUIRED"/>
11                 <add key="Find*" value="PROPAGATION_REQUIRED,readonly"/>
12             </name-values>
13         </property>
14 </object>

要注意,上面的例子咱們已經顯示地把父類對象定義成抽象,因此它其實是不能被實例化的。應用程序上下文(並非簡單的對象工廠)會默認預實例化(pre-instantiate )全部的單例。所以,要注意的是(至少對於單例對象),若是你有一個你想做爲一個模板的父對象,而且這個模板定義了一個特定的類,你必須保證將abstract 屬性設置成true,否則應用程序上下文會嘗試着去預實例化它。

代理機制

Spring在運行時經過使用類型構造器(TypeBuilder )的API建立AOP代理。

兩種代理類型會被生成,基於結構(composition based )或者基於繼承(inheritance based)。若是目標對象實現了至少一個接口那麼就會生成基於結構的代理,否則就會生成基於繼承的代理。

基於結構的代理是經過生成一個實現全部目標對象的接口的類型來實現。這個動態類真實的名字是一個相似於」GUID「的東西。經過一個私有字段來保存目標對象的引用,而後這個動態類型的實現會在調用目標類以前或者以後執行通知。

基於繼承的代理會生成一個直接繼承目標類型的動態類型。這樣就能夠在須要的時候直接追溯到目標類。要注意的是,兩種方式中的目標方法實現若是調用了目標對象中的其餘方法,這個方法是不會被通知的。想強制使用基於繼承的代理方式,你能夠設置ProxyFactory 的ProxyTargetType 屬性爲true,或者使用將XML的名稱控件裏的proxy-target-type 設置true。

在.NET 2.0中你能夠定義程序集的特性InternalsVisibleTo來使得程序集內部的接口或者類容許被特定的」友好「的程序集訪問。若是你須要在一個內部的類或者接口生成一個AOP代理,須要在AssemblyInfo文件中加入如下代碼:[assembly: InternalsVisibleTo("Spring.Proxy")] and [assembly: InternalsVisibleTo("Spring.DynamicReflection")]。

基於繼承的AOP配置(InheritanceBasedAopConfigurer)

在上文提到的基於繼承的代理有一個很大的限制,那就是全部的方法都要被聲明成虛方法。否則一些方法的調用會直接獲取私有的」目標「字段或者基類。winform對象就是這樣一個例子,它們的方法都不是虛方法。爲了解決這個問題,在1.2版本中引入了一個新的後處理機制(post-processing mechanism ),這種機制生成一個不包含私有的」目標「字段的代理類型。通知在調用基類方法以前直接被添加到方法體中。

想要使用這種新的基於繼承的代理,要在你的配置文件中聲明一個InheritanceBasedAopConfigurer實例,和一個IObjectFactoryPostProcessor,一個例子:

 1 <object type="Spring.Aop.Framework.AutoProxy.InheritanceBasedAopConfigurer, Spring.Aop">
 2   <property name="ObjectNames">
 3       <list>
 4           <value>Form*</value>
 5           <value>Control*</value>
 6       </list>
 7   </property>
 8   <property name="InterceptorNames">
 9       <list>
10           <value>debugInterceptor</value>
11       </list>
12   </property>
13 </object>
14 <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/>

這個配置風格和自動代理很類似,尤爲是在你想爲WinForm類添加通知的時候很好用。

經過代理工廠使用代碼建立AOP代理

在Spring.NET中也很容易地經過代碼來建立AOP代理。這樣你就不須要依賴Spring.NET 的IoC功能。

如下展現瞭如何爲一個目標對象建立代理,其中包含一個攔截器和一個通知器。目標對象實現的接口會被自動代理:

1 ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); 2 factory.AddAdvice(myMethodInterceptor); 3 factory.AddAdvisor(myAdvisor); 4 IBusinessInterface tb = (IBusinessInterface) factory.GetProxy();

第一步是構建一個Spring.Aop.Framework.ProxyFactory對象。你能夠像上面的例子同樣建立一個目標對象,或者在構造器中指定要被代理的接口。

你能夠添加攔截器或者通知器,而後配置它們。

ProxyFactory中也有一些方便的方法來容許你添加其餘的通知類型。AdvisedSupport 是ProxyFactory 和ProxyFactoryObject的基類。

配置被通知對象

不管你經過那種方式建立AOP代理,你均可以經過使用Spring.Aop.Framework.IAdvised 來配置它們。全部的AOP代理均可以轉換成這個接口類型,不管它實現了其餘什麼接口。這個接口包括如下的方法和屬性:

 1 public interface IAdvised  2 {  3     IAdvisor[] Advisors { get; }  4     
 5     IIntroductionAdvisor[] Introductions { get; }  6  
 7     void AddInterceptor(IInterceptor interceptor);  8  
 9     void  AddInterceptor(int pos, IInterceptor interceptor); 10     void AddAdvisor(IAdvisor advisor); 11     void  AddAdvisor(int pos, IAdvisor advisor); 12   
13     void AddIntroduction(IIntroductionAdvisor advisor); 14   
15     void  AddIntroduction(int pos, IIntroductionAdvisor advisor); 16     int IndexOf(IAdvisor advisor); 17     int IndexOf(IIntroductionAdvisor advisor); 18     bool RemoveAdvisor(IAdvisor advisor); 19     void RemoveAdvisor(int index); 20  
21     bool RemoveInterceptor(IInterceptor interceptor); 22     bool RemoveIntroduction(IIntroductionAdvisor advisor); 23  
24     void RemoveIntroduction(int index); 25     void ReplaceIntroduction(int index, IIntroductionAdvisor advisor); 26     bool ReplaceAdvisor(IAdvisor a, IAdvisor b); 27 }

通知器屬性會被加入到工廠的全部通知器,攔截器或者其餘通知類型返回一個IAdvisor接口。若是你添加了一個IAdvisor,返回的通知器就將會是你添加的對象。若是你天界了一個攔截器或者其餘的通知類型,Spring.NET將會在一個通知器中使用一個老是返回true的IPointcut 接口包裝它。所以若是你添加了一個IMethodInterceptor,通知器就會返回一個DefaultPointcutAdvisor和一個匹配全部類型和方法的IPointcut 接口。

AddAdvisor() 方法能夠用來添加任意的IAdvisor接口。一般這個會是一個泛型DefaultPointcutAdvisor,這個通知器能夠和任何的通知或者切入點使用(除了引入通知)。

通常來講,一個代理被生成以後也能夠添加或者移除通知器或者攔截器。惟一的限制就是不能添加或者移除引入通知,所以工廠中的現存代理的接口不會改變。(你能夠從工廠獲取一個新的代理來避免這個問題)。

However you create AOP proxies, you can manipulate them using the Spring.Aop.Framework.IAdvised interface. Any AOP proxy can be cast to this interface, whatever other interfaces it implements. This interface includes the following methods and properties:

The Advisors property will return an IAdvisor for every advisor, interceptor or other advice type that has been added to the factory. If you added an IAdvisor, the returned advisor at this index will be the object that you added. If you added an interceptor or other advice type, Spring.NET will have wrapped this in an advisor with a IPointcut that always returns true. Thus if you added an IMethodInterceptor, the advisor returned for this

index will be a DefaultPointcutAdvisor returning your IMethodInterceptor and an IPointcut that matches all types and methods.
The AddAdvisor() methods can be used to add any IAdvisor. Usually this will be the generic DefaultPointcutAdvisor, which can be used with any advice or pointcut (but not for introduction).


By default, it's possible to add or remove advisors or interceptors even once a proxy has been created. The only restriction is that it's impossible to add or remove an introduction advisor, as existing proxies from the factory will not show the interface change. (You can obtain a new proxy from the factory to avoid this problem.) It's questionable whether it's advisable (no pun intended) to modify advice on a business object in production, although there are no doubt legitimate usage cases. However, it can be very useful in development: for example, in tests. I have sometimes found it very useful to be able to add test code in the form of an interceptor or other advice, getting inside a method invocation I want to test. (For example, the advice can get inside a transaction created for that method: for example, to run SQL to check that a database was correctly updated, before marking the transaction for roll back.)
Depending on how you created the proxy, you can usually set a Frozen flag, in which case the IAdvised IsFrozen property will return true, and any attempts to modify advice through addition or removal will result in an AopConfigException. The ability to freeze the state of an advised object is useful in some cases: For example, to prevent calling code removing a security interceptor.

相關文章
相關標籤/搜索