| 領域 | 具體功能 |
| 安全 | Authentication、Authorization |
| 效能 | Caching、Lazy Loading、Performance Optimization |
| 可靠性 | Transactions、Error Handling、Persistence |
| 觀測 | Logging、Tracing、Profiling、Monitoring、Debugging |
| 資源 | Resource Pooling、Synchronization、Context Passing |
本框架切面定義:方法執行四階段攔截點
- PreProcess (前置):日誌、權限、驗證、快取命中短路
- Method Body (本體):真實業務邏輯執行
- PostProcess (後置):日誌、審計、結果轉換、持久化
- ExceptionHandler (例外):錯誤處理、重試、熔斷、包裝
三類可擴充切面:IPreProcess、IPostProcess、IExceptionHandler (詳細定義見第二部分)
客戶端使用體驗:聲明式 + 配置驅動
1. 程式碼標記切面需求
public class Test
{
[Aspect(AspectTypes.PreProcess | AspectTypes.ExceptionHandler)]
public virtual void Test1(string num) { Console.WriteLine(Convert.ToInt32(num)); }
[Aspect(AspectTypes.PostProcess | AspectTypes.ExceptionHandler)]
public virtual int Test2(string num) { return Convert.ToInt32(num); }
[Aspect(AspectTypes.ExceptionHandler)]
public virtual int Test3(string num) { return Convert.ToInt32(num); }
}
2. 配置檔指定具體實作 (預設/覆寫)
<AOP>
<Registry>
<IPreProcess>
<Aspect id="LogPre" value="MyApp.Aspects:MyApp.Aspects.LogPreProcess" isDefault="true"/>
</IPreProcess>
<IPostProcess>
<Aspect id="LogPost" value="MyApp.Aspects:MyApp.Aspects.LogPostProcess" isDefault="true"/>
</IPostProcess>
<IExceptionHandler>
<Aspect id="DefaultEx" value="MyApp.Aspects:MyApp.Aspects.DefaultExceptionHandler" isDefault="true"/>
</IExceptionHandler>
</Registry>
<Application>
<Class id="MyApp.Test">
<Method id="Void Test1(System.String)">
<IPreProcess><Aspect id="LogPre"/></IPreProcess>
<IExceptionHandler><Aspect id="DefaultEx"/></IExceptionHandler>
</Method>
</Class>
</Application>
</AOP>
3. 一鍵建立代理實例
// 核心入口:動態代理工廠
Test test = DynamicProxyFactory.CreateProxy<Test>(typeof(Test));
// 正常呼叫,自動觸發切面管線
test.Test1("123");
動態代理生成核心原理 (Emit 實作藍圖)
步驟 1:定義動態組件與模組
var asmName = new AssemblyName("DynamicProxyAssembly");
var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
var moduleBuilder = asmBuilder.DefineDynamicModule("ProxyModule");
步驟 2:建立代理類型 (繼承目標類別/實作介面)
var typeBuilder = moduleBuilder.DefineType(
"Proxy_" + targetType.Name,
TypeAttributes.Public | TypeAttributes.Class,
targetType); // 繼承目標類別,覆寫虛擬方法
步驟 3:定義切面欄位 (每個切面類型一個清單)
var preProcessField = typeBuilder.DefineField(
"_preProcessAspects", typeof(List<IPreProcess>), FieldAttributes.Private);
var postProcessField = typeBuilder.DefineField(
"_postProcessAspects", typeof(List<IPostProcess>), FieldAttributes.Private);
var exceptionField = typeBuilder.DefineField(
"_exceptionAspects", typeof(List<IExceptionHandler>), FieldAttributes.Private);
步驟 4:建構函數注入切面實例
var ctorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public, CallingConventions.Standard,
new[] { typeof(List<IPreProcess>), typeof(List<IPostProcess>), typeof(List<IExceptionHandler>) });
var il = ctorBuilder.GetILGenerator();
// this._preProcessAspects = arg0; this._postProcessAspects = arg1; this._exceptionAspects = arg2;
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, preProcessField);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Stfld, postProcessField);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_3); il.Emit(OpCodes.Stfld, exceptionField);
il.Emit(OpCodes.Ret);
步驟 5:覆寫虛擬方法並植入切面邏輯 (核心難點)
foreach (var method in targetType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Virtual))
{
if (!method.IsVirtual || method.IsFinal) continue;
var proxyMethod = typeBuilder.DefineMethod(
method.Name,
method.Attributes & ~MethodAttributes.NewSlot, // 覆寫而非隱藏
method.CallingConvention,
method.ReturnType,
method.GetParameters().Select(p => p.ParameterType).ToArray());
// 為每個參數定義同名
for (int i = 0; i < method.GetParameters().Length; i++)
proxyMethod.DefineParameter(i+1, ParameterAttributes.None, method.GetParameters()[i].Name);
var il = proxyMethod.GetILGenerator();
// === 此處生成偽代碼對應的 IL 指令序列 ===
// 1. new MethodContext(methodInfo, arguments)
// 2. try {
// foreach(pre in _preProcessAspects) if(!pre.PreProcess(ctx)) return default;
// var result = base.Method(arguments); // Callvirt 父類方法
// ctx.Result = result;
// foreach(post in _postProcessAspects) post.PostProcess(ctx);
// return result;
// } catch(ex) {
// foreach(exh in _exceptionAspects) exh.ProcessException(ctx, ex);
// throw;
// }
}
步驟 6:建立型別並快取
var proxyType = typeBuilder.CreateType();
_typeCache[targetType] = proxyType; // 避免重複生成
return proxyType;
架構優勢與局限
| 優勢 | 局限 (教學級框架) |
| 零配置啟動 (屬性+配置檔) | 不支援非虛擬方法/靜態方法/介面方法攔截 |
| 切面實作完全解耦 (介面導向) | 無非同步切面支援 (Task/async-await) |
| 多切面鏈管線、順序可控 | 無條件式切面、優先序控制 |
| Emit 生成 IL,執行效能接近原生 | 無參數驗證、結果快取等進階功能 |
| 原始碼 < 500 行,極易理解修改 | 生產環境請用 Castle/PostSharp/AspectCore |
學習價值總結
- Emit 動態 IL 生成:理解 CLR 執行模型、堆疊機制、方法呼叫慣例
- 動態代理模式:繼承/介面實作 + 方法覆寫 + 攔截管線
- AOP 核心概念:切面、連接點、切入點、通知、織入
- 配置驅動架構:聲明式標記 + 外部配置 + 反射解析 + 依賴注入
- 框架設計思維:擴充點設計、介面契約、預設慣例、向後相容
下一部分將詳細講解三大切面介面、MethodContext 上下文物件與聲明式 [Aspect] 屬性的設計細節。
- 上一篇文章: Emit 實戰教學 (二):AOP 框架核心介面與屬性設計——切面契約、上下文物件與聲明式標記
- 下一篇文章: .NET 托管/非托管資源與 GC 回收深度疑問解析:實測驗證與最佳實踐
Copyright ©
程式碼之家 All Rights Reserved