使用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是
下面對上文提到的定義進行一些解釋。
在當前大多數支援面向物件的程式語言中(例如C#,Java等),函式(Function)是表述程式功能的最小單元,而一個函式的程式碼層面往往同時含有核心業務邏輯和輔助性功能。核心業務邏輯指一個函式本身主要要實現的業務功能,例如在一個線上電子商務系統中,“PlaceOrder”函式其核心業務邏輯是“下訂單”,而“UpgradeMember”函式其核心業務是“提升一個會員的等級”。但是,一個函式除了核心業務程式碼外,往往還會有一些輔助性功能程式碼,如事務處理、快取處理、日誌記錄、異常處理等等。而這些輔助性功能一般會存在於大多數甚至所有業務函式中,即形成AOSD中所謂的橫切關注點,如圖1所示。
圖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平臺為例)。
圖2、編譯時AOP示意圖
如圖2所示,當使用靜態織入時,帶AOP擴充套件的編譯器會在編譯時將Aspect程式碼織入業務函式程式碼,形成整合後的IL,然後交由CLR執行。
執行時AOP(動態織入)
執行時AOP如圖3所示。
圖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”。
圖4、新建專案
編寫核心業務函式
首先我們來編寫核心業務。當然這裡不存在真正的業務,我們只是模擬一個而已。將要模擬的核心業務是預定房間。先構建一個如圖5所示的簡單UI。
圖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(
"資訊不完整"
,
"提示"
);
}
}
}
|
執行程式就可以看到相應的效果:
圖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的內容。
圖7、日誌內容
可以看到,我們已經通過AOP實現了日誌記錄功能。通過AOP將橫切關注點分離出來後,日誌記錄的程式碼都放在LoggingAttribute裡,需要修改只要修改一處即可。同時,業務方法僅含有業務程式碼,這樣大大提高了程式程式碼的可讀性和可維護性。
對PostSharp執行機制的簡要分析
上文已經說到,PostSharp使用的是靜態織入技術,下面我們分析一下PostSharp是如何實現的。
首先,當安裝PostSharp時,它自動為Visual Studio編譯器添加了AOP擴充套件。如果仔細觀察PostSharpExample編譯資訊,會發現有這麼兩行:
圖8、PostSharp編譯資訊
很明顯,在.NET Complier編譯完成後,下面PostSharp又做了一部分工作,這部分工作就是靜態織入的過程。如果我們用.NET Reflector檢視PostSharpExample.exe中Subscribe方法的反編譯程式碼,會發現多了很多東西:
圖9、織入Aspect後的Describe程式碼(由.NET Reflector反編譯)
這些多出來的程式碼,就是PostSharp靜態織入進去的。當然,這些程式碼在每次編譯完成後,PostSharp都會重新織入一次,所以整個過程對程式設計師是透明的,我們只需維護純淨的業務程式碼和Aspect程式碼即可。
使用PostSharp的優點和缺點(即使用AOP的優點和缺點)
總體來說,使用PostSharp,將會帶來如下優點:
- 橫切關注點單獨分離出來,提高了程式碼的清晰性和可維護性。
- 只要在Aspect中編寫輔助性功能程式碼,在一定程度上減少了工作量和冗餘程式碼。
當然,使用PostSharp也不是沒有缺點,主要缺點有如下兩方面:
- 增加了除錯的難度。
- 相比於不用AOP的程式碼,執行效率有所降低。
所以,對於是否引入AOP,請根據專案具體情況,權衡而定。
對於PostSharp的進一步學習
本文只是簡要介紹了PostSharp以及實現了一個小例子,並不打算詳細完整地介紹PostSharp的方方面面,而只想起到一個拋磚引玉的作用。PostSharp還有非常豐富的功能等待各位學習,因此,如果您對PostSharp十分有興趣,想進一步學習,請參看PostSharp官方參考文件。
相關下載
本文用到的Example請點選這裡下載。
PostSharp1.5安裝包請點選這裡下載。