.NET裏簡易實現AOP

 

.NET裏簡易實現AOP

前言

在MVC的過濾器章節中對於過濾器的使用就是AOP的一個實現了吧,時常在工作學習中遇到AOP對於它的運用可以說是很熟練了,就是沒想過如果自己來實現的話是怎麼實現的,性子比較犟硬是沒想明白,茶不思飯不想的,主要問題就是卡在了怎麼能攔截用戶調用,如果可以解決了這個問題AOP的實現也就引刃而解了,爲此在網上看了一些文章解決了這個問題,在這裏和大家分享一下。

 

AOP概述

AOP的作用就是橫切關注點,然後將分離後的關注點已面的形式來呈現,這是概念性的說法,舉個列子來說明吧。

比如說有個處理過程是計算提交訂單中的貨品總額,然後想在這個過程中執行之前記錄一下數據或者是執行一些必要的操作。

比如說記錄日誌,然後是選擇記錄日誌的方式,是選擇存本地文件還是存庫,又或者是選擇了存本地文件後選擇數據的存儲介質(XML格式、文本格式、加密格式、序列化格式等等)。

這只是其中的一個點,比如說還有驗證等等其它一些方面的關注點。

圖1

圖2

從圖1、圖2中我們就可以看出AOP的目的,就是將日誌記錄、驗證、性能監測這些關注點從一個執行過程中分離了出來,讓彼此不再有關係以及和計算總額的關係。在此可以把分離出來的關注點封裝,已“面”的形式展現出來,這樣的情況下也使得這些“面”可以在其它地方複用。

 

AOP的實現

1攔截的基礎實現

在前言中說到在.NET中實現AOP技術難點就是在攔截的那一塊,看到一些大神的文章利用remoting中的管道技術來實現信息攔截,我們先來了解一下怎麼利用remoting來實現攔截的。

圖3

圖3就是大概示意出了利用remoting攔截信息的一個示意圖,很突兀的插了個圖可能不太好理解,我們還是通過代碼配合圖文來粗淺的解說一下吧。

代碼1-1

    [AOPWriter]
    public class MyContextObject : ContextBoundObject
    {
        public void WriterLine(string meg)
        {
            Console.WriteLine("這是方法執行中" + meg);
        }
    }

代碼1-1中的MyContextObject類型繼承自上下文綁定類ContextBoundObject對象,繼承過後就表示MyContextObject類型需要強制綁定上下文意思就是說它需要在一個特性環境的上下文中,對於沒有繼承ContextBoundObject類型的類型被稱之爲靈活對象,它們的誕生是在默認的當前上下文中。

這裏爲什麼要說到這些內容呢?因爲這樣才能創建新的上下文,然後當前上下文對於MyContextObject的調用都是屬於遠程調用(在remoting裏跨越了上下文邊界的所有調用都應該叫遠程調用,不管服務端在哪),只有這樣才能利用remoting中的消息管道來進行消息攔截。

那麼是在什麼時候創建新的上下文的呢?在MyContextObject類型定義的上面,有個AOPWriter特性類型,我們先來看下它的定義,示例代碼1-2.

代碼1-2

    [AttributeUsage(AttributeTargets.Class)]
    public class AOPWriterAttribute : Attribute, IContextAttribute
    {

        public void GetPropertiesForNewContext(IConstructionCallMessage msg)
        {
            msg.ContextProperties.Add(new AOPContextProperty());
            
        }

        public bool IsContextOK(System.Runtime.Remoting.Contexts.Context ctx, IConstructionCallMessage msg)
        {
            return false;
        }
    }

在代碼1-2中我們看到AOPWriterAttribute類型實現了IContextAttribute類型的接口,IContextAttribute類型的定義如下,示例代碼1-3.

代碼1-3

    public interface IContextAttribute
    {
        // 摘要:
        //     在給定消息中將上下文屬性返回給調用方。
        //
        // 參數:
        //   msg:
        //     將上下文屬性添加到的 System.Runtime.Remoting.Activation.IConstructionCallMessage。
        [SecurityCritical]
        void GetPropertiesForNewContext(IConstructionCallMessage msg);
        //
        // 摘要:
        //     返回一個布爾值,指示指定上下文是否滿足上下文屬性的要求。
        //
        // 參數:
        //   ctx:
        //     當前上下文屬性檢查所依據的上下文。
        //
        //   msg:
        //     構造調用,需要依據當前上下文檢查其參數。
        //
        // 返回結果:
        //     如果傳入的上下文一切正常,則爲 true;否則爲 false。
        [SecurityCritical]
        bool IsContextOK(Context ctx, IConstructionCallMessage msg);
    }

代碼1-3中的定義同代碼1-2中AOPWriterAttribute類型的實現一樣,首先在上下文綁定對象進行實例化的時候系統默認的會調用IsContextOK()方法來判斷當前執行實例化過程所在的上下文是否滿足於上下文綁定對象的要求,這裏我們在代碼1-2中的實現是返回的false,意思就是當前上下文並不滿足於MyContextObject類型所需要的,這是系統會去調用IContextAttribute中的GetPropertiesForNewContext()方法用於向新建上下文中添加自定義的上下文屬性,也就是實現了IContextProperty接口類型的類型對象,在普通的運用中我們可以在自定義的上下文屬性中設置一些邏輯操作,以便在新建上下文中使用,對於這部分的示例可以去看我的《.Net組件程序設計之上下文》篇幅。

對於代碼1-2中AOPContextProperty類型的定義我們也來看一下,示例代碼1-4.

代碼1-4

    /// <summary>
    /// 上下文成員屬性
    /// </summary>
    public class AOPContextProperty : IContextProperty,IContributeServerContextSink
    {

        public void Freeze(Context newContext)
        {

        }

        public bool IsNewContextOK(Context newCtx)
        {
            
            return true;
        }

        public string Name
        {
            get { return "ContextService"; }
        }

        /// <summary>
        /// 提供的服務
        /// </summary>
        /// <param name="meg"></param>
        public void WriterMessage(string meg)
        {
            Console.WriteLine(meg);
        }

        public IMessageSink GetServerContextSink(IMessageSink nextSink)
        {
            AOPWriterSink megSink = new AOPWriterSink(nextSink);
            return megSink;
        }
}

在代碼1-4中上下文屬性對象添加了個WriterMessage()方法,也就是上面所說的在上下文中的所有對象都可獲取上下文屬性中提供的邏輯操作。Name屬性表示上下文屬性名稱,這個要是唯一的,在獲取上下文屬性就是根據這個Name屬性值來獲取的,這個下面的消息接收器示例中會有演示。

對了這裏上下文屬性還實現了IContributeServerContextSink類型,示例代碼1-5.

代碼1-5

    // 摘要:
    //     在遠程處理調用的服務器端上的上下文邊界上分配偵聽接收器。
    [ComVisible(true)]
    public interface IContributeServerContextSink
    {
        // 摘要:
        //     將第一個接收器放入到目前爲止組成的接收器鏈中,然後將其消息接收器連接到已經形成的鏈前面。
        //
        // 參數:
        //   nextSink:
        //     到目前爲止組成的接收鏈。
        //
        // 返回結果:
        //     複合接收器鏈。
        [SecurityCritical]
        IMessageSink GetServerContextSink(IMessageSink nextSink);
    }

在上下文屬性的實現中GetServerContextSink()將自定義的消息接收器添加到了新建上下文的消息接收器鏈的前端,這是一點非常重要我們AOP的實現主要依賴於自定義消息接收器中對於調用函數信息的攔截。

圖4

對於消息接收器,是要實現IMessageSink的,代碼1-6.

代碼1-6

    public interface IMessageSink
    {
        // 摘要:
        //     獲取接收器鏈中的下一個消息接收器。
        //
        // 返回結果:
        //     接收器鏈中的下一個消息接收器。
        //
        // 異常:
        //   System.Security.SecurityException:
        //     直接調用方通過接口引用進行調用,它沒有基礎結構權限。
        IMessageSink NextSink { get; }
        [SecurityCritical]
        IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink);
        [SecurityCritical]
        IMessage SyncProcessMessage(IMessage msg);
    }

在代碼的定義可以看到喲給NextSink的屬性,用以鏈接在管道中的下個消息接收器並且已這樣的形式形成消息接收器鏈(單向鏈表?職責鏈模式?),對於AsyncProcessMessage()方法暫且不去聊它,SyncProcessMessage()方法就可以理解爲是調用的遠程對象所執行的函數。來看下,示例代碼1-7.

代碼1-7

    public class AOPWriterSink : IMessageSink
    {
        private IMessageSink m_NextSink;
        public AOPWriterSink(IMessageSink nextSink)
        {
            m_NextSink = nextSink;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            return null;
        }

        public IMessageSink NextSink
        {
            get { return m_NextSink; }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {


            IMethodCallMessage callMessage = msg as IMethodCallMessage;
            if (callMessage.MethodName == "WriterLine")
            {
                Context context = Thread.CurrentContext;
                AOPContextProperty contextWriterService =
                    context.GetProperty("ContextService") as AOPContextProperty;
                if (callMessage == null)
                {
                    return null;
                }
                IMessage retMsg = null;
                if (contextWriterService != null)
                {
                    contextWriterService.WriterMessage("方法調用之前");
                }
                retMsg = m_NextSink.SyncProcessMessage(msg);
                contextWriterService.WriterMessage("方法調用之後");
                return retMsg;
            }
            else
            {
                return m_NextSink.SyncProcessMessage(msg);
            }
        }
    }

在實例化綁定上下文對象的時候或者是調用定上下文對象的方法的時候都會調用SyncProcessMessage()方法,在SyncProcessMessage()方法中我們根據IMessage消息對象來獲取當前遠程對象執行方法的名稱(對應代碼1-1中的對象的函數名稱),隨之獲取當前上下文屬性,利用上下文屬性中的邏輯操作來進行攔截後的操作。來看下測試代碼結果如圖5,示例代碼1-8.

代碼1-8

        static void Main(string[] args)
        {
            MyContextObject myContextObject = new MyContextObject();
            myContextObject.WriterLine("Test");
            Console.ReadLine();
        }

圖5

 

2消息接收器的執行過程

這個小節我們來說明一下消息接收器的執行過程,示例還是接着上個小節的內容,需要再次定義套上面示例中的內容,代碼近乎相同爲了更清楚的說明所以示例出來。來看代碼定義,示例代碼2-1.

代碼2-1

    [AttributeUsage(AttributeTargets.Class)]
    public class AOPTestAttribute : Attribute, IContextAttribute
    {
        public void GetPropertiesForNewContext(IConstructionCallMessage msg)
        {
            msg.ContextProperties.Add(new AOPContextPropertyTest());
           
        }
        public bool IsContextOK(System.Runtime.Remoting.Contexts.Context ctx, IConstructionCallMessage msg)
        {
            return false;
        }
    }

首先還是上下文綁定對象的標識特性定義,用處上節中說過了,再來看對應的上下文屬性定義,示例代碼2-2.

代碼2-2

    public class AOPContextPropertyTest : IContextProperty, IContributeServerContextSink
    {

        public void Freeze(Context newContext)
        {

        }

        public bool IsNewContextOK(Context newCtx)
        {

            return true;
        }

        public string Name
        {
            get { return "ContextServiceTest"; }
        }

        /// <summary>
        /// 提供的服務
        /// </summary>
        /// <param name="meg"></param>
        public void WriterMessage(string meg)
        {
            Console.WriteLine(meg);
        }

        public IMessageSink GetServerContextSink(IMessageSink nextSink)
        {
            AOPWriterSinkTest megSink = new AOPWriterSinkTest(nextSink);
            return megSink;
        }
    }

這裏上下文屬性的定義不同於上面的內容是對於屬性Name值的修改以及在設置消息接收器鏈的實現中重新設置了新定義的消息接收器,我們看一下新消息接收器的定義,示例代碼2-3

代碼2-3

     public class AOPWriterSinkTest : IMessageSink
    {
        private IMessageSink m_NextSink;
        public AOPWriterSinkTest(IMessageSink nextSink)
        {
            m_NextSink = nextSink;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            return null;
        }

        public IMessageSink NextSink
        {
            get { return m_NextSink; }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {


            IMethodCallMessage callMessage = msg as IMethodCallMessage;
            if (callMessage.MethodName == "WriterLine")
            {
                Context context = Thread.CurrentContext;
                AOPContextPropertyTest contextWriterService =
                    context.GetProperty("ContextServiceTest") as AOPContextPropertyTest;
                if (callMessage == null)
                {
                    return null;
                }
                IMessage retMsg = null;
                if (contextWriterService != null)
                {
                    contextWriterService.WriterMessage("方法調用之前TEST");
                }
                retMsg = m_NextSink.SyncProcessMessage(msg);
                contextWriterService.WriterMessage("方法調用之後TEST");
                return retMsg;
            }
            else
            {
                return m_NextSink.SyncProcessMessage(msg);
            }
        }
    }

在代碼2-3中SyncProcessMessage()方法中的實現也有所改變,對於獲取上下文屬性的參數修改爲2-2中定義的屬性名稱了。修改代碼1-1的內容,修改爲代碼2-4.

代碼2-4

    [AOPWriter]
    [AOPTest]
    public class MyContextObject : ContextBoundObject
    {
        public void WriterLine(string meg)
        {
            Console.WriteLine("這是方法執行中" + meg);
        }
    }

最後我們看一下結果圖6.

圖6

有的朋友可能疑問了,爲什麼AOPWriter在AOPTest之前而執行的結果明顯的是AOPTest部分的內容先執行了,這確實跟執行關係有很大的關係,在系統首先執行的時候會先設置AOPWriter部分所對應的消息接收器到新建上下文中消息接收器鏈的前端,隨後在設置AOPTest部分的消息接收器的時候又是重複的執行上述的操作了,所以AOPTest部分的接收器排在了前面,所以先執行了。看示意圖7所表示的。

圖7

看到這裏想必大家已經清楚的知道了消息接收器的設置過程了,但是對消息接收器鏈的執行過程並不清楚,我們再橫向的看一下消息接收器鏈執行的時候是個什麼樣的過程,如圖8.

圖8

在起初的AOPWriterSinkTest消息接收器中執行完攔截的Before操作時,會調用AOPWriterSinkTest消息接收器中的SyncProcessMessage()將消息對象往下面的消息器接收器中傳遞,到最後AOPWriterSink也執行完Before操作時再次向下傳遞的時候沒有發現消息接收器了,便會調用遠程對象所需執行的方法,在方法執行完畢後執行AOPWriterSink中的After操作,在AOPWriterSink的After操作執行完畢後消息也會隨着起初AOPWriterSinkTest消息接收器中的SyncProcessMessage()返回,返回到了AOPWriterSinkTest消息接收器中,之後再執行它的After操作,這時的結果就如同圖6中所演示的那樣。

 

3 AOP的概念轉換

在上面的小節中是講解說明了利用remoring的技術來進行消息攔截,以及所用的詞彙都是remoting中的類型詞彙,在本節將會對上面的內容進行抽象,並且已AOP的概念來描述這些類型。

首先我們會對上節中實現了IContextAttribute類型的標識上下文對象進行抽象,其實也就是對AOP中關注點(切面)的抽象,來看代碼定義3-1.

代碼3-1

using System.Runtime.Remoting;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;

namespace FrameWork.AOP.Achieve.AOPBasics
{
    [AttributeUsage(AttributeTargets.Class)]
    public abstract class AOPAttribute : Attribute, IContextAttribute
    {
        public AOPAttribute() { }
        public abstract AOPProperty CreateContextProperty();

        public void GetPropertiesForNewContext(IConstructionCallMessage msg)
        {
            AOPProperty contextProperty = CreateContextProperty();
            msg.ContextProperties.Add(contextProperty);
        }

        public bool IsContextOK(Context ctx, IConstructionCallMessage msg)
        {
            return false;
        }
    }
}

代碼3-1中定義和上面近似相同,只不過把需要添加的到切面中的屬性(上下文屬性)的實現是通過CreateContextProperty()方法來實現的,並且CreateContextProperty()方法定義爲抽象的由實現類來提供具體的實現,這樣做的好處可以讓實現更加的靈活添置更多的可擴展點。

在對關注點(切面)進行抽象後下面要對切面中的屬性進行抽象,這裏我感覺它的作用在於兩點,第一點是功能上的是用於連接抽象關注點和具體關注點實現的類別,第二點就是用於細化關注點,也就是第一點中說到的一個關注點中可能有N種的類別,可能這裏這樣說大家有點不理解什麼點不點面不面的亂七八糟的概念,我這裏舉個例子,比如說在上面小節中我們對一個完成的執行過程進行了橫切,把日誌、性能監測等關注點分離出原執行過程,然而在日誌這個關注點中,可能會存在普通日誌、性能日誌、錯誤日誌等,對於關注點的抽象和關注點的抽象可以很好的解決這一點,也就是對關注點再次的抽象,下面我們看一下關注點屬性的代碼定義,示例代碼3-2

using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;

namespace FrameWork.AOP.Achieve.AOPBasics
{
    public abstract class AOPProperty : IContextProperty, IContributeServerContextSink
    {
        public AOPProperty() { }
        public void Freeze(Context newContext)
        {
            
        }

        public bool IsNewContextOK(Context newCtx)
        {
            return true;
        }

        public string Name
        {
            get { return GetName(); }
        }

        protected virtual string GetName()
        {
            return "AOP";
        }

        protected abstract IMessageSink CreateAOPAspect(IMessageSink nextSink);

        public IMessageSink GetServerContextSink(IMessageSink nextSink)
        {
            AOPAspect aopAspect = CreateAOPAspect(nextSink) as AOPAspect;
            aopAspect.AOPPropertyName = Name;
            return (IMessageSink)aopAspect;
        }
    }
}

同樣的在代碼3-2中,我們將具體關注點執行類型(並不是最終執行的地方)的生成同樣是放到了實現具體關注點中的實現了,並且把抽象具體關注點的Name屬性值設置爲GetName(),GetName()方法的定義也是可以由實現具體關注點來定義。下面我們再來看一下具體關注點執行類型的定義,示例代碼3-3

代碼3-3

using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
using FrameWork.AOP.Achieve.Config;

namespace FrameWork.AOP.Achieve.AOPBasics
{
    public abstract class AOPAspect : IMessageSink
    {
        private IMessageSink _NextSink;

        private string _AOPPropertyName;

        public string AOPPropertyName
        {
            get { return _AOPPropertyName; }
            set { _AOPPropertyName = value; }
        }

        public AOPAspect(IMessageSink nextsink)
        {
            _NextSink = nextsink;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            return null;
        }

        public IMessageSink NextSink
        {
            get { return _NextSink; }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            IMethodCallMessage callmsg = msg as IMethodCallMessage;
            if (callmsg == null)
            {
                return null;
            }
            IMessage resultMsg = null;
            BeforeAchieve();
            resultMsg = _NextSink.SyncProcessMessage(msg);
            AfterAchieve();
            return resultMsg;
        }

        protected virtual void BeforeAchieve()
        {

        }

        protected virtual void AfterAchieve ()
        {

        }
    }
}

我們在代碼3-3中可以看到具體關注點執行類型的定義,並且把真正的執行包含在了具體關注點執行類型中(真正的執行是指攔截後的具體操作BeforeAchieve和AfterAchieve方法),這個是不妥的,我們會在下文中說到,並且會把它分離出去。

現在我們來看一下上面所說概念的對應關係,如圖9

圖9

對於關注點和具體關注點的對應關係是1對N的關係,而具體關注點和具體關注點執行類型的關係是N對N的。

現在我們來測試一下上面所示例的代碼內容,會發現暴露的問題。

首先我們實現關注點,示例代碼3-4.

代碼3-4

using FrameWork.AOP.Achieve.AOPBasics;

namespace FrameWork.AOP.Test.Log
{
    public class AOPLog:AOPAttribute
    {
        public override AOPProperty CreateContextProperty()
        {
            return new AOPLogProperty();
        }
    }
}

之後我們再實現一個具體關注點,示例代碼3-5

代碼3-5

using FrameWork.AOP.Achieve.AOPBasics;
using System.Runtime.Remoting.Messaging;

namespace FrameWork.AOP.Test.Log
{
    public class AOPLogProperty : AOPProperty
    {
        protected override IMessageSink CreateAOPAspect(IMessageSink nextSink)
        {
            return new AOPLogAspect(nextSink);
        }

        protected override string GetName()
        {
            return "AOPLog";
        }
    }
}

隨之再實現一個具體關注點執行類型,示例代碼3-6

代碼3-6

using FrameWork.AOP.Achieve.AOPBasics;
using System.Runtime.Remoting.Messaging;

namespace FrameWork.AOP.Test.Log
{
    public class AOPLogAspect:AOPAspect
    {
        public AOPLogAspect(IMessageSink nextSink)
            : base(nextSink)
        { }

        protected override void BeforeAchieve()
        {
            Console.WriteLine("MethodBefore");
        }
        protected override void AfterAchieve()
        {
            Console.WriteLine("MethodAfter");
        }
    }
}

我們再看一下測試用例,示例代碼3-7

   [AOPLog]
    public class TestInstance : ContextBoundObject
    {
        public void TestMethod()
        {
            Console.WriteLine("MethodAction……");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            TestInstance testInstance = new TestInstance();
            testInstance.TestMethod();
            Console.ReadLine();
        }
    }

最後我們看下測試結果,如圖10.

圖10

從這裏我們可以發現一個問題,就是對於信息的攔截上面的定義是不能具體區分的,以至於在遠程對象實例化的時候,連構造函數都執行了一遍攔截,這是我們不希望看到的,我們希望可以對此進行控制,第二個問題就是具體用於執行攔截的操作是不應該包含在具體關注點執行類型中的。

針對這兩個問題我們要做出修改,第一通過配置信息來對信息攔截做控制,希望攔截我們所想要攔截的信息,第二將具體的執行操作從具體關注點執行類型中分離出來,通過配置信息來操作。

在做具體修改之前我們再看一下上面所有概念的關係,如圖11

圖11

這裏的配置信息所要包含信息要有如下幾點:

第一 需要有我們要攔截信息的標識

第二 所對應的自定義執行的類型

第三 攔截信息的類型

現在我們再來看一下圖11經過對象化抽象過後的示意圖,圖12

圖12

我們先從執行過程的最末端開始實現,也就是攔截後所要做的具體操作,來看示例代碼3-8

代碼3-8

using System.Runtime.Remoting.Messaging;

namespace FrameWork.AOP.Achieve.AOPCustomAchieve
{
    public interface IBeforeAchieve
    {
        void BeforeAchieve(IMethodCallMessage callMsg);
    }
    public interface IAfterAchieve
    {
        void AfterAchieve(IMethodReturnMessage returnMsg);
    }
    public abstract class AOPInterceptAchieve:IBeforeAchieve,IAfterAchieve
    {
        public abstract void BeforeAchieve(IMethodCallMessage callMsg);

        public abstract void AfterAchieve(IMethodReturnMessage returnMsg);
    }

}

對於代碼3-8不用說什麼了,很明瞭。下面我們要對配置文件進行實現,示例代碼3-9

代碼3-9

using FrameWork.AOP.Achieve.AOPCustomAchieve;

namespace FrameWork.AOP.Achieve.Config
{
    public enum InterceptAchieveType
    {
        Before,
        After,
        All
    }
    public class Config
    {
        private List<string> _MethodNameList;

        public List<string> MethodNameList
        {
            get { return _MethodNameList; }
        }

        private InterceptAchieveType _InterceptActhieveType;

        public InterceptAchieveType InterceptActhieveType
        {
            get { return _InterceptActhieveType; }
            set { _InterceptActhieveType = value; }
        }

        private AOPInterceptAchieve _AOPInterceptAchieve;

        public AOPInterceptAchieve AOPInterceptAchieve
        {
            get { return _AOPInterceptAchieve; }
            set { _AOPInterceptAchieve = value; }
        }

        public Config()
        {
            _MethodNameList = new List<string>();
            _InterceptActhieveType = InterceptAchieveType.All;
        }
        public Config(List<string> methodNameList, InterceptAchieveType interceptActhieveType)
        {
            _MethodNameList = methodNameList;
            _InterceptActhieveType = interceptActhieveType;
        }

        public void AddMethodName(string methodName)
        {
            if (string.IsNullOrEmpty(methodName))
            {
                throw new ArgumentNullException("methodName");
            }
            _MethodNameList.Add(methodName);
        }

        public void SetInterceptActhieveType(InterceptAchieveType interceptAchieveType)
        {
            _InterceptActhieveType = interceptAchieveType;
        }

        public void SetAOPInterceptAchieve(AOPInterceptAchieve aopInterceptAchieve)
        {
            if (aopInterceptAchieve == null)
            {
                throw new ArgumentNullException("aopInterceptAchieve");
            }
            _AOPInterceptAchieve = aopInterceptAchieve;
        }
    }
}

這裏的配置類可以用配置文件來表示,可以表示爲一個節點中所包含的信息,不過在這裏我是出於簡便的考慮,省去了從配置文件讀取信息後轉化爲對象的這麼一個過程。在代碼3-9中定義了一個枚舉類型用於設置攔截的操作類型,然後就是這個配置所要對應的攔截方法的名稱集合以及具體執行操作的這麼一個自定義操作抽象基類。

最後我們看一下處理配置信息類的配置信息處理引擎的定義,示例代碼3-10。

代碼3-10

using FrameWork.AOP.Achieve.AOPCustomAchieve;
using System.Runtime.Remoting.Messaging;

namespace FrameWork.AOP.Achieve.Config
{
    public class ConfigRelationExecution
    {
        private static Dictionary<string, Config> _ConfigRelation;

        public static Dictionary<string, Config> ConfigRelation
        {
            get 
            {
                if (_ConfigRelation == null)
                {
                    _ConfigRelation = new Dictionary<string, Config>();
                }
                return _ConfigRelation;
            }
        }

        public static void ActionConfigByAspectName(string aspectname, string methodname, InterceptAchieveType interceptAchieveType, IMessage msg)
        {
            if (_ConfigRelation.ContainsKey(aspectname))
            {
                Config config = _ConfigRelation[aspectname];
                if (config.MethodNameList.Contains(methodname))
                {
                    if (config.AOPInterceptAchieve != null)
                    {
                        if (config.InterceptActhieveType == InterceptAchieveType.All)
                        {
                            InterceptAchieve(interceptAchieveType, config.AOPInterceptAchieve, msg);
                        }
                        else if (config.InterceptActhieveType == interceptAchieveType)
                        {
                            InterceptAchieve(interceptAchieveType, config.AOPInterceptAchieve, msg);
                        }
                    }
                }
            }
        }

        private static void InterceptAchieve(InterceptAchieveType interceptAchieveType, AOPInterceptAchieve aopInterceptAchieve,IMessage msg)
        {
            if (interceptAchieveType == InterceptAchieveType.Before)
            {
                IBeforeAchieve beforeAchieve = aopInterceptAchieve as IBeforeAchieve;
                if (beforeAchieve != null)
                {
                    beforeAchieve.BeforeAchieve((IMethodCallMessage)msg);
                }
            }
            else
            {
                IAfterAchieve beforeAchieve = aopInterceptAchieve as IAfterAchieve;
                if (beforeAchieve != null)
                {
                    beforeAchieve.AfterAchieve((IMethodReturnMessage)msg);
                }
            }
        }
    }
}

在代碼3-10配置信息處理類中,我設置了ConfigRelation屬性,這個屬性的作用是用來保存具體關注點執行類型和配置信息的對應關係的,對於ActionConfigByAspectName()方法和InterceptAchieve()方法也就是一個邏輯判斷的操作。根據當前具體關注點的名稱來做一些判斷。

現在我們修改一下代碼3-3中的內容,修改爲代碼3-11

代碼3-11

using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Messaging;
using FrameWork.AOP.Achieve.Config;

namespace FrameWork.AOP.Achieve.AOPBasics
{
    public abstract class AOPAspect : IMessageSink
    {
        private IMessageSink _NextSink;

        public AOPAspect(IMessageSink nextsink)
        {
            _NextSink = nextsink;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            return null;
        }

        public IMessageSink NextSink
        {
            get { return _NextSink; }
        }
        private string _AOPPropertyName;

        public string AOPPropertyName
        {
            get { return _AOPPropertyName; }
            set { _AOPPropertyName = value; }
        }
        public IMessage SyncProcessMessage(IMessage msg)
        {
            IMethodCallMessage callmsg = msg as IMethodCallMessage;
            if (callmsg == null)
            {
                return null;
            }
            IMessage resultMsg = null;
            ConfigRelationExecution.ActionConfigByAspectName(_AOPPropertyName, callmsg.MethodName, InterceptAchieveType.Before, callmsg);
            resultMsg = _NextSink.SyncProcessMessage(msg);
            ConfigRelationExecution.ActionConfigByAspectName(_AOPPropertyName, callmsg.MethodName, InterceptAchieveType.After, resultMsg);
            return resultMsg;
        }
    }
}

在這之後再修改下代碼3-6的內容,刪除掉兩個重寫基類的方法。

最後我們還是要實現一個定義攔截的操作,來看示例代碼3-12

代碼3-12

using System.Runtime.Remoting.Messaging;
using FrameWork.AOP.Achieve;
using FrameWork.AOP.Achieve.AOPCustomAchieve;

namespace FrameWork.AOP.Test.Log.Achieve
{
    public class LogWriterAchieve:AOPInterceptAchieve
    {
        public override void BeforeAchieve(IMethodCallMessage callMsg)
        {
            if (callMsg == null)
            {
                return;
            }
            Console.WriteLine(callMsg.MethodName + "—Before—" + this.GetType().Name);
        }

        public override void AfterAchieve(IMethodReturnMessage returnMsg)
        {
            if (returnMsg == null)
            {
                return;
            }
            Console.WriteLine(returnMsg.MethodName + "—After—" + this.GetType().Name);
        }
    }
}

最後我們來看下測試代碼,示例3-13

代碼3-13

class Program
    {
        static void Main(string[] args)
        {
            Config config = new Config();
            config.AddMethodName("TestMethod");
            config.InterceptActhieveType = InterceptAchieveType.Before;
            config.AOPInterceptAchieve = new Log.Achieve.LogWriterAchieve();
            ConfigRelationExecution.ConfigRelation.Add("AOPLog", config);
            TestInstance testInstance = new TestInstance();
            testInstance.TestMethod();
            Console.ReadLine();
        }
    }

這裏可以修改config.InterceptActhieveType的值依次是Before、After、All,顯示的結果如下圖。

還有很多測試用例就不一一的列舉了,不過這裏還要提一句的就是利用remoting來實現的AOP是不能滿足正常運用的有個弊端,因爲在遠程對象方法內部調用的是遠程對象內部的另一個方法時,攔截到的只能是調用的方法,被調用的則不行。

到這裏對於AOP的講解實現已經說完了,謝謝大家的閱讀。

作者:金源

發佈了74 篇原創文章 · 獲贊 1 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章