AOP 是 Aspect-Oriented programming 的縮寫,中文翻譯為面向切面編程,它是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。它和OOP一樣是一種編程思想。
AOP基本概念
橫切(cross-cutting):與對象核心功能無關的公共行為
關注點(concern):一塊我們感興趣的區域
方面(Aspect):就是將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重復代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。
連接點(join point):是程序執行中一個精確執行點,例如類中的一個方法。它是一個抽象的概念,在實現AOP時,並不需要去定義一個連接點。
切入點(point cut):本質是一個捕獲連接點的結構。在AOP中,可以定義一個切入點來捕獲相關方法的調用。
通知(advice):是“切入點”的執行方代碼,是執行“方面”的具體邏輯
引入(introduce):為對象引入附加的方法或屬性,從而達到修改對象結構的目的
AOP本質
OOP通過封裝、繼承及多態等概念來建立一種對象層次結構,用來模式現實事物。因為OOP中每個對象都是獨立的,因此當我們需要引入公共行為時(日志、安全、異常),代碼顯得比較臃腫。而AOP很好的解決了這一問題。它剖開對象內部把”橫切”的代碼封裝成一個“方面”。引用大牛的形象比喻來說“如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。”
AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程部分是核心關注點,與之關系不大的部分是橫切關注點。AOP的作用在於分離系統中各種關注點,將核心關注點和橫切關注點分離開來。
AOP實現方式
實現AOP的方式有兩種:一是動態代理技術,二是采用靜態織入方式
動態代理:利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行。一般通過Emit來動態的創建代理類;
靜態織入:通過引入特定的語法來創建“方面”,從而在編譯期間就織入了“方面”的代碼。目前這種實現方式,都是對編譯器擴展。這種實現方式性能是最好的
采用動態代理實現AOP
在說動態代理之前,我們要看一下之前是怎么實現“橫切”的。
首先,我們有這樣一個類,負責處理用戶的業務邏輯。
public interface IUserService { bool AddUser(User user); IList<User> GetUsers(); } public class UserService : IUserService { public bool AddUser(User user) { Console.WriteLine("添加人員:{0}", user.Name); return true; } public IList<User> GetUsers() { return new List<User> { new User{Name = "張三",BirthDate = DateTime.Now.AddYears(-1)}, new User{Name = "李四",BirthDate = DateTime.Now.AddYears(-2)}, new User{Name = "王五",BirthDate = DateTime.Now.AddYears(-3)}, }; } }
現在我們要給所有的核心業務添加日志記錄的功能,我們以下幾種選擇:
1、直接在方法內部添加日志記錄的代碼
public class UserService : IUserService { public bool AddUser(User user) { Console.WriteLine("添加人員:{0}", user.Name); Console.WriteLine("Log:AddUser"); return true; } public IList<User> GetUsers() { Console.WriteLine("Log:GetUsers"); return new List<User> { new User{Name = "張三",BirthDate = DateTime.Now.AddYears(-1)}, new User{Name = "李四",BirthDate = DateTime.Now.AddYears(-2)}, new User{Name = "王五",BirthDate = DateTime.Now.AddYears(-3)}, }; } }
2、添加一個繼承UserServeice的子類,並重寫/覆蓋父類的方法
public class MyUserService : UserService { public new bool AddUser(User user) { base.AddUser(user); Console.WriteLine("Log:AddUser"); return true; } public new IList<User> GetUsers() { Console.WriteLine("Log:GetUsers"); return base.GetUsers(); } }
3、寫一個靜態的代理類
首先聲明一個攔截器接口
public interface IInterceptor { object Invoke(object obj, string methodName, object[] paramters); }
然后創建一個日志攔截器
public class LogInterceptor : IInterceptor { public object Invoke(object obj, string methodName, object[] paramters) { Console.WriteLine("Log:{0}", methodName); object result = obj.GetType().GetMethod(methodName).Invoke(obj, paramters); return result; } }
最后創建靜態代理類
public class MyUserServiceProxy { private readonly IInterceptor _interceptor; private readonly User _user; public MyUserServiceProxy(User user, IInterceptor interceptor) { _user = user; _interceptor = interceptor; } public bool AddUser(User user) { return (bool)_interceptor.Invoke(_user, "AddUser", new object[] { user }); } public IList<User> GetUsers() { return (IList<User>)_interceptor.Invoke(_user, "GetUsers", null); } }
下面調用
var proxy = new AOPTest.MyUserServiceProxy(new AOPTest.User { Name = "Khadron", BirthDate = DateTime.Now }, new AOPTest.LogInterceptor()); IList<AOPTest.User> users = proxy.GetUsers(); if (users.Count > 0) { proxy.AddUser(users[0]); } Console.ReadKey();
相對於前面的兩種方法,第三種方法算是比較好的了,不過還是不夠好,缺點是不僅必須給每個業務類都重寫一個包含日志處理的代理類,而且如果還有其他類似日志處理的模塊(權限),那還需要在寫一個其他代理類,這樣做起來不僅工作量大,而且維護起來也十分麻煩。 采用動態代理的方式很好的解決了這個問題,上面說過主要采用Emit來實現,代碼太多就不貼了,請查看我的github,這里面實現了一個簡單的AOP框架
.NET平台下AOP框架
動態代理:Castle Dynamic Proxy 、Unity 、LOOM.NET等
靜態織入:Postsharp(收費) 、Eos等
個人推薦使用 Castle框架,至此結束