在開發權限系統時,面對日誌記錄、異常處理、事務管理、快取等橫切關注點,AOP(面向切面程式設計)自然成為架構首選。近期我深入研究 PostSharp 的架構與實現原理,並將其整合至現有框架中。PostSharp 採用 MSIL Injection(靜態代碼注入),其執行效率顯著優於傳統的 Reflection Emit 動態代理方式;透過 MSBuild Task 整合,開發者可如同使用編譯器內建屬性般輕鬆套用 AOP。
new Proxy(),業務邏輯保持純粹。靜態注入的挑戰在於注入代碼品質與除錯複雜度,但 PostSharp 成熟的工具鏈已有效緩解此問題。參考國外專家關於「Using AOP and PostSharp to Enhance Your Code」的兩篇經典文章,本文將結合實戰案例深入剖析。
假設一個典型業務場景:訂單模組具備新增、刪除功能。新增/刪除需驗證權限,刪除需記錄日誌,所有操作需捕獲並記錄異常。傳統寫法導致大量樣板代碼散落業務邏輯中,違反單一職責原則,維護極其困難。
public class XOrders {
public bool Add(string id, string orderName) {
try {
if (User.AddEnable) {
// 核心業務邏輯
Console.WriteLine("新增訂單: {0}, {1}", id, orderName);
return true;
}
} catch (Exception) {
// 異常處理樣板代碼
throw;
}
return true;
}
// Delete 方法同理...
}
首先安裝 PostSharp(必須安裝才能在編譯期注入代碼),再定義乾淨的業務類別:
public class Orders {
[Permission]
[Exception]
public bool Add(string id, string orderName) {
Console.WriteLine("正在執行新增訂單操作...");
Console.ReadLine();
Console.WriteLine("新增成功:編號:{0},名稱:{1}", id, orderName);
return true;
}
[Logger]
[Permission]
[Exception]
public bool Delete(string id) {
Console.WriteLine("刪除訂單成功:編號:{0}", id);
return true;
}
}
輔助的靜態用戶權限類別:
public static class User {
private static string _userId;
public static string UserId { get => _userId; set => _userId = value; }
public static bool AddEnable => _userId?.ToLower() == "admin";
public static bool DeleteEnable => _userId?.ToLower() == "admin";
}
[Serializable]
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class PermissionAttribute : OnMethodBoundaryAspect {
public override void OnEntry(MethodExecutionEventArgs eventArgs) {
// 簡化示範:實際應根據方法名稱判斷 Add/Delete 權限
if (!User.AddEnable) {
Console.WriteLine("用戶 [{0}] 無權限執行 [{1}]", User.UserId, eventArgs.Method);
eventArgs.FlowBehavior = FlowBehavior.Return; // 中斷執行
}
}
}
[Serializable]
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class LoggerAttribute : OnMethodInvocationAspect {
public override void OnInvocation(MethodInvocationEventArgs eventArgs) {
DateTime start = DateTime.Now;
Console.WriteLine("[LOG] 開始執行 {0} 於 {1}", eventArgs.Method, start);
try {
eventArgs.Proceed(); // 執行原方法
} finally {
Console.WriteLine("[LOG] 結束執行 {0},耗時 {1}ms", eventArgs.Method, (DateTime.Now - start).TotalMilliseconds);
}
}
}
[Serializable]
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ExceptionAttribute : OnMethodBoundaryAspect {
public override void OnException(MethodExecutionEventArgs eventArgs) {
Console.WriteLine("[EXCEPTION] 方法 {0} 發生異常: {1}", eventArgs.Method, eventArgs.Exception.Message);
// 記錄異常至日誌系統...
eventArgs.FlowBehavior = FlowBehavior.Continue; // 可選:吞噬異常或繼續拋出
}
}
static void Main() {
Orders order = new Orders();
Console.Write("請輸入用戶名: ");
User.UserId = Console.ReadLine();
Console.Write("請輸入密碼: ");
Console.ReadLine(); // 簡化驗證
while (true) {
Console.Write("操作選項 - 新增(A)、刪除(D)、退出(X): ");
string opt = Console.ReadLine()?.ToLower();
if (opt == "a") {
Console.Write("訂單編號: "); string id = Console.ReadLine();
Console.Write("訂單名稱: "); string name = Console.ReadLine();
order.Add(id, name);
} else if (opt == "d") {
Console.Write("訂單編號: "); string id = Console.ReadLine();
order.Delete(id);
} else if (opt == "x") break;
else Console.WriteLine("輸入錯誤,請重新輸入!");
}
}
編譯後反編譯生成的 EXE,發現 Orders 類別已被 PostSharp 重寫。以 Add 方法為例:
public bool Add(string id, string orderName) {
bool returnValue;
MethodExecutionEventArgs laosEventArgs;
try {
object[] arguments = new object[] { id, orderName };
laosEventArgs = new MethodExecutionEventArgs(methodof(Orders.Add), this, arguments);
// 1. 執行 OnEntry (權限檢查)
~PostSharp~Laos~Implementation.~aspect~1.OnEntry(laosEventArgs);
if (laosEventArgs.FlowBehavior == FlowBehavior.Return)
return (bool)laosEventArgs.ReturnValue; // 權限不足直接返回
try {
// 2. 原始業務代碼
Console.WriteLine("正在執行新增訂單操作...");
Console.ReadLine();
Console.WriteLine("新增成功:編號:{0},名稱:{1}", id, orderName);
returnValue = true;
} catch (Exception exception) {
// 3. 執行 OnException (異常處理)
var exArgs = new MethodExecutionEventArgs(...);
exArgs.Exception = exception;
~PostSharp~Laos~Implementation.~aspect~3.OnException(exArgs);
// 根據 FlowBehavior 決定是否重拋異常
throw;
}
// 4. 執行 OnSuccess
laosEventArgs.ReturnValue = returnValue;
~PostSharp~Laos~Implementation.~aspect~1.OnSuccess(laosEventArgs);
} finally {
// 5. 執行 OnExit (無論成敗皆執行)
laosEventArgs.ReturnValue = returnValue;
~PostSharp~Laos~Implementation.~aspect~1.OnExit(laosEventArgs);
}
return returnValue;
}
關鍵發現:若在 OnEntry 中設定 eventArgs.FlowBehavior = FlowBehavior.Return,原始方法將**完全不會執行**——這正是權限攔截的核心機制。
| 維度 | PostSharp (MSIL Injection) | 動態代理 |
|---|---|---|
| 執行效能 | 接近原生代碼,零反射開銷 | 反射調用,有效能損耗 |
| 攔截範圍 | 任意方法、欄位、屬性、事件 | 僅虛擬方法/介面方法 |
| 部署複雜度 | 需安裝 VS 擴充、MSBuild 整合 | 純代碼,無外部依賴 |
| 除錯體驗 | 需 PDB 支援,可單步進入切面 | 標準除錯,但代理層干擾 |
| 適用場景 | 企業級框架、高效能需求、全方位 AOP | 輕量級專案、介面導向設計 |
MulticastAttribute 批次套用。FlowBehavior.Continue(吞噬異常)、Return(自定返回值)、ThrowException(重拋/包裝)。PostSharp 以編譯期 MSIL 重寫實現真正的 AOP,雖引入建置依賴,卻換來卓越效能與完整攔截能力。對於需要處理大量橫切關注點的企業級應用,它是極具價值的架構選擇。掌握其反編譯後的代碼結構,有助於深度理解執行流程,排查疑難雜症。