在本文中,我将向您展现如何建立拦截器来实现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
示例开始,但请遵循本文的更新以获取具体示例。