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

摘要

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

AOP(Aspect-Oriented Programming)

AOP的基本定義及做用

根據維基百科的定義,「AOP(Aspect-Oriented Programming)是一種將函數的輔助性功能與業務邏輯相分離的編程泛型(programming paradigm),其目的是將橫切關注點(cross-cutting concerns)分離出來,使得程序具備更高的模塊化特性。AOP是面向方面軟件開發(Aspect-Oriented Software Development)在編碼實現層面上的具體表現(面向方面軟件開發AOSD是一個囊括面向方面分析、面向方面設計和麪向方面編程等一系列概念的完整工程系統——筆者注)。AOP包括編程模型和具體用於實現AOP的框架兩部分。」程序員

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

在當前大多數支持面向對象的編程語言中(例如C#,Java等),函數(Function)是表述程序功能的最小單元,而一個函數的代碼層面每每同時含有核心業務邏輯和輔助性功能。核心業務邏輯指一個函數自己主要要實現的業務功能,例如在一個在線電子商務系統中,「PlaceOrder」函數其核心業務邏輯是「下訂單」,而「UpgradeMember」函數其核心業務是「提高一個會員的等級」。可是,一個函數除了核心業務代碼外,每每還會有一些輔助性功能代碼,如事務處理、緩存處理、日誌記錄、異常處理等等。而這些輔助性功能通常會存在於大多數甚至全部業務函數中,即造成AOSD中所謂的橫切關注點,如圖1所示。編程

image

圖一、橫切關注點示意緩存

橫切關注點的存在,形成了以下幾個問題。框架

  • 代碼編寫和維護困難。

橫切關注點不只橫切各個函數,還可能在不一樣類甚至不一樣工程間橫切,使得同一個輔助功能(如事務處理)分散到各處,若是要增長新函數時要時刻注意別忘了添加全部須要的橫切代碼。另外,若是須要對其進行修改,則須要到全部被橫切的函數中修改,維護難度極大。編程語言

  • 引入大量冗餘代碼

因爲同一個輔助性功能的代碼幾乎是徹底相同的,這樣就會令一樣的代碼在各個函數中出現,引入了大量冗餘代碼。ide

  • 下降代碼質量

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

因此,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

圖二、編譯時AOP示意圖

如圖2所示,當使用靜態織入時,帶AOP擴展的編譯器會在編譯時將Aspect代碼織入業務函數代碼,造成整合後的IL,而後交由CLR運行。

運行時AOP(動態織入)

運行時AOP如圖3所示。image

圖三、運行時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

圖四、新建項目

編寫核心業務函數

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

image

圖五、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

圖六、預約房間成功演示效果

使用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

圖七、日誌內容

能夠看到,咱們已經經過AOP實現了日誌記錄功能。經過AOP將橫切關注點分離出來後,日誌記錄的代碼都放在LoggingAttribute裏,須要修改只要修改一處便可。同時,業務方法僅含有業務代碼,這樣大大提升了程序代碼的可讀性和可維護性。

對PostSharp運行機制的簡要分析

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

首先,當安裝PostSharp時,它自動爲Visual Studio編譯器添加了AOP擴展。若是仔細觀察PostSharpExample編譯信息,會發現有這麼兩行:

image

圖八、PostSharp編譯信息

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

image

圖九、織入Aspect後的Describe代碼(由.NET Reflector反編譯)

這些多出來的代碼,就是PostSharp靜態織入進去的。固然,這些代碼在每次編譯完成後,PostSharp都會從新織入一次,因此整個過程對程序員是透明的,咱們只需維護純淨的業務代碼和Aspect代碼便可。

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

整體來講,使用PostSharp,將會帶來以下優勢:

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

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

  • 增長了調試的難度。
  • 相比於不用AOP的代碼,運行效率有所下降。

因此,對因而否引入AOP,請根據項目具體狀況,權衡而定。

對於PostSharp的進一步學習

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

相關下載

本文用到的Example請點擊這裏下載。 

PostSharp1.5安裝包請點擊這裏下載

相關文章
相關標籤/搜索