框架Spring.NET之面向切面(AOP)

     文章開頭咱們須要瞭解幾個概念,如:什麼是面向切面編程?它的發展及其優點是什麼?首先咱們來共同瞭解下什麼面向切面編程,又稱做AOP。 java

     AOP(Aspect-Oriented Programming)是對OOP的一種補充,它從一個不一樣於OOP的角度來看待程序的結構:OOP將應用程序分解爲一系列表現爲繼承關係的對象;AOP則把程序分解爲一系列方面(aspects)或者關注點(concerns)。AOP將諸如事務管理等原本橫向分佈在多個對象中的關注點進行了模塊化處理(這些關注點也常稱爲橫切(crosscutting)關注點)。 正則表達式

   AOP框架是Spring.NET的一個關鍵組件。Spring.NET的IoC容器與AOP框架是相互獨立的,二者徹底能夠不依賴對方而單獨使用,可是AOP作爲一個強大的中間件解決方案,完善了IoC容器的功能。 spring

  接下來,咱們須要瞭解下,AOP編程中的幾個概念: 編程


  • 方面(Aspect):對橫向分佈在多個對象中的關注點所作的模塊化。在企業應用中,事務管理就是一個典型的橫切關注點。Spring.NET將方面實現爲Advisor或攔截器(interceptor)。(按:Advisor是通知和切入點的組合,攔截器實際就是指通知,注意在本文檔中,通常會把環繞通知稱爲攔截器,而將其它類型的通知稱爲通知,這是由於環繞通知實現的是AopAlliance.Intercept.IMethodInterceptor接口,而其它通知類型實現的都是Spring.Aop命名空間下的通知接口。) c#

  • 鏈接點(Joinpoint):程序執行過程當中的一個點,例如對某個方法的調用或者某個特定異常的拋出均可以稱爲鏈接點。 緩存

  • 通知(Advice):AOP框架在某個鏈接點所採起的行爲。通知有多種類型,包括「環繞」通知,「前置」通知和「異常」通知等,後文將對通知類型進行討論。包括Spring.NET在內的不少AOP框架都把通知建模爲攔截器(interceptor),而且會維護一個"包圍"在鏈接點周圍的攔截器鏈。 框架

  • 切入點(Pointcut):指通知的應用條件,用於肯定某個通知要被應用到哪些鏈接點上。AOP框架應容許讓開發人員指定切入點,例如,可使用正則表達式來指定一個切入點。 模塊化

  • 引入(Introduction):向目標對象添加方法或字段的行爲。Spring.NET容許爲任何目標對象引入新的接口。例如,能夠利用引入讓任何對象在運行期實現IAuditable接口,以簡化對象狀態變化的跟蹤過程。(按:也稱爲mixin,混入) 性能

  • 目標對象(Target object):指包含鏈接點的對象。也稱爲被通知或被代理對象。(按:「被通知對象」實際是「被應用了通知的對象」,在譯文中,將advised object或proxied object統稱爲目標對象,這樣更爲統一) this

  • AOP代理(AOP proxy):由AOP框架在將通知應用於目標對象後建立的對象。在Spring.NET中,AOP代理是使用IL代碼在運行時建立的動態代理。

  • 織入(Weaving):將方面進行組裝,以建立一個目標對象。織入能夠在編譯期完成(例如使用Gripper_Loom.NET編譯器),也能夠在運行時完成。Spring.NET在運行時執行織入。

  • 各類通知類型包括:

    • 環繞通知(Around Advise):包圍(按:即在鏈接點執行的前、後執行)某個鏈接點(如方法調用)的通知。這是功能最強大的一種通知。環繞通知容許在方法調用的先後執行自定義行爲。它能夠決定是讓鏈接點繼續執行,仍是用本身的返回值或異常來將鏈接點「短路」。

    • 前置通知(Before Advise):在某個鏈接點執行以前執行,可是不具有阻止鏈接點繼續執行的能力(除非它拋出異常)。

    • 異常通知(Throws Advise):當方法(鏈接點)拋出異常時執行。Spring.NET的異常通知是強類型的(按:Spring.NET用標識接口來定義異常通知,異常通知的處理方法僅需遵循必定的命名規則,能夠用具體的異常類型聲明其參數,參見12.3.2.3節),因此,能夠在代碼中直接捕捉某個類型的異常(及其子類異常),沒必要從Exception轉型。

    • 後置通知(After returning Advise):在鏈接點正常執行完成後執行,例如,若是方法正常返回,沒有拋出異常時,後置通知就會被執行。

    Spring.NET內置了以上全部類型的通知。在應用時,應儘可能使用功能最少(只要對要實現的行爲來講是足夠的)的通知類型,這樣可簡化編程模型並減小出錯的可能。例如,若是隻需使用某個方法的返回值來更新緩存,那麼用後置通知就比環繞通知合適。由於,儘管環繞通知能夠完成一樣的功能,但在後置通知中不用象環繞通知那樣必須調用IMethodInvocation接口的Proceed()方法來容許鏈接點繼續執行,因此鏈接點老是能正常執行。(按:換句話說,由於環繞通知能夠控制鏈接點的繼續執行,因此若是沒有調用IMethodInvocation接口的Proceed()方法,鏈接點就會被「短路」;而使用後置通知就不存在這個問題)。

    切入點是AOP的關鍵概念,使AOP從根本上區別於舊的攔截技術。切入點使通知能夠獨立於OO的繼承層次以外。例如,一個聲明式事務處理的環繞通知能夠應用於不一樣對象的方法。切入點是AOP的結構性要素。

     Spring.NET AOP的功能

    Spring.NET的AOP框架徹底用C#實現。全部的織入工做都在運行時完成,不須要特殊的編譯過程。因爲在沒必要控制或修改程序集裝載的方式,也不依賴於非託管API,因此Spring.NET的AOP框架適用於任何CLR環境。

    目前,Spring.NET支持對方法調用的攔截。雖然也能夠在不破壞核心API的前提下加入對字段攔截的支持,但Spring.NET沒有實現這一功能。這源於一個有爭議的話題:字段攔截破壞了OO的封裝性。在應用開發中這並非明智之舉。

    Spring.NET爲切入點和不一樣的通知類型都提供了相應的類。在Spring.NET的類庫中,方面由Advisor對象來表示,而Advisor又由通知和切入點組成(切入點用於肯定將通知應用在哪些鏈接點上)。

    各類類型的通知分別是由IMethodInterceptor接口(由AOP聯盟定義的攔截器API,在AopAlliance.Intercept命名空間下)和Spring.Aop命名空間下的各個通知接口定義的。全部通知都必須(最終)實現AopAlliance.Aop.IAdvice接口(這是個標識接口,沒有定義任何成員),Spring.Aop命名空間下的IMethodInterceptor、IThrowsAdvice、IBeforeAdvice和IAfterReturningAdvice接口都擴展了IAdvice接口。咱們將在後文中討論它們。

    Spring.NET將AOP聯盟定義的java接口移植到了.NET平臺上。環繞通知必須實現AOP聯盟的AopAlliance.Intercept.IMethodInterceptor接口。在Java領域,AOP聯盟的接口已經獲得了普遍的應用,但在.NET領域,Spring.NET是目前惟一實現了這些接口的AOP框架。就短時間來講,這爲同時使用.NET和Java的開發人員提供了一個統一的編程模型;就長期而言,咱們也但願看到更多的.NET項目能採用AOP聯盟的接口。

    Spring.NET的AOP框架並非要象AspectJ(Aspect#)同樣全面支持AOP。但Spring.NET的AOP框架能夠爲.NET應用領域中許多應該用AOP來處理的問題提供很好的解決方案。

    通常咱們會將Spring.NET的AOP框架與IoC容器一塊兒使用。在IoC容器中,能夠象普通對象同樣來佈署AOP通知(也可使用強大的自動代理(autoproxying)功能);通知和切入點均可以由Spring.NET的IoC容器管理

    Spring.NET的AOP代理

    Spring.NET利用System.Reflection.Emit命名空間下的類在運行時動態建立IL代碼來生成AOP代理。這使得代理(的建立)很是高效,而且不受任何繼承層次的限制。

    在.NET中,實現AOP代理的另外一種方法是使用ContextBoundObject類和Remoting基礎框架。但這個方法不是太好,由於它要求被代理的類必須直接或間接繼承自ContextBoundObject。這個沒必要要的限制會影響到對象模型的設計,也沒法將AOP應用在設計者沒法直接控制的「第三方」類型上。同時,因爲上下文切換和Remoting基礎框架的開銷,所生成的代理也會比IL直接生成的代理慢得多。

    Spring.NET的AOP代理是很是「智能」的。在生成代理時,因爲代理的配置是能肯定的,因此生成的代理對象僅會在必要時才經過反射調用目標方法(即,當已有通知應用到目標方法時)。其餘狀況下,目標方法是直接調用的,避免了因反射調用而帶來的性能開銷。

    最後,Spring.NET的AOP代理永遠不直接返回目標對象的原始引用。若是檢測到目標對象的某個方法返回了自身的引用(好比用return this;),AOP代理會將返回值替換爲代理自身的引用。

    目前,AOP代理生成器使用對象的組合來轉發從代理到目標對象的調用,這和傳統Decorator模式的實現方式很類似。這表示被代理的類必須實現一或多個接口,讓類實現接口並非限制,而是在任什麼時候候都應該遵照的最佳編程方式。同時,這樣也會比讓類繼承ContextBoundObject更少侵入性,

    將來的版本會使用繼承來實現代理,這樣就能夠代理沒有實現任何接口的類,同時也能夠消除一些用組合方式沒法解決的遺留問題。

   經過對以上概念的瞭解,接下來,咱們須要經過實戰代碼,來進一步闡述AOP。首先簡單配置下Spring.NET框架吧。咱們能夠經過Nuget,來進行對Spring.NET的安裝引用,這裏,根據需求,咱們須要引用Spring.Core核心框。

rready工做完成後,接下來,繼續配置,在應用程序配置文件中,配置以下:


<configuration>
  <configSections>
    <!--Spring.NET-->
    <sectionGroup name="spring">
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    </sectionGroup>
    <!--日誌-->
    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
    </sectionGroup>
  </configSections>
下面在在App.config中進行配置的,咱們也能夠外部文件進行配置。
  <spring>
    <context>
      <!--容器配置-->
      <!--<resource uri="assembly://SpringApp/xmls/objects.xml"/>-->
    </context>
  </spring>
如下示例,是經過控制檯程序進行演示的。首先,咱們新建了一個objects.xml文件,用於配置應用程序中的對象.
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
         xmlns:aop="http://www.springframework.net/aop">
  <!--Target 定義老師-->
  <object id="teacher" type="SpringApp.Teacher,SpringApp"></object>
  <!--定義代理對象-->
  <object id="user" type="SpringApp.UserProxy,SpringApp"></object>
  <object id="log"  type="SpringApp.LogProxy,SpringApp"></object>
  <!--定義通知-->
  <object id="advisor" type="SpringApp.MyAdvisor,SpringApp">
    <property name="user" ref="user"></property>
  </object>
  <object id="logAdvisor" type="SpringApp.LogAdvisor,SpringApp">
    <property name="Log" ref="log"></property>
  </object>
  <!--定義切入點-->
  <object id="pointcut" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop">
    <property name="pattern" value="SpringApp.Teacher.Speak"/>
  </object>
  <!--切面-->
  <aop:config>
    <aop:advisor pointcut-ref="pointcut" advice-ref="advisor"/>
    <aop:advisor pointcut-ref="pointcut" advice-ref="logAdvisor"/>
  </aop:config>
</objects>
切入點,必須經過接口實現,所以,咱們首先建立了一個名爲ITeacher的接口。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/******************************************************************************************************************
 * 
 * 
 * 說 明:ITeacher (版本:Version1.0.0)
 * 做 者:李朝強
 * 日 期:2015/05/19
 * 修 改:
 * 參 考:http://my.oschina.net/lichaoqiang/
 * 備 注:暫無...
 * 
 * 
 * ***************************************************************************************************************/
namespace SpringApp
{
    /// <summary>
    /// 切入點:接口
    /// </summary>
    public interface ITeacher
    {
        /// <summary>
        /// 定義方法:鏈接點
        /// </summary>
        void Speak();

        /// <summary>
        /// 
        /// </summary>
        void Run();
    }
}
定義目標對象:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/******************************************************************************************************************
 * 
 * 
 * 說 明:Teacher對象(Target) (版本:Version1.0.0)
 * 做 者:李朝強
 * 日 期:2015/05/19
 * 修 改:
 * 參 考:http://my.oschina.net/lichaoqiang/
 * 備 注:暫無...
 * 
 * 
 * ***************************************************************************************************************/
namespace SpringApp
{
    /// <summary>
    /// Target對象: 教師
    /// </summary>
    public class Teacher : ITeacher
    {

        #region ITeacher 成員
        /// <summary>
        /// 被代理的方法必須經過接口實現  實現ITeacher接口 ============================================================= 切入點
        /// </summary>
        public void Speak()
        {
            //
            Console.WriteLine("I'm a teacher!");
        }

        /// <summary>
        /// 
        /// </summary>
        public void Run()
        {
            Console.WriteLine("Run:方法");
        }

        #endregion
    }
}
截止目前,實現切入點的接口、Target都有了,接下來,該定義通知:
using AopAlliance.Intercept;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/******************************************************************************************************************
 * 
 * 
 * 說 明:通知者(版本:Version1.0.0)
 * 做 者:李朝強
 * 日 期:2015/05/19
 * 修 改:
 * 參 考:http://my.oschina.net/lichaoqiang/
 * 備 注:暫無...
 * 
 * 
 * ***************************************************************************************************************/
namespace SpringApp
{
    /// <summary>
    /// 一、通知者Advice:通知描述了切面要完成的任務,同時還描述了什麼時候執行這個任務。
    /// </summary>
    public class MyAdvisor : IMethodInterceptor/*環繞通知*/
    {

        #region IMethodInterceptor 成員

        /// <summary>
        /// 要代理方法的對象
        /// </summary>
        public UserProxy user { set; get; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="invocation"></param>
        /// <returns></returns>
        public object Invoke(IMethodInvocation invocation)
        {

            //Target:
            ITeacher teacher = (Teacher)invocation.Target;

            //前置通知
            user.ComeBefore(teacher);

            object result = invocation.Proceed();

            //後置通知
            user.ComeAfter(teacher);

            return result;
        }

        #endregion
    }


    /// <summary>
    /// 二、日誌
    /// </summary>
    public class LogAdvisor : IMethodInterceptor
    {
        #region IMethodInterceptor 成員

        /// <summary>
        /// 
        /// </summary>
        public LogProxy Log { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="invocation"></param>
        /// <returns></returns>
        public object Invoke(IMethodInvocation invocation)
        {
            object result = invocation.Proceed();
            if (Log != null) { Log.Info("後置的方式:日誌"); }
            Console.WriteLine("通知日誌");
            return result;
        }
        #endregion
    }

    public class AuthenAdvisor : IMethodInterceptor
    {
        #region IMethodInterceptor 成員
        /// <summary>
        /// 
        /// </summary>
        /// <param name="invocation"></param>
        /// <returns></returns>
        public object Invoke(IMethodInvocation invocation)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

}
如下代碼只是輔助用的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/******************************************************************************************************************
 * 
 * 
 * 說 明: 代理(版本:Version1.0.0)
 * 做 者:李朝強
 * 日 期:2015/05/19
 * 修 改:
 * 參 考:http://my.oschina.net/lichaoqiang/
 * 備 注:暫無...
 * 
 * 
 * ***************************************************************************************************************/
namespace SpringApp
{
    public class UserProxy
    {
        /// <summary>
        /// id
        /// </summary>
        public long Id { get; set; }

        /// <summary>
        /// name
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="teacher"></param>
        public void ComeBefore(ITeacher teacher)
        {
            Console.WriteLine("ComeBefore:通知前置");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="teacher"></param>
        public void ComeAfter(ITeacher teacher) { Console.WriteLine("ComeBefore:通知後置"); }
    }
}
咱們來經過Spring.NET框架,進行演示:
using System;
using Spring.Context;
using Spring.Context.Support;
using Spring.Globalization;
using Spring.Objects.Factory;
using System.IO;
using System.Reflection;
using Spring.Aop.Framework;
/******************************************************************************************************************
 * 
 * 
 * 說 明: Spring.NET(版本:Version1.0.0)
 * 做 者:李朝強
 * 日 期:2015/05/19
 * 修 改:
 * 參 考:http://my.oschina.net/lichaoqiang/
 * 備 注:暫無...
 * 
 * 
 * ***************************************************************************************************************/
namespace SpringApp
{
    internal class Program
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        private static void Main(string[] args)
        {
            Console.WriteLine("*****************Spring.NET**************************");

            //<!--嵌入程序集方式,assembly://程序集名/項目名/objects.xml,更改屬性,始終複製,生成操做,嵌入的資源-->
            var xmls = new string[1] {/*"xmls/objects.xml"*/"assembly://SpringApp/SpringApp.config/objects.xml" };

            //應用程序容器
            IApplicationContext context = new XmlApplicationContext(xmls);
            IObjectFactory factory = context;


            //一、AOP
            //Aop();

            //二、代理
            Proxy(factory);

            Console.ReadLine();

        }

        /// <summary>
        /// 
        /// </summary>
        static void Proxy(IObjectFactory factory)
        {
            //使用代理
            ProxyFactory proxyFactory = new ProxyFactory(new Teacher());
            proxyFactory.AddAdvice(new LogAdvisor());
            ITeacher proxy = (ITeacher)proxyFactory.GetProxy();
            proxy.Speak();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="factory"></param>
        static void Aop(IObjectFactory factory)
        {
            //實例化對象
            ITeacher teacher = factory.GetObject("teacher") as ITeacher;
            teacher.Speak();

            //
            teacher.Run();
        }
    }
}

 

關於演示代碼:http://www.oschina.net/code/snippet_584165_53274

相關文章
相關標籤/搜索