aop 階段性概況

前言

對aop進行一個階段性的總結。

正文

首先什麼是aop呢?

那麼首先看aop的解決什麼樣的問題。

public class Program
{
    public static void Main(string[] args)
    {
        
    }

    public void ChangePosition1()
    {
        // your operation
        SavePosition();
    }

    public void ChangePosition2()
    {
        // your operation
        SavePosition();
    }

    public void SavePosition()
    {
    }
}

看上面這個位置:
ChangePosition1 和 ChangePosition2

他們做完一些操作之後,需要做相同的操作,如上面所述,需要保存位置。

只有兩個方法調用,那麼不是啥子問題。

但是如果有大量的方法去調用這個東西,那麼問題就出現了。

第一:重複性的工作
第二:出錯率,每次都要寫一遍,可能出現忘記的情況
第三:看着不優雅

那麼在如此多的案例的情況下,人們都發現了規律。

比如說,在某個行爲前做什麼,在某個行爲後做什麼。

那麼我們可以進行擴展:

public void Dosomething()
{
  // your operaion
}

aspect logging
{
   befor(dosomething is called)
   {
      Log.Write("enter dosomething")
   }

   after(dosomething is called)
   {
      Log.Write("after dosomething")
   }
}

aspect verification()
{
    befor(dosomething is called)
    {
      // your verification
    }
}

比如我們的驗證和日誌,可以通過這些aop去處理。

aop 全稱是aspect-oriented programming 中文大多翻譯過來是面向切面編程。

oriented 是面向,比如日誌、驗證,這些是aspect。

所以取了asepct-oriented programming 這個名字。

好的,那麼現在瞭解了aop是什麼東西,也瞭解了他的由來。

下面瞭解一下aop濫用的情況。

什麼情況aop會濫用呢?

比如說:

public void setsomething()
{
}

aspect shutup
{
  after(something is called)
  {
    // shutup
  }
}

這種aop 就是反模式,不被推薦的。

原因是,比如我執行了setsomething之後,我setsomething 的意思是設置某一些東西,而沒有shutup的含義。

但是最後卻運行了該操作,這樣難以維護和理解。

那麼爲什麼logging 和 verification 能夠被人所接受呢?

因爲其沒有破壞我們該代碼的邏輯,的確setsomething做的事情就是它應該做的事情,不對執行邏輯造成影響。

深入一下aop是如何實現的呢?其實aop的原理很簡單,但是優雅實現的卻有一點點複雜。

  • 字節碼操作

    • 優點:字節碼操作可以在更細粒度的層面上操作代碼,可以實現更靈活和精細的AOP功能。可以在編譯期或運行期動態修改字節碼,對目標類進行修改。
    • 缺點:實現相對複雜,需要對字節碼結構有一定的瞭解。可能會影響代碼的可讀性和維護性。
  • 代理技術

    • 優點:代理技術相對簡單易懂,可以快速實現AOP功能。可以通過代理對象來實現橫切關注點的功能,不需要直接操作字節碼。
    • 缺點:代理技術通常在運行時動態創建代理對象,可能會引入性能開銷。對於一些高性能要求的場景,可能不太適合。

下面對這個分別進行舉例:

先從好理解的代理開始:

public class LoggingAspect
{
    public void LogBefore(string methodName)
    {
        Console.WriteLine($"Logging before {methodName} execution");
    }
}

然後創建相應的代理:

public class UserServiceProxy<T> : DispatchProxy
{
    private T _decorated;
    
    private LoggingAspect _loggingAspect;
    
    protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
    {
        _loggingAspect.LogBefore(targetMethod.Name);
        
        return targetMethod.Invoke(_decorated, args);
    }
    
    public static T Create(T decorated)
    {
        object proxy = Create<T, UserServiceProxy<T>>();
        ((UserServiceProxy<T>)proxy)._decorated = decorated;
        ((UserServiceProxy<T>)proxy)._loggingAspect = new LoggingAspect();
        
        return (T)proxy;
    }
}

解釋一下,這個create創建了什麼,這個create 創建了兩個類型的繼承類。

也就是創建了動態類型。

public class UserService : IUserService
{
    public void Login()
    {
        Console.WriteLine("User logged in");
    }
}

現在是我們的代碼實現了。

那麼看下是怎麼調用的。

public static void Main(string[] args)
{
    IUserService userService = new UserService();
    IUserService proxy = UserServiceProxy<IUserService>.Create(userService);
    proxy.Login();
}

這樣就調用完成了。

看下效果。

這樣就完成了相應的code。

至於爲什麼我們在使用框架的時候進行屬性標記即可,那是因爲框架幫我們把事情做了。

例如:

  1. 安裝PostSharp:首先需要安裝PostSharp NuGet包。

  2. 定義切面類

[Serializable]
public class LoggingAspect : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine($"Logging before {args.Method.Name} execution");
    }
}
  1. 應用切面:在需要應用AOP的方法上添加切面標記。
public class UserService
{
    [LoggingAspect]
    public void Login()
    {
        Console.WriteLine("User logged in");
    }
}

至於這個框架是怎麼實現的,原理就是利用MSBuilder。

MSBuild工具可以通過自定義任務(Custom Tasks)來實現預處理操作。通過編寫自定義任務,可以在MSBuild構建過程中執行特定的預處理邏輯。以下是一個簡單的示例,演示如何在MSBuild中使用自定義任務進行預處理:

  1. 創建自定義任務
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class CustomPreprocessTask : Task
{
    public override bool Execute()
    {
        // 在這裏編寫預處理邏輯
        Log.LogMessage("Custom preprocessing task executed.");
        return true;
    }
}
  1. 在項目文件中引用自定義任務

在項目文件(.csproj)中添加以下內容,引用自定義任務:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="CustomPreprocessTask" AssemblyFile="Path\\To\\CustomTask.dll" />
  
  <Target Name="CustomPreprocessTarget" BeforeTargets="Build">
    <CustomPreprocessTask />
  </Target>
</Project>
  1. 執行預處理操作

在構建項目時,MSBuild會執行自定義任務中定義的預處理邏輯。可以在Execute方法中編寫任何需要的預處理代碼,例如生成文件、修改配置等操作。

通過編寫自定義任務並在項目文件中引用,可以利用MSBuild進行預處理操作。這樣可以在構建過程中執行特定的邏輯,實現更靈活的構建流程。

代理模式,就是利用了msbuilder 預處理邏輯,在編譯前進行了預處理。

那麼字節碼模式就是在msbuilder 編譯後進行預處理。

using Mono.Cecil;
using Mono.Cecil.Cil;

public class LoggingAspect
{
    public void LogBefore()
    {
        Console.WriteLine("Logging before method execution");
    }
}

public class Program
{
    public static void Main()
    {
        AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly("YourAssembly.dll");
        ModuleDefinition module = assembly.MainModule;

        TypeDefinition type = module.Types.Single(t => t.Name == "UserService");
        MethodDefinition method = type.Methods.Single(m => m.Name == "Login");

        ILProcessor processor = method.Body.GetILProcessor();
        Instruction firstInstruction = method.Body.Instructions.First();

        // 在方法開頭插入日誌記錄代碼
        processor.InsertBefore(firstInstruction, processor.Create(OpCodes.Call, typeof(LoggingAspect).GetMethod("LogBefore")));

        assembly.Write("YourModifiedAssembly.dll");
    }
}

就是在我們程序編譯完成後,進行在相應的位置進行注入程序。

下一節oop,關於aop解決一些實際問題的例子後續補齊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章