首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Emit 實戰教學 (一):從零構建輕量級 AOP 框架——核心概念、架構設計與動態代理原型

     編輯:關於.NET

利用週末兩天,運用 System.Reflection.Emit 從零實作一個極簡 AOP (面向切面程式設計) 框架。雖然粗糙,但完整展示了「攔截器模式 + 動態代理生成 + 配置驅動」的核心機制,是理解 Castle DynamicProxy、PostSharp 等成熟框架內部原理的最佳入門教材。

AOP 核心概念速覽

AOP (Aspect Oriented Programming):OOP 的延伸,關注橫切關注點,透過預編譯或執行期動態代理,在不修改原始碼前提下統一注入通用功能 (日誌、事務、安全、快取等)。

經典範例:鎖同步的橫切關注點

傳統 OOP 痛點:

  • 每個存取類都要繼承基類實作 Lock/Unlock
  • 單一繼承限制導致無法同時繼承其他基類
  • 「鎖」邏輯汙染業務類,重用性極低

AOP 解法:將「鎖」抽離為切面,在方法呼叫前後自動注入 Lock/Unlock,業務類零感知。

AOP 適用場景 (JBoss 4.0 採用清單)

本框架切面定義:方法執行四階段攔截點

  1. PreProcess (前置):日誌、權限、驗證、快取命中短路
  2. Method Body (本體):真實業務邏輯執行
  3. PostProcess (後置):日誌、審計、結果轉換、持久化
  4. 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;

架構優勢與局限

領域具體功能
安全Authentication、Authorization
效能Caching、Lazy Loading、Performance Optimization
可靠性Transactions、Error Handling、Persistence
觀測Logging、Tracing、Profiling、Monitoring、Debugging
資源Resource Pooling、Synchronization、Context Passing
優勢局限 (教學級框架)
零配置啟動 (屬性+配置檔)不支援非虛擬方法/靜態方法/介面方法攔截
切面實作完全解耦 (介面導向)無非同步切面支援 (Task/async-await)
多切面鏈管線、順序可控無條件式切面、優先序控制
Emit 生成 IL,執行效能接近原生無參數驗證、結果快取等進階功能
原始碼 < 500 行,極易理解修改生產環境請用 Castle/PostSharp/AspectCore

學習價值總結

  1. Emit 動態 IL 生成:理解 CLR 執行模型、堆疊機制、方法呼叫慣例
  2. 動態代理模式:繼承/介面實作 + 方法覆寫 + 攔截管線
  3. AOP 核心概念:切面、連接點、切入點、通知、織入
  4. 配置驅動架構:聲明式標記 + 外部配置 + 反射解析 + 依賴注入
  5. 框架設計思維:擴充點設計、介面契約、預設慣例、向後相容

下一部分將詳細講解三大切面介面、MethodContext 上下文物件與聲明式 [Aspect] 屬性的設計細節。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved