前言
對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。
至於為什麼我們在使用框架的時候進行屬性標記即可,那是因為框架幫我們把事情做了。
例如:
-
安裝PostSharp:首先需要安裝PostSharp NuGet包。
-
定義切面類:
[Serializable]
public class LoggingAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine($"Logging before {args.Method.Name} execution");
}
}
- 應用切面:在需要應用AOP的方法上新增切面標記。
public class UserService
{
[LoggingAspect]
public void Login()
{
Console.WriteLine("User logged in");
}
}
至於這個框架是怎麼實現的,原理就是利用MSBuilder。
MSBuild工具可以透過自定義任務(Custom Tasks)來實現預處理操作。透過編寫自定義任務,可以在MSBuild構建過程中執行特定的預處理邏輯。以下是一個簡單的示例,演示如何在MSBuild中使用自定義任務進行預處理:
- 建立自定義任務:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class CustomPreprocessTask : Task
{
public override bool Execute()
{
// 在這裡編寫預處理邏輯
Log.LogMessage("Custom preprocessing task executed.");
return true;
}
}
- 在專案檔案中引用自定義任務:
在專案檔案(.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>
- 執行預處理操作:
在構建專案時,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解決一些實際問題的例子後續補齊。