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

Emit 實戰教學 (三):AOP 框架核心——配置檔驅動的切面解析與動態植入引擎

     編輯:關於.NET

系列第三部分,實作 AOP 框架的配置檔案定義、解析管理器、方法級切面解析邏輯。配置檔採用聲明式結構,分離「切面實作註冊」與「目標方法套用規則」,支援預設切面與方法級覆寫。

配置檔案完整結構

<?xml version="1.0" encoding="utf-8" ?>
<AOP>
  <!-- 切面實作註冊表:所有可用切面的類別對應 -->
  <Registry>
    <IPreProcess>
      <Aspect id="AddPreProcessLog" value="YQL.TestProject:YQL.TestProject.AddPreProcessLog" isDefault="true" />
      <Aspect id="VerifyPermission" value="YQL.AOP:YQL.AOP.VerifyPermission" />
    </IPreProcess>
    <IPostProcess>
      <Aspect id="AddPostProcessLog" value="YQL.TestProject:YQL.TestProject.AddPostProcessLog" isDefault="true" />
    </IPostProcess>
    <IExceptionHandler>
      <Aspect id="SimplyExceptionHandler" value="YQL.TestProject:YQL.TestProject.SimplyExceptionHandler" isDefault="true" />
    </IExceptionHandler>
  </Registry>

  <!-- 應用規則:特定類別/方法套用哪些切面 -->
  <Application>
    <Class id="YQL.TestProject:YQL.TestProject.Test">
      <Method id="Int32 Test4(System.String)">
        <IPreProcess>
          <Aspect id="AddPreProcessLog" />
        </IPreProcess>
        <IPostProcess>
          <Aspect id="AddPostProcessLog" />
          <Aspect id="TestException" />
        </IPostProcess>
        <IExceptionHandler>
          <Aspect id="SimplyExceptionHandler" />
        </IExceptionHandler>
      </Method>
    </Class>
  </Application>
</AOP>

配置設計精要

區段用途關鍵屬性
<Registry>切面實作型別登錄id (唯一鍵)、value (組件:完整型別名)、isDefault (預設切面旗標)
<Application>目標方法切面套用規則巢狀 Class → Method → 切面類型 → Aspect id 結構

設計意圖:未來可開發視覺化配置工具,透過反射掃描組件自動產生 Registry,開發者僅需在 Application 區勾選套用規則。

AopConfigManager 核心實作

初始化:XPath 解析建構記憶體模型

public static class AopConfigManager
{
    private static Dictionary<string, AspectTag> _aspectInfoDict;  // Registry 解析結果
    private static Dictionary<string, ClassTag> _classInfoDict;    // Application 解析結果

    public static void Init(string configFilePath)
    {
        var document = new XPathDocument(configFilePath);
        var navigator = document.CreateNavigator();
        
        InitAspectInfo(navigator);   // 解析 Registry
        InitClassInfo(navigator);    // 解析 Application
    }
    
    // XPathNavigator 遍歷 XML 節點,建構 AspectTag/ClassTag/MethodTag 物件樹
}

核心查詢:獲取方法指定切面型別的實作型別清單

public static List<Type> GetMethodAspectInfo(MethodInfo methodInfo, AspectTypes aspectType)
{
    string methodName = methodInfo.ToString();  // 含簽名: "Int32 Test4(System.String)"
    string typeName = ReflectionHelper.GetTypeUniqueName(methodInfo.DeclaringType);
    
    List<string> aspectIdList = null;
    
    // 1. 嘗試從 Application 獲取方法專屬配置
    if (_classInfoDict.TryGetValue(typeName, out var classTag))
    {
        aspectIdList = classTag.GetAspectInfoList(methodName, aspectType);
    }
    
    // 2. 回退至 Registry 預設切面
    if (aspectIdList == null || aspectIdList.Count == 0)
    {
        string aspectName = EnumHelper.GetEnumDescription(aspectType);
        var aspectTag = _aspectInfoDict[aspectName];
        aspectIdList = aspectTag.GetDefaultAspectList();
    }
    
    // 3. 解析 id → 完整型別名 → Type
    var typeNames = aspectIdList.Select(id => _aspectInfoDict[aspectName].GetAspectTypeUniqueName(id)).ToList();
    return ConvertToTypeList(typeNames);  // 反射載入組件、GetType
}

關鍵輔助類別設計

// 切面標籤:對應 Registry 中某切面類型 (IPreProcess 等) 的所有實作
class AspectTag
{
    public Dictionary<string, string> AspectMap { get; }  // id -> "Assembly:TypeName"
    public List<string> DefaultAspectIds { get; }
    
    public List<string> GetDefaultAspectList() => DefaultAspectIds;
    public string GetAspectTypeUniqueName(string id) => AspectMap[id];
}

// 類別標籤:對應 Application 中某類別的方法切面規則
class ClassTag
{
    public Dictionary<string, MethodTag> Methods { get; }  // 方法簽名 -> MethodTag
    
    public List<string> GetAspectInfoList(string methodName, AspectTypes aspectType)
        => Methods.TryGetValue(methodName, out var mt) ? mt.GetAspectIds(aspectType) : null;
}

// 方法標籤:單一方法在三類切面上的具體套用 id 清單
class MethodTag
{
    public Dictionary<AspectTypes, List<string>> AspectIds { get; }
    public List<string> GetAspectIds(AspectTypes type) 
        => AspectIds.TryGetValue(type, out var list) ? list : null;
}

執行時動態植入流程整合

  1. 應用啟動呼叫 AopConfigManager.Init("aop.config")
  2. 動態代理建立時,針對每個虛擬方法呼叫 GetMethodAspectInfo(method, AspectTypes.PreProcess)
  3. 取得 List<Type>,透過 Activator.CreateInstance 或容器解析建立切面實例
  4. Emit 動態生成代理方法體,按順序插入切面呼叫邏輯 (見偽代碼)

偽代碼:生成的代理方法結構

public virtual int Test4(string arg)
{
    var context = new MethodContext(methodInfo, new object[]{arg});
    try
    {
        // PreProcess 切面鏈
        if (preProcessAspects.All(a => a.PreProcess(context)))
        {
            // 原始方法體
            var result = base.Test4(arg);
            context.Result = result;
            
            // PostProcess 切面鏈
            postProcessAspects.ForEach(a => a.PostProcess(context));
            return result;
        }
        return default;
    }
    catch (Exception ex)
    {
        // ExceptionHandler 切面鏈
        exceptionAspects.ForEach(a => a.ProcessException(context, ex));
        throw; // 或根據切面決定是否吞噬
    }
}

框架定位與擴展方向

「這只是一個極簡 AOP 框架,主要為了練習 Emit 動態代理生成、配置驅動架構、切面鏈管線設計。生產環境建議使用成熟方案:Castle DynamicProxy、PostSharp、AspectCore、.NET Core 內建攔截器。」

下一步擴展建議

  • 非同步切面支援:Task PreProcessAsync(...) 配合 await
  • 條件式切面:基於屬性/參數值動態決定是否套用
  • 切面優先序:多切面執行順序顯式控制
  • 配置熱重載:FileSystemWatcher 監聽配置變更自動重新解析
  • 診斷追蹤:整合 System.Diagnostics.Activity 實現分散式追蹤

掌握「配置驅動 + 反射解析 + Emit 動態代理」三位一體架構,是理解現代 AOP 框架核心運作機制的關鍵路徑。

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