在本文中,我將向您展現如何建立攔截器來實現AOP技術。我將使用ASP.NET Boilerplate(ABP)做爲基礎應用程序框架和Castle Windsor做爲攔截庫。這裏描述的大多數技術對於使用獨立於ABP框架的Castle Windsor也是有效的。git
維基百科:「 在計算中,面向方面的編程(AOP)是一種編程範式,旨在增長模塊性容許的分離橫切關注它經過添加額外的行爲,以現有的代碼(諮詢)這樣作。無需修改代碼而是分別指定哪一個代碼經過「切入點」規範進行修改。github
在應用程序中,咱們可能會有一些重複/相似的代碼用於日誌記錄,受權,驗證,異常處理等等...數據庫
示例代碼所有手動執行:編程
public class TaskAppService : ApplicationService { private readonly IRepository<Task> _taskRepository; private readonly IPermissionChecker _permissionChecker; private readonly ILogger _logger; public TaskAppService(IRepository<Task> taskRepository, IPermissionChecker permissionChecker, ILogger logger) { _taskRepository = taskRepository; _permissionChecker = permissionChecker; _logger = logger; } public void CreateTask(CreateTaskInput input) { _logger.Debug("Running CreateTask method: " + input.ToJsonString()); try { if (input == null) { throw new ArgumentNullException("input"); } if (!_permissionChecker.IsGranted("TaskCreationPermission")) { throw new Exception("No permission for this operation!"); } _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId)); } catch (Exception ex) { _logger.Error(ex.Message, ex); throw; } _logger.Debug("CreateTask method is successfully completed!"); } }
在CreateTask
方法中,基本代碼是_taskRepository.Insert(...)
方法調用。全部其餘代碼重複代碼,並將與咱們其餘方法相同/類似TaskAppService
。在實際應用中,咱們將有不少應用服務須要相同的功能。另外,咱們可能有其餘相似的數據庫鏈接開關代碼,審覈日誌等等...框架
若是咱們使用AOP和截取技術,TaskAppService
能夠用以下所示的相同功能來寫:異步
public class TaskAppService : ApplicationService { private readonly IRepository<Task> _taskRepository; public TaskAppService(IRepository<Task> taskRepository) { _taskRepository = taskRepository; } [AbpAuthorize("TaskCreationPermission")] public void CreateTask(CreateTaskInput input) { _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId)); } }
如今,它徹底是CreateTask
方法惟一的。異常處理,驗證和日誌記錄代碼被徹底刪除,由於它們與其餘方法類似,而且能夠以傳統方式集中。受權代碼被 AbpAuthorize
更容易寫入和讀取的屬性所代替。async
幸運的是,全部這些和更多的由ABP框架自動完成。可是,您可能但願建立一些特定於您本身的應用程序需求的自定義攔截邏輯。這就是爲何我建立了這篇文章。ide
我從ABP 啓動模板(包括模塊零)建立了一個示例項目,並添加到Github倉庫。post
咱們先來看一個簡單的攔截器來測量方法的執行時間:this
using System.Diagnostics; using Castle.Core.Logging; using Castle.DynamicProxy; namespace InterceptionDemo.Interceptors { public class MeasureDurationInterceptor : IInterceptor { public ILogger Logger { get; set; } public MeasureDurationInterceptor() { Logger = NullLogger.Instance; } public void Intercept(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); //Executing the actual method invocation.Proceed(); //After method execution stopwatch.Stop(); Logger.InfoFormat( "MeasureDurationInterceptor: {0} executed in {1} milliseconds.", invocation.MethodInvocationTarget.Name, stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") ); } } }
攔截器是實現IInterceptor
接口(Castle Windsor)的類。它定義了Intercept
獲取IInvocation
參數的方法。經過這個調用參數,咱們能夠調查執行方法,方法參數,返回值,方法聲明的類,彙編等等。Intercept
調用註冊方法時調用方法(請參閱下面的註冊部分)。Proceed()
方法執行實際截取的方法。咱們能夠在實際的方法執行以前和以後編寫代碼,如本示例所示。
一Interceptor
類也能夠注入其依賴像其餘類。在這個例子中,咱們將屬性注入一個ILogger
寫入日誌的方法執行時間。
在咱們建立一個攔截器以後,咱們能夠註冊所需的類。例如,咱們可能想要註冊MeasureDurationInterceptor
全部應用程序服務類的全部方法。由於全部應用程序服務類都IApplicationService
在ABP框架中實現,咱們能夠很容易地識別應用程序服務
有一些替代方法來註冊攔截器。可是,ABP處理ComponentRegistered
Castle Windsor事件最合適的方法是Kernel
:
public static class MeasureDurationInterceptorRegistrar { public static void Initialize(IKernel kernel) { kernel.ComponentRegistered += Kernel_ComponentRegistered; } private static void Kernel_ComponentRegistered(string key, IHandler handler) { if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add (new InterceptorReference(typeof(MeasureDurationInterceptor))); } } }
以這種方式,每當一個類註冊到依賴注入系統(IOC)時,咱們能夠處理事件,檢查這個類是不是咱們想攔截的類之一,若是是這樣,添加攔截器。
建立這樣的註冊碼後,咱們須要Initialize
從別的地方調用該方法。最好在PreInitialize
你的模塊中調用它(由於課程一般在IOC中註冊Initialize
):
public class InterceptionDemoApplicationModule : AbpModule { public override void PreInitialize() { MeasureDurationInterceptorRegistrar.Initialize(IocManager.IocContainer.Kernel); } //... }
執行這些步驟後,我運行並登陸到應用程序。而後,我查看日誌文件並查看日誌:
INFO 2016-02-23 14:59:28,611 [63 ] .Interceptors.MeasureDurationInterceptor - GetCurrentLoginInformations executed in 4,939 milliseconds.
注意:GetCurrentLoginInformations是一個SessionAppService類的方法。你能夠在源代碼中檢查它,但這並不重要,由於咱們的攔截器不知道截取的方法的細節。
攔截異步方法與截取同步方法不一樣。例如,MeasureDurationInterceptor
上面定義的異步方法不能正常工做。由於一個異步方法當即返回一個異步方法Task
。因此,咱們沒法測量什麼時候實際完成(實際上,GetCurrentLoginInformations
上面的例子也是一個異步方法,4,939 ms是錯誤的值)。
咱們來改變MeasureDurationInterceptor以支持異步方法,而後解釋咱們如何實現它:
using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; using Castle.Core.Logging; using Castle.DynamicProxy; namespace InterceptionDemo.Interceptors { public class MeasureDurationAsyncInterceptor : IInterceptor { public ILogger Logger { get; set; } public MeasureDurationAsyncInterceptor() { Logger = NullLogger.Instance; } public void Intercept(IInvocation invocation) { if (IsAsyncMethod(invocation.Method)) { InterceptAsync(invocation); } else { InterceptSync(invocation); } } private void InterceptAsync(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); //Calling the actual method, but execution has not been finished yet invocation.Proceed(); //We should wait for finishing of the method execution ((Task) invocation.ReturnValue) .ContinueWith(task => { //After method execution stopwatch.Stop(); Logger.InfoFormat( "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.", invocation.MethodInvocationTarget.Name, stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") ); }); } private void InterceptSync(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); //Executing the actual method invocation.Proceed(); //After method execution stopwatch.Stop(); Logger.InfoFormat( "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.", invocation.MethodInvocationTarget.Name, stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") ); } public static bool IsAsyncMethod(MethodInfo method) { return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) ); } } }
因爲同步和異步執行邏輯徹底不一樣,我檢查了當前的方法是異步仍是同步(IsAsyncMethod
是)。我把之前的代碼移到了InterceptSync
方法,並引入了新的 InterceptAsync
方法。我使用Task.ContinueWith(...)
方法在任務完成後執行動做。ContinueWith
即便攔截方法拋出異常,方法仍然有效。
如今,我MeasureDurationAsyncInterceptor
經過修改MeasureDurationInterceptorRegistrar
上面定義來註冊爲應用程序服務的第二個攔截器:
public static class MeasureDurationInterceptorRegistrar { public static void Initialize(IKernel kernel) { kernel.ComponentRegistered += Kernel_ComponentRegistered; } private static void Kernel_ComponentRegistered(string key, IHandler handler) { if (typeof(IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationInterceptor))); handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationAsyncInterceptor))); } } }
若是咱們再次運行應用程序,咱們將會看到, MeasureDurationAsyncInterceptor
測量的時間要長得多MeasureDurationInterceptor
,由於它實際上等待直到方法徹底執行。
INFO 2016-03-01 10:29:07,592 [10 ] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: GetCurrentLoginInformations executed in 4.964 milliseconds. INFO 2016-03-01 10:29:07,693 [7 ] rceptors.MeasureDurationAsyncInterceptor - MeasureDurationAsyncInterceptor: GetCurrentLoginInformations executed in 104,994 milliseconds.
這樣,咱們能夠正確攔截異步方法來運行先後的代碼。可是,若是咱們的先後代碼涉及另外一個異步方法調用,事情會變得有點複雜。
首先,我找不到之前執行異步代碼的方法 invocation.Proceed()
。由於溫莎城堡本身不支持異步(其餘國際奧委會經理也不支持我所知)。因此,若是您須要在實際執行方法以前運行代碼,請同步執行。若是您找到方法,請分享您的解決方案做爲本文的評論。
方法執行後咱們能夠執行異步代碼。我改變了 InterceptAsync
,以支持它:
using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; using Castle.Core.Logging; using Castle.DynamicProxy; namespace InterceptionDemo.Interceptors { public class MeasureDurationWithPostAsyncActionInterceptor : IInterceptor { public ILogger Logger { get; set; } public MeasureDurationWithPostAsyncActionInterceptor() { Logger = NullLogger.Instance; } public void Intercept(IInvocation invocation) { if (IsAsyncMethod(invocation.Method)) { InterceptAsync(invocation); } else { InterceptSync(invocation); } } private void InterceptAsync(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); //Calling the actual method, but execution has not been finished yet invocation.Proceed(); //Wait task execution and modify return value if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task) invocation.ReturnValue, async () => await TestActionAsync(invocation), ex => { LogExecutionTime(invocation, stopwatch); }); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, async () => await TestActionAsync(invocation), ex => { LogExecutionTime(invocation, stopwatch); }); } } private void InterceptSync(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); //Executing the actual method invocation.Proceed(); //After method execution LogExecutionTime(invocation, stopwatch); } public static bool IsAsyncMethod(MethodInfo method) { return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) ); } private async Task TestActionAsync(IInvocation invocation) { Logger.Info("Waiting after method execution for " + invocation.MethodInvocationTarget.Name); await Task.Delay(200); //Here, we can await another methods. This is just for test. Logger.Info("Waited after method execution for " + invocation.MethodInvocationTarget.Name); } private void LogExecutionTime(IInvocation invocation, Stopwatch stopwatch) { stopwatch.Stop(); Logger.InfoFormat( "MeasureDurationWithPostAsyncActionInterceptor: {0} executed in {1} milliseconds.", invocation.MethodInvocationTarget.Name, stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") ); } } }
若是咱們要在方法執行後執行一個異步方法,咱們應該用第二個方法的返回值替換返回值。我創造了一個神奇的InternalAsyncHelper
課程來完成它。InternalAsyncHelper
以下所示:
internal static class InternalAsyncHelper { public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; try { await actualReturnValue; await postAction(); } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } } public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; try { var result = await actualReturnValue; await postAction(); return result; } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } } public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction) { return typeof (InternalAsyncHelper) .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) .MakeGenericMethod(taskReturnType) .Invoke(null, new object[] { actualReturnValue, action, finalAction }); } }
我會經過添加一些用例來改進這篇文章:
雖然您能夠從MeasureDurationInterceptor
示例開始,但請遵循本文的更新以獲取具體示例。