關於AOP(面向切面編程)還是先講一個日常經常碰到的場景“錯誤日志的記錄”,一般來說我們編碼的時候想記錄錯誤日志都是用try..catch來進行捕捉和記錄,慢慢的你會發現你每一個方法都得加try..catch才能記錄每一個方法的錯誤日志。而有什麼辦法能做一個統一的攔截呢?做webform的時候可能會用到IHttpModule去實現,也有的會繼承HttpApplication去做異常攔截。但這並不能應用在所有的項目場景中,而且這兩者針對的對象也不是方法而是http請求。在面對這些場景中AOP就派上用場了。AOP在園子裡也不算是一個陌生的話題了,關於AOP理論的文章也是挺多的,也有不少開源的代碼,可以很好的幫組我們了解這方面的內容。這段時間自己也是在了解這方面的東西趁著周末的時間就和大家分享一下這方面的內容,代碼不高深,寫得不好也別吐槽!
class BaseException : IHttpModule { #region 接口實現 /// <summary> /// 初始化 /// </summary> /// <param name="context"></param> public void Init(HttpApplication context) { context.Error += new EventHandler(this.OnError); } /// <summary> /// 釋放 /// </summary> public void Dispose() { } #endregion #region 事件 /// <summary> /// 錯誤事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnError(object sender, EventArgs e) { this.ExceptionHandle(); } #endregion #region 異常處理 /// <summary> /// 異常處理 /// </summary> protected virtual void ExceptionHandle() { //寫異常日志 Exception finalEx = HttpContext.Current.Server.GetLastError(); Exception innerEx = finalEx.InnerException; while (innerEx != null) { finalEx = innerEx; innerEx = finalEx.InnerException; } ExceptionLogs.Write(finalEx);//寫日志 //轉到異常錯誤頁面 HttpContext.Current.ApplicationInstance.Server.Transfer("url"); } #endregion }HttpModule攔截日志
就以MVC中權限驗證作為一個例子引入,先定義一個特性BaseAuthorizeAttribute繼承AuthorizeAttribute,中間可以定義其他屬性來滿足自己本身的業務,然後通過特性來進行攔截,在每個方法頭添加[BaseAuthorize(IsHaveAuthorize=true)](屬性只作為示例)則可以進行攔截。
public class BaseAuthorizeAttribute : AuthorizeAttribute { public string typeName { get; set; } public string desc { get; set; } /// <summary> /// 定義屬性 /// </summary> public bool IsHaveAuthorize { get; set; } public override void OnAuthorization(AuthorizationContext filterContext) { if (null == filterContext) { base.OnAuthorization(filterContext); return; } var method = ((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo;//當前方法 Debug("攔截到了方法:"); //權限判斷(僅僅是示例) if (IsHaveAuthorize) filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { Controller = "Account", action = "Index" })); else filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { Controller = "Account", action = "Login" })); return; } void Debug(string message) { string folder = AppDomain.CurrentDomain.BaseDirectory + "TaskError/"; if (!Directory.Exists(folder)) { Directory.CreateDirectory(folder); } StringBuilder sb = new StringBuilder(); DateTime dt = DateTime.Now; sb.AppendLine(dt.ToString("yyyy-MM-dd HH:mm:ss ") + message); Mutex m = new Mutex(false, "TaskDebug2016"); m.WaitOne(); File.AppendAllText(folder + "/" + dt.ToString("yyyyMMddHH") + ".debug.log", sb.ToString()); m.ReleaseMutex(); Thread.Sleep(100); } }BaseAuthorizeAttribute
[BaseAuthorize(IsHaveAuthorize=true)] public string HaveAuthorize() { return "有權限"; } [BaseAuthorize(IsHaveAuthorize = false)] public string NoAuthorize() { return "無權限"; } /// <summary> /// 不攔截 /// </summary> /// <returns></returns> public string NotIntercept() { return "不攔截"; }方法示例
RealProxy 類是 abstract 代理必須從中派生的基類。
使用跨遠程邊界任何類型的對象的客戶端實際上用於透明代理對象。 透明代理提供了實際對象駐留在客戶端的空間內的視覺效果。 它通過將轉發至真實對象使用遠程處理基礎結構對它進行的調用實現此目的。
透明代理是托管的運行時類型的類的實例本身 RealProxy。 RealProxy 實現從透明代理轉發操作所需的功能的一部分。 請注意,代理對象繼承關聯的托管對象,如垃圾收集、 字段和方法,支持語義,並且可以擴展以形成新類。 代理具有雙重特性︰ 它是作為遠程對象 (透明代理),相同的類的對象,它是一個托管的對象本身。
首先我們新建一個代理類AopRealProxy<T> 繼承 RealProxy
private T _target; public AopRealProxy(T target) : base(typeof(T)) { this._target = target; }View Code
然後我們重寫RealProxy裡的Invoke方法來執行我們攔截到的方法
public override IMessage Invoke(IMessage msg) { var methodCall = msg as IMethodCallMessage; var methodInfo = methodCall.MethodBase as MethodInfo; Before(msg); try { var result = methodInfo.Invoke(_target, methodCall.InArgs); After(msg); return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } catch(Exception ex) { Excepting(msg); return new ReturnMessage(ex, methodCall); } } public void Before(IMessage msg) { Console.WriteLine("方法執行前"); } public void After(IMessage msg) { Console.WriteLine("方法執行後"); } public void Excepting(IMessage msg) { Console.WriteLine("異常執行"); }View Code
接著通過GetTransparentProxy來獲得當前實例的透明代理 RealProxy
public static T Create<T>() { T instance = Activator.CreateInstance<T>(); AopRealProxy<T> aopRealProxy = new AopRealProxy<T>(instance); T aopTransparentProxy = (T)aopRealProxy.GetTransparentProxy(); return aopTransparentProxy; }View Code
定義接口和實現查看運行結果(實現類中必須繼承MarshalByRefObject否則攔截不到對應的信息),異常攔截的時候不能加try..catch
public interface IAnimal { string Eat(); string Run(); }接口
public class AnimalRealize : MarshalByRefObject, IAnimal { public string Eat() { return "Dog Eat"; } public string Run() { return "Dog Run"; } }實現
這是一個簡單的攔截,下面再結合另一種形式來實現。ProxyAttribute
新建特性AopProxyAttribute繼承ProxyAttribute,自動實現RealProxy植入,重寫ProxyAttribute中的CreateInstance獲得代理
public override MarshalByRefObject CreateInstance(Type serverType) { var instance = base.CreateInstance(serverType); AopRealProxy realProxy = new AopRealProxy(serverType, instance); //事物入侵 //TransactionAopRealProxy realProxy = new TransactionAopRealProxy(serverType, instance); //realProxy.InterceptionTransaction(new TransactionRealize()); return realProxy.GetTransparentProxy() as MarshalByRefObject; }View Code
同樣我們新建一個代理AopRealProxy繼承RealProxy(重寫Invoke和之前一樣),注意構造函數部分第一次執行發現攔截的是構造函數的話先要進行初始化。
public class AopRealProxy : RealProxy { private MarshalByRefObject _target; public AopRealProxy(Type type, MarshalByRefObject target) : base(type) { this._target = target; } /// <summary> /// /// </summary> /// <param name="msg"></param> /// <returns></returns> public override IMessage Invoke(IMessage msg) { IMethodCallMessage methodCall = (IMethodCallMessage)msg; IConstructionCallMessage ctr = methodCall as IConstructionCallMessage; IMessage message = null; InterceptionType interceptionType = GetInterceptionType(methodCall); //構造函數 if (ctr != null) { IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg); RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue); message = constructionReturnMessage; } //方法 else { if (interceptionType == InterceptionType.Before || interceptionType == InterceptionType.None) Console.WriteLine("開始執行"); try { var methodInfo = methodCall.MethodBase as MethodInfo; var result = methodInfo.Invoke(GetUnwrappedServer(), methodCall.InArgs); message= new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); if (interceptionType == InterceptionType.After || interceptionType == InterceptionType.None) Console.WriteLine("結束執行"); } catch (Exception ex) { message = new ReturnMessage(ex, methodCall); if (interceptionType == InterceptionType.Exception || interceptionType == InterceptionType.None) Console.WriteLine("異常執行"); } } return message; } /// <summary> /// 獲取方法標簽類型 /// </summary> private InterceptionType GetInterceptionType(IMethodCallMessage MethodCall) { InterceptionType type = InterceptionType.None; foreach (System.Attribute attr in MethodCall.MethodBase.GetCustomAttributes(false)) { MethodInterceptionAttribute methodInterceptionAttr = attr as MethodInterceptionAttribute; if (null !=methodInterceptionAttr) { type = methodInterceptionAttr.AdviceType; break; } } return type; } }View Code
新建測試類AopTestClass為類添加特性AopProxyAttribute實現攔截,類需要繼承ContextBoundObject,到這一步只要執行類裡面的方法在AopRealProxy裡面都可以攔截得到對應的方法。但是方法應該在什麼地方執行呢?可以在為方法定義一個特性在攔截的過程中判斷方法想要在攔截前執行還是在攔截後執行又或者是異常的時候執行。
/// <summary> /// 攔截位置 /// </summary> public enum InterceptionType { /// <summary> /// 默認 /// </summary> None, /// <summary> /// 攔截之前 /// </summary> Before, /// <summary> /// 攔截之後 /// </summary> After, /// <summary> /// 異常 /// </summary> Exception }特性枚舉
public class MethodInterceptionAttribute : System.Attribute { private InterceptionType interceptionType = InterceptionType.None; public MethodInterceptionAttribute(InterceptionType interceptionType) { this.interceptionType = interceptionType; } public InterceptionType AdviceType { get { return this.interceptionType; } } }方法特性
[AopProxyAttribute] public class AopTestClass : ContextBoundObject { public string content { get; set; } public AopTestClass(string content) { this.content = content; } public AopTestClass() { Console.WriteLine("初始化構造函數"); } [MethodInterception(InterceptionType.Before)] public string Before() { return "Before"; } [MethodInterception(InterceptionType.After)] public string After() { return "After"; } [MethodInterception(InterceptionType.Exception)] public string Exception(string content) { //Convert.ToInt32(content); return content; } }測試代碼
AopTestClass ap = new AopTestClass("aoptest"); string before = ap.Before(); Console.WriteLine(before); string after = ap.After(); Console.WriteLine(after);View Code
通過這種方式我們只需要為我們業務類添加上ContextBoundObject和添加上對應的特性AopProxyAttribute就可以實現攔截了。是不是覺得很方便呢?
結合上面的例子,我們再通過一個例子來試驗一下。在平時寫代碼的過程中,在數據庫操作的時候,如果一次需要更新多個表我們應該怎麼樣做呢?sql;sql;這樣?又或者是結合當前的業務寫一個存儲過程?當然這些都可以實現。結合Aop我們可以定義一個全局的事務來實現,在有關數據庫操作方面的類加上對應的特性,這樣就可以不用每次操作數據庫都得聲明事務。實現的方式是和上面講的都差不多,只是在代理層增加了事務注入這一操作(對應之前的before和after),直接給出代碼。
public class TransactionAopRealProxy : RealProxy { private MarshalByRefObject _target; private ITransaction transactionService = null; public TransactionAopRealProxy(Type type, MarshalByRefObject target) : base(type) { this._target = target; } /// <summary> /// /// </summary> /// <param name="msg"></param> /// <returns></returns> public override IMessage Invoke(IMessage msg) { IMethodCallMessage methodCall = (IMethodCallMessage)msg; IConstructionCallMessage ctr = methodCall as IConstructionCallMessage; IMessage message = null; //構造函數 if (ctr != null) { IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg); RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue); message = constructionReturnMessage; } //方法 else { transactionService.BeginTransaction(); try { var methodInfo = methodCall.MethodBase as MethodInfo; var result = methodInfo.Invoke(GetUnwrappedServer(), methodCall.InArgs); message = new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); transactionService.CommitTransaction(); } catch (Exception ex) { transactionService.RollbackTransaction(); message = new ReturnMessage(ex, methodCall); } } return message; } /// <summary> /// 事務入侵 /// </summary> /// <param name="transactionService"></param> public void InterceptionTransaction(ITransaction transactionService) { this.transactionService = transactionService; } }新建代理
/// <summary> /// 事務接口 /// </summary> public interface ITransaction { /// <summary> /// 開始事務 /// </summary> void BeginTransaction(); /// <summary> /// 提交事務 /// </summary> void CommitTransaction(); /// <summary> /// 事務回滾 /// </summary> void RollbackTransaction(); }定義事務接口
public class TransactionRealize:ITransaction { private TransactionScope transaction = null; /// <summary> /// 開始事務 /// </summary> public void BeginTransaction() { transaction = new TransactionScope(); } /// <summary> /// 提交事務 /// </summary> public void CommitTransaction() { transaction.Complete(); transaction.Dispose(); } /// <summary> /// 回滾事務 /// 執行Dispose不提交事務 /// </summary> public void RollbackTransaction() { transaction.Dispose(); } }事務實現
public class AopProxyAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { var instance = base.CreateInstance(serverType); //AopRealProxy realProxy = new AopRealProxy(serverType, instance); //事物入侵 TransactionAopRealProxy realProxy = new TransactionAopRealProxy(serverType, instance); realProxy.InterceptionTransaction(new TransactionRealize()); return realProxy.GetTransparentProxy() as MarshalByRefObject; } }獲得代理
[MethodInterception(InterceptionType.None)] public void deleteData() { string deleteSql = "DELETE FROM tblmessage WHERE ID='0aa080c81ce546efbb82d7a4fc5357ab'"; var result = MySqlDBManager.ExecuteNonQuery(deleteSql); string deleteSql2 = "DELETE FROM tblmessage WHERE ID1='5c02208f182045888df24fc19c7c31af'"; var result2 = MySqlDBManager.ExecuteNonQuery(deleteSql2); }代碼測試
AOP實現的方式還有很多,如IL、PostSharp等,我個人也想基於IL去進行實現,這方面實現出來後再與大家進行分享,有興趣的同學也可以自己去深入學習一下,園子裡面也有比較多這方面的文章,我也是一邊借鑒一邊學習。有很多文章都是寫得很不錯的
http://www.cnblogs.com/yingql/archive/2009/03/30/1425500.html
http://www.cnblogs.com/knowledgesea/p/5308123.html
最後還是給出對應Demo的源碼供大家學習:AOP.Demo.zip