ABP AOP 用例

介绍

在本文中,我将向您展现如何建立拦截器来实现AOP技术。我将使用ASP.NET Boilerplate(ABP)做为基础应用程序框架Castle Windsor做为拦截库。这里描述的大多数技术对于使用独立于ABP框架的Castle Windsor也是有效的。git

什么是面向方面编程(AOP)和方法拦截?

维基百科:“ 在计算中,面向方面的编程(AOP)是一种编程范式,旨在增长模块性容许的分离横切关注它经过添加额外的行为,以现有的代码(咨询)这样作。无需修改代码而是分别指定哪一个代码经过“切入点”规范进行修改github

在应用程序中,咱们可能会有一些重复/相似的代码用于日志记录,受权,验证,异常处理等等...数据库

手动方式(无AOP)

示例代码所有手动执行:编程

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方式

若是咱们使用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处理ComponentRegisteredCastle 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示例开始,但请遵循本文的更新以获取具体示例。

相关文章
相关标签/搜索