1. 程式人生 > >使用PostSharp在.NET平臺上實現AOP(轉)

使用PostSharp在.NET平臺上實現AOP(轉)

出處:https://www.cnblogs.com/leoo2sk/archive/2010/11/30/aop-postsharp.html

摘要

本文首先介紹AOP(面向方面程式設計)的相關概念及理論,然後介紹如何使用PostSharp框架在.NET平臺上實現AOP,最後對PostSharp的機制及AOP的優劣進行一個簡單的分析。

AOP(Aspect-Oriented Programming)

AOP的基本定義及作用

根據維基百科的定義,“AOP(Aspect-Oriented Programming)是一種將函式的輔助性功能與業務邏輯相分離的程式設計泛型(programming paradigm),其目的是將橫切關注點(cross-cutting concerns)分離出來,使得程式具有更高的模組化特性。AOP是

面向方面軟體開發(Aspect-Oriented Software Development)在編碼實現層面上的具體表現(面向方面軟體開發AOSD是一個囊括面向方面分析、面向方面設計和麵向方面程式設計等一系列概念的完整工程系統——筆者注)。AOP包括程式設計模型和具體用於實現AOP的框架兩部分。”

下面對上文提到的定義進行一些解釋。

在當前大多數支援面向物件的程式語言中(例如C#,Java等),函式(Function)是表述程式功能的最小單元,而一個函式的程式碼層面往往同時含有核心業務邏輯和輔助性功能。核心業務邏輯指一個函式本身主要要實現的業務功能,例如在一個線上電子商務系統中,“PlaceOrder”函式其核心業務邏輯是“下訂單”,而“UpgradeMember”函式其核心業務是“提升一個會員的等級”。但是,一個函式除了核心業務程式碼外,往往還會有一些輔助性功能程式碼,如事務處理、快取處理、日誌記錄、異常處理等等。而這些輔助性功能一般會存在於大多數甚至所有業務函式中,即形成AOSD中所謂的橫切關注點,如圖1所示。

image

圖1、橫切關注點示意

橫切關注點的存在,造成了如下幾個問題。

  • 程式碼編寫和維護困難。

橫切關注點不僅橫切各個函式,還可能在不同類甚至不同工程間橫切,使得同一個輔助功能(如事務處理)分散到各處,如果要增加新函式時要時刻注意別忘了新增所有需要的橫切程式碼。另外,如果需要對其進行修改,則需要到所有被橫切的函式中修改,維護難度極大。

  • 引入大量冗餘程式碼

由於同一個輔助性功能的程式碼幾乎是完全相同的,這樣就會令同樣的程式碼在各個函式中出現,引入了大量冗餘程式碼。

  • 降低程式碼質量

橫切關注點令核心業務程式碼和輔助性程式碼雜糅糾纏在一起,破壞了業務函式程式碼的純淨性和函式職責的單一性,引入了大量繁雜的程式碼和結構,使得程式碼質量下降。

所以,AOP的核心思想就是在編寫程式碼時將橫切關注點分離出來,形成單獨的模組,單獨編寫和維護,不再分散到各業務函式,使得業務函式僅包含核心業務程式碼,從而解決以上問題。而在程式編譯或執行時,通過某些手段(下文介紹)令獨立的橫切關注點程式碼可以與核心業務程式碼自動協作執行,完成本身需要的功能。

AOP相關術語

方面(Aspect)

一個Aspect指上文提到的橫切關注點在程式設計中的具體實現,它包含一個橫切關注點所需要實現的具體輔助功能。具體到程式碼中,Aspect可能會被實現為一個Class,一個Function或一個Attribute。

連線點(Join Point)

連線點指一個業務函式程式碼中的一個位置或時機,在這個位置或時機允許Aspect程式碼插入執行。常見的連線點有進入函式執行業務程式碼前時、執行完全部業務程式碼離開函式前、當有異常發生在異常處理程式碼執行前等等。

織入(Weaving)

織入指將指定的Aspect程式碼插入指定連線點,使得橫切程式碼與業務程式碼交合在一起。

連線模型(JPM, Join Point Model)

JPM主要是面向方面語言(如AspectJ)或面向方面框架的語義模型。主要包含以下三點:有哪些可用連線點,如何指定連線點以及如何織入。

AOP的實現方式

一般來說,在純編譯型語言(如C、C++)等語言中實現AOP非常困難,必須完全從編譯器角度入手。本文主要討論託管型語言(如C#,Java)中AOP的實現方式。AOP的主要實現方式有編譯時AOP和執行時AOP兩種,下面分別介紹。

編譯時AOP(靜態織入)

編譯時AOP的實現思想是給語言的編譯器做擴充套件,使得在編譯程式的時候編譯器將相應的Aspect程式碼織入到業務程式碼的指定連線點,輸出整合的結果。圖2是編譯時AOP的示意圖(以.NET平臺為例)。

image

圖2、編譯時AOP示意圖

如圖2所示,當使用靜態織入時,帶AOP擴充套件的編譯器會在編譯時將Aspect程式碼織入業務函式程式碼,形成整合後的IL,然後交由CLR執行。

執行時AOP(動態織入)

執行時AOP如圖3所示。image

圖3、執行時AOP的示意圖

如圖3所示,執行時AOP的實現方式是將擴充套件新增到執行虛擬機器而不是編譯器。Aspect和業務程式碼分別獨立編譯,而在執行時由虛擬機器在必要時進行織入。

PostSharp

PostSharp簡介

PostSharp是一個用於在.NET平臺上實現AOP的框架,是我比較常用的一個AOP框架,官方網站為http://www.sharpcrafters.com。目前最新版本為2.0,但是2.0的license不再免費,因此個人建議下載1.5版,同時下文都是基於PostSharp1.5。

PostSharp使用靜態織入方式實現AOP,其連線點非常豐富,使用簡單,而且相對其它一些.NET平臺上的AOP框架來說,PostSharp較為輕量級,但是功能卻一點也不遜色,因此是我比較喜歡的一個AOP框架。更多關於PostSharp的介紹請參看其官方網站。

另外使用PostSharp與其它框架不太一樣的是一定要下載安裝包安裝,只引用類庫是不行的,因為上文說過,AOP框架需要為編譯器或執行時新增擴充套件。

使用PostSharp實現AOP示例

這一節將通過一個例子演示如何使用PostSharp在.NET平臺上實現AOP。這個例子將通過AOP為核心業務函式增加日誌記錄功能。

新建專案

首先新建一個C#的WinForm應用程式,如圖4所示,這裡將工程命名為“PostSharpExample”。

image

圖4、新建專案

編寫核心業務函式

首先我們來編寫核心業務。當然這裡不存在真正的業務,我們只是模擬一個而已。將要模擬的核心業務是預定房間。先構建一個如圖5所示的簡單UI。

image

圖5、UI介面

下面我們為專案增加一個“CoreBusiness”類,並在其中新增“Subscribe”方法。程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 using  System;   namespace  PostSharpExample {      public  class  CoreBusiness      {          public  static  void  Describe(string memberName, string roomNumber)          {              System.Windows.Forms.MessageBox.Show(String.Format( "尊敬的會員{0},恭喜您預定房間{1}成功!" , memberName, roomNumber), "提示" );          }      } }

可以看到,這裡Subscribe方法僅僅是輸出一個提示框。當然,在真正專案中這種輸出型程式碼不應該寫在業務邏輯中,這裡這樣寫主要是為了演示方便。然後,我們在Form1中呼叫Subscribe業務方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using  System; using  System.Windows.Forms;   namespace  PostSharpExample {      public  partial class  Form1 : Form      {          public  Form1()          {              InitializeComponent();          }            private  void  BTN_SUBSCRIBE_Click(object sender, EventArgs e)          {              if  (!String.IsNullOrEmpty(TXB_NAME.Text.Trim()) && !String.IsNullOrEmpty(TXB_ROOM.Text.Trim()))                  CoreBusiness.Describe(TXB_NAME.Text.Trim(), TXB_ROOM.Text.Trim());              else                  MessageBox.Show( "資訊不完整" , "提示" );          }      } }

執行程式就可以看到相應的效果:

image

圖6、預定房間成功演示效果

使用AOP增加日誌記錄功能

現在加入我們要為程式新增日誌功能,記錄業務函式的執行情況。這裡我們假定需要將日誌記錄到純文字檔案中,首先我們完成日誌記錄工具類,LoggingHelper。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using  System; using  System.IO;   namespace  PostSharpExample {      class  LoggingHelper      {          private  const  String _errLogFilePath = @ "log.txt" ;            public  static  void  Writelog(String message)          {              StreamWriter sw = new  StreamWriter(_errLogFilePath, true );              String logContent = String.Format( "[{0}]{1}" , DateTime.Now.ToString( "yyyy-MM-dd hh:mm:ss" ), message);              sw.WriteLine(logContent);              sw.Flush();              sw.Close();          }      } }

如果不使用AOP,則我們要為包括Subscribe在內的每一個方法在核心業務程式碼的前後插入日誌記錄程式碼(Writelog),我們看看使用PostSharp如何將這種橫切關注點分離出來。因為要使用PostSharp,所以要先新增對PostSharp庫檔案的引用,安裝過PostSharp後,在系統可引用項中會多出“PostSharp.Laos”、“PostSharp.Public”和“PostSharp.AspNet”,這裡我們做的是Winform程式,所以只需新增對“PostSharp.Laos”和“PostSharp.Public”的引用即可。

下面我們就要寫Aspect了,PostSharp的Aspect是使用Attribute實現的,下面是我實現的日誌記錄Aspect程式碼。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using  System; using  PostSharp.Laos;   namespace  PostSharpExample {      [Serializable]      [AttributeUsage(AttributeTargets.Method, AllowMultiple = true , Inherited = true )]      public  sealed class  LoggingAttribute : OnMethodBoundaryAspect      {          public  string BusinessName { get; set; }            public  override void  OnEntry(MethodExecutionEventArgs eventArgs)          {              LoggingHelper.Writelog(BusinessName + "開始執行" );          }            public  override void  OnExit(MethodExecutionEventArgs eventArgs)          {              LoggingHelper.Writelog(BusinessName + "成功完成" );          }      } }

我們約定每個Aspect類的命名必須為“XXXAttribute”的形式。其中“XXX”就是這個Aspect的名字。PostSharp中提供了豐富的內建“Base Aspect”以便我們繼承,其中這裡我們繼承“OnMethodBoundaryAspect ”,這個Aspect提供了進入、退出函式等連線點方法。另外,Aspect上必須設定“[Serializable] ”,這與PostSharp內部對Aspect的生命週期管理有關,具體為什麼請參看這裡

我們的LoggingAttribute非常簡單,就是在進入(Entry)和離開(Exit)函式時分別記錄日誌到log檔案。現在我們把這個Aspect應用到業務方法上:

1 2 3 4 5 [Logging(BusinessName= "預定房間" )] public  static  void  Describe(string memberName, string roomNumber) {      System.Windows.Forms.MessageBox.Show(String.Format( "尊敬的會員{0},恭喜您預定房間{1}成功!" , memberName, roomNumber), "提示" ); }

可以看到,應用Aspect非常簡單,就是將相應的Attribute加到業務方法上面。現在我們再執行預定房間程式,結果和上次沒什麼兩樣,但是如果我們開啟程式目錄,會看到多了一個“log.txt”檔案,裡面記錄有類似圖7的內容。

image

圖7、日誌內容

可以看到,我們已經通過AOP實現了日誌記錄功能。通過AOP將橫切關注點分離出來後,日誌記錄的程式碼都放在LoggingAttribute裡,需要修改只要修改一處即可。同時,業務方法僅含有業務程式碼,這樣大大提高了程式程式碼的可讀性和可維護性。

對PostSharp執行機制的簡要分析

上文已經說到,PostSharp使用的是靜態織入技術,下面我們分析一下PostSharp是如何實現的。

首先,當安裝PostSharp時,它自動為Visual Studio編譯器添加了AOP擴充套件。如果仔細觀察PostSharpExample編譯資訊,會發現有這麼兩行:

image

圖8、PostSharp編譯資訊

很明顯,在.NET Complier編譯完成後,下面PostSharp又做了一部分工作,這部分工作就是靜態織入的過程。如果我們用.NET Reflector檢視PostSharpExample.exe中Subscribe方法的反編譯程式碼,會發現多了很多東西:

image

圖9、織入Aspect後的Describe程式碼(由.NET Reflector反編譯)

這些多出來的程式碼,就是PostSharp靜態織入進去的。當然,這些程式碼在每次編譯完成後,PostSharp都會重新織入一次,所以整個過程對程式設計師是透明的,我們只需維護純淨的業務程式碼和Aspect程式碼即可。

使用PostSharp的優點和缺點(即使用AOP的優點和缺點)

總體來說,使用PostSharp,將會帶來如下優點:

  • 橫切關注點單獨分離出來,提高了程式碼的清晰性和可維護性。
  • 只要在Aspect中編寫輔助性功能程式碼,在一定程度上減少了工作量和冗餘程式碼。

當然,使用PostSharp也不是沒有缺點,主要缺點有如下兩方面:

  • 增加了除錯的難度。
  • 相比於不用AOP的程式碼,執行效率有所降低。

所以,對於是否引入AOP,請根據專案具體情況,權衡而定。

對於PostSharp的進一步學習

本文只是簡要介紹了PostSharp以及實現了一個小例子,並不打算詳細完整地介紹PostSharp的方方面面,而只想起到一個拋磚引玉的作用。PostSharp還有非常豐富的功能等待各位學習,因此,如果您對PostSharp十分有興趣,想進一步學習,請參看PostSharp官方參考文件

相關下載

本文用到的Example請點選這裡下載。 

PostSharp1.5安裝包請點選這裡下載