介绍

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

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

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

在应用程序中,我们可能会有一些重复/类似的代码用于日志记录,授权,验证,异常处理等等...

手动方式(无AOP)

示例代码全部手动执行:

  1. public class TaskAppService : ApplicationService
  2. {
  3. private readonly IRepository<Task> _taskRepository;
  4. private readonly IPermissionChecker _permissionChecker;
  5. private readonly ILogger _logger;
  6.  
  7. public TaskAppService(IRepository<Task> taskRepository,
  8. IPermissionChecker permissionChecker, ILogger logger)
  9. {
  10. _taskRepository = taskRepository;
  11. _permissionChecker = permissionChecker;
  12. _logger = logger;
  13. }
  14.  
  15. public void CreateTask(CreateTaskInput input)
  16. {
  17. _logger.Debug("Running CreateTask method: " + input.ToJsonString());
  18.  
  19. try
  20. {
  21. if (input == null)
  22. {
  23. throw new ArgumentNullException("input");
  24. }
  25.  
  26. if (!_permissionChecker.IsGranted("TaskCreationPermission"))
  27. {
  28. throw new Exception("No permission for this operation!");
  29. }
  30.  
  31. _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId));
  32. }
  33. catch (Exception ex)
  34. {
  35. _logger.Error(ex.Message, ex);
  36. throw;
  37. }
  38.  
  39. _logger.Debug("CreateTask method is successfully completed!");
  40. }
  41. }

CreateTask方法中,基本代码_taskRepository.Insert(...)方法调用。所有其他代码重复代码,并将与我们其他方法相同/相似TaskAppService。在实际应用中,我们将有很多应用服务需要相同的功能。另外,我们可能有其他类似的数据库连接开关代码,审核日志等等...

AOP方式

如果我们使用AOP和截取技术,TaskAppService可以用如下所示的相同功能来写:

  1. public class TaskAppService : ApplicationService
  2. {
  3. private readonly IRepository<Task> _taskRepository;
  4.  
  5. public TaskAppService(IRepository<Task> taskRepository)
  6. {
  7. _taskRepository = taskRepository;
  8. }
  9.  
  10. [AbpAuthorize("TaskCreationPermission")]
  11. public void CreateTask(CreateTaskInput input)
  12. {
  13. _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId));
  14. }
  15. }

现在,它完全是CreateTask方法唯一的。异常处理验证日志记录代码被完全删除,因为它们与其他方法相似,并且可以以传统方式集中。授权代码被 AbpAuthorize更容易写入和读取的属性所代替。

幸运的是,所有这些和更多的由ABP框架自动完成。但是,您可能希望创建一些特定于您自己的应用程序需求的自定义拦截逻辑。这就是为什么我创建了这篇文章。

关于示例项目

我从ABP 启动模板(包括模块零)创建了一个示例项目,并添加到Github仓库

创建拦截器

我们先来看一个简单的拦截器来测量方法的执行时间:

  1. using System.Diagnostics;
  2. using Castle.Core.Logging;
  3. using Castle.DynamicProxy;
  4.  
  5. namespace InterceptionDemo.Interceptors
  6. {
  7. public class MeasureDurationInterceptor : IInterceptor
  8. {
  9. public ILogger Logger { get; set; }
  10.  
  11. public MeasureDurationInterceptor()
  12. {
  13. Logger = NullLogger.Instance;
  14. }
  15.  
  16. public void Intercept(IInvocation invocation)
  17. {
  18. //Before method execution
  19. var stopwatch = Stopwatch.StartNew();
  20.  
  21. //Executing the actual method
  22. invocation.Proceed();
  23.  
  24. //After method execution
  25. stopwatch.Stop();
  26. Logger.InfoFormat(
  27. "MeasureDurationInterceptor: {0} executed in {1} milliseconds.",
  28. invocation.MethodInvocationTarget.Name,
  29. stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
  30. );
  31. }
  32. }
  33. }

拦截器是实现IInterceptor接口(Castle Windsor)的类。它定义了Intercept获取IInvocation参数的方法。通过这个调用参数,我们可以调查执行方法,方法参数,返回值,方法声明的类,汇编等等。Intercept调用注册方法时调用方法(请参阅下面的注册部分)。Proceed()方法执行实际截取的方法。我们可以在实际的方法执行之前之后编写代码,如本示例所示。

Interceptor类也可以注入其依赖像其他类。在这个例子中,我们将属性注入一个ILogger写入日志的方法执行时间。

注册拦截器

在我们创建一个拦截器之后,我们可以注册所需的类。例如,我们可能想要注册MeasureDurationInterceptor所有应用程序服务类的所有方法。因为所有应用程序服务类都IApplicationService在ABP框架中实现,我们可以很容易地识别应用程序服务

有一些替代方法来注册拦截器。但是,ABP处理ComponentRegisteredCastle Windsor事件最合适的方法是Kernel

  1. public static class MeasureDurationInterceptorRegistrar
  2. {
  3. public static void Initialize(IKernel kernel)
  4. {
  5. kernel.ComponentRegistered += Kernel_ComponentRegistered;
  6. }
  7.  
  8. private static void Kernel_ComponentRegistered(string key, IHandler handler)
  9. {
  10. if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
  11. {
  12. handler.ComponentModel.Interceptors.Add
  13. (new InterceptorReference(typeof(MeasureDurationInterceptor)));
  14. }
  15. }
  16. }

以这种方式,每当一个类注册到依赖注入系统(IOC)时,我们可以处理事件,检查这个类是否是我们想拦截的类之一,如果是这样,添加拦截器。

创建这样的注册码后,我们需要Initialize从别的地方调用该方法。最好在PreInitialize你的模块中调用它(因为课程通常在IOC中注册Initialize):

  1. public class InterceptionDemoApplicationModule : AbpModule
  2. {
  3. public override void PreInitialize()
  4. {
  5. MeasureDurationInterceptorRegistrar.Initialize(IocManager.IocContainer.Kernel);
  6. }
  7.  
  8. //...
  9. }

执行这些步骤后,我运行并登录到应用程序。然后,我查看日志文件并查看日志:

  1. INFO -- ::, [ ] .Interceptors.MeasureDurationInterceptor -
  2. GetCurrentLoginInformations executed in , milliseconds.

注意:GetCurrentLoginInformations是一个SessionAppService类的方法。你可以在源代码中检查它,但这并不重要,因为我们的拦截器不知道截取的方法的细节。

拦截异步方法

拦截异步方法与截取同步方​​法不同。例如,MeasureDurationInterceptor上面定义的异步方法不能正常工作。因为一个异步方法立即返回一个异步方法Task。所以,我们无法测量何时实际完成(实际上,GetCurrentLoginInformations上面的例子也是一个异步方法,4,939 ms是错误的值)。

我们来改变MeasureDurationInterceptor以支持异步方法,然后解释我们如何实现它:

  1. using System.Diagnostics;
  2. using System.Reflection;
  3. using System.Threading.Tasks;
  4. using Castle.Core.Logging;
  5. using Castle.DynamicProxy;
  6.  
  7. namespace InterceptionDemo.Interceptors
  8. {
  9. public class MeasureDurationAsyncInterceptor : IInterceptor
  10. {
  11. public ILogger Logger { get; set; }
  12.  
  13. public MeasureDurationAsyncInterceptor()
  14. {
  15. Logger = NullLogger.Instance;
  16. }
  17.  
  18. public void Intercept(IInvocation invocation)
  19. {
  20. if (IsAsyncMethod(invocation.Method))
  21. {
  22. InterceptAsync(invocation);
  23. }
  24. else
  25. {
  26. InterceptSync(invocation);
  27. }
  28. }
  29.  
  30. private void InterceptAsync(IInvocation invocation)
  31. {
  32. //Before method execution
  33. var stopwatch = Stopwatch.StartNew();
  34.  
  35. //Calling the actual method, but execution has not been finished yet
  36. invocation.Proceed();
  37.  
  38. //We should wait for finishing of the method execution
  39. ((Task) invocation.ReturnValue)
  40. .ContinueWith(task =>
  41. {
  42. //After method execution
  43. stopwatch.Stop();
  44. Logger.InfoFormat(
  45. "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
  46. invocation.MethodInvocationTarget.Name,
  47. stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
  48. );
  49. });
  50. }
  51.  
  52. private void InterceptSync(IInvocation invocation)
  53. {
  54. //Before method execution
  55. var stopwatch = Stopwatch.StartNew();
  56.  
  57. //Executing the actual method
  58. invocation.Proceed();
  59.  
  60. //After method execution
  61. stopwatch.Stop();
  62. Logger.InfoFormat(
  63. "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
  64. invocation.MethodInvocationTarget.Name,
  65. stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
  66. );
  67. }
  68.  
  69. public static bool IsAsyncMethod(MethodInfo method)
  70. {
  71. return (
  72. method.ReturnType == typeof(Task) ||
  73. (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
  74. );
  75. }
  76. }
  77. }

由于同步和异步执行逻辑完全不同,我检查了当前的方法是异步还是同步(IsAsyncMethod是)。我把以前的代码移到了InterceptSync方法,并引入了新的 InterceptAsync方法。我使用Task.ContinueWith(...)方法在任务完成后执行动作。ContinueWith即使拦截方法抛出异常,方法仍然有效

现在,我MeasureDurationAsyncInterceptor通过修改MeasureDurationInterceptorRegistrar上面定义来注册为应用程序服务的第二个拦截器:

  1. public static class MeasureDurationInterceptorRegistrar
  2. {
  3. public static void Initialize(IKernel kernel)
  4. {
  5. kernel.ComponentRegistered += Kernel_ComponentRegistered;
  6. }
  7.  
  8. private static void Kernel_ComponentRegistered(string key, IHandler handler)
  9. {
  10. if (typeof(IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
  11. {
  12. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationInterceptor)));
  13. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationAsyncInterceptor)));
  14. }
  15. }
  16. }

如果我们再次运行应用程序,我们将会看到, MeasureDurationAsyncInterceptor测量的时间要长得多MeasureDurationInterceptor,因为它实际上等待直到方法完全执行。

  1. INFO -- ::, [ ] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: GetCurrentLoginInformations executed in 4.964 milliseconds.
  2. INFO -- ::, [ ] rceptors.MeasureDurationAsyncInterceptor - MeasureDurationAsyncInterceptor: GetCurrentLoginInformations executed in , milliseconds.

这样,我们可以正确拦截异步方法来运行前后的代码。但是,如果我们的前后代码涉及另一个异步方法调用,事情会变得有点复杂。

首先,我找不到以前执行异步代码的方法 invocation.Proceed()。因为温莎城堡自己不支持异步(其他国际奥委会经理也不支持我所知)。所以,如果您需要在实际执行方法之前运行代码,请同步执行。如果您找到方法,请分享您的解决方案作为本文的评论。

方法执行后我们可以执行异步代码。我改变了 InterceptAsync,以支持它:

  1. using System.Diagnostics;
  2. using System.Reflection;
  3. using System.Threading.Tasks;
  4. using Castle.Core.Logging;
  5. using Castle.DynamicProxy;
  6.  
  7. namespace InterceptionDemo.Interceptors
  8. {
  9. public class MeasureDurationWithPostAsyncActionInterceptor : IInterceptor
  10. {
  11. public ILogger Logger { get; set; }
  12.  
  13. public MeasureDurationWithPostAsyncActionInterceptor()
  14. {
  15. Logger = NullLogger.Instance;
  16. }
  17.  
  18. public void Intercept(IInvocation invocation)
  19. {
  20. if (IsAsyncMethod(invocation.Method))
  21. {
  22. InterceptAsync(invocation);
  23. }
  24. else
  25. {
  26. InterceptSync(invocation);
  27. }
  28. }
  29.  
  30. private void InterceptAsync(IInvocation invocation)
  31. {
  32. //Before method execution
  33. var stopwatch = Stopwatch.StartNew();
  34.  
  35. //Calling the actual method, but execution has not been finished yet
  36. invocation.Proceed();
  37.  
  38. //Wait task execution and modify return value
  39. if (invocation.Method.ReturnType == typeof(Task))
  40. {
  41. invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
  42. (Task) invocation.ReturnValue,
  43. async () => await TestActionAsync(invocation),
  44. ex =>
  45. {
  46. LogExecutionTime(invocation, stopwatch);
  47. });
  48. }
  49. else //Task<TResult>
  50. {
  51. invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
  52. invocation.Method.ReturnType.GenericTypeArguments[],
  53. invocation.ReturnValue,
  54. async () => await TestActionAsync(invocation),
  55. ex =>
  56. {
  57. LogExecutionTime(invocation, stopwatch);
  58. });
  59. }
  60. }
  61.  
  62. private void InterceptSync(IInvocation invocation)
  63. {
  64. //Before method execution
  65. var stopwatch = Stopwatch.StartNew();
  66.  
  67. //Executing the actual method
  68. invocation.Proceed();
  69.  
  70. //After method execution
  71. LogExecutionTime(invocation, stopwatch);
  72. }
  73.  
  74. public static bool IsAsyncMethod(MethodInfo method)
  75. {
  76. return (
  77. method.ReturnType == typeof(Task) ||
  78. (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
  79. );
  80. }
  81.  
  82. private async Task TestActionAsync(IInvocation invocation)
  83. {
  84. Logger.Info("Waiting after method execution for " + invocation.MethodInvocationTarget.Name);
  85. await Task.Delay(); //Here, we can await another methods. This is just for test.
  86. Logger.Info("Waited after method execution for " + invocation.MethodInvocationTarget.Name);
  87. }
  88.  
  89. private void LogExecutionTime(IInvocation invocation, Stopwatch stopwatch)
  90. {
  91. stopwatch.Stop();
  92. Logger.InfoFormat(
  93. "MeasureDurationWithPostAsyncActionInterceptor: {0} executed in {1} milliseconds.",
  94. invocation.MethodInvocationTarget.Name,
  95. stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
  96. );
  97. }
  98. }
  99. }

如果我们要在方法执行后执行一个异步方法,我们应该用第二个方法的返回值替换返回值。我创造了一个神奇的InternalAsyncHelper课程来完成它。InternalAsyncHelper如下所示:

  1. internal static class InternalAsyncHelper
  2. {
  3. public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
  4. {
  5. Exception exception = null;
  6.  
  7. try
  8. {
  9. await actualReturnValue;
  10. await postAction();
  11. }
  12. catch (Exception ex)
  13. {
  14. exception = ex;
  15. throw;
  16. }
  17. finally
  18. {
  19. finalAction(exception);
  20. }
  21. }
  22.  
  23. public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
  24. {
  25. Exception exception = null;
  26.  
  27. try
  28. {
  29. var result = await actualReturnValue;
  30. await postAction();
  31. return result;
  32. }
  33. catch (Exception ex)
  34. {
  35. exception = ex;
  36. throw;
  37. }
  38. finally
  39. {
  40. finalAction(exception);
  41. }
  42. }
  43.  
  44. public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
  45. {
  46. return typeof (InternalAsyncHelper)
  47. .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
  48. .MakeGenericMethod(taskReturnType)
  49. .Invoke(null, new object[] { actualReturnValue, action, finalAction });
  50. }
  51. }

更多

我会通过添加一些用例来改进这篇文章:

  • 定义属性来控制拦截逻辑
  • 使用方法参数
  • 操纵返回值
  • ...

虽然您可以从MeasureDurationInterceptor示例开始,但请遵循本文的更新以获取具体示例。

ABP AOP 用例的更多相关文章

  1. spring aop 样例

    基于注解的AOP 方式 1.加入jar包 com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver- ...

  2. .Net中的AOP系列之《间接调用——拦截方法》

    返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...

  3. 借助 AOP 为 Java Web 应用记录性能数据

    作为开发者,应用的性能始终是我们最感兴趣的话题之一.然而,不是所有的开发者都对自己维护的应用的性能有所了解,更别说快速定位性能瓶颈并实施解决方案了. 今年北京 Velocity 的赞助商大多从事 AP ...

  4. Spring的配置文件ApplicationContext.xml配置头文件解析

    Spring的配置文件ApplicationContext.xml配置头文件解析 原创 2016年12月16日 14:22:43 标签: spring配置文件 5446 spring中的applica ...

  5. 这一次搞懂SpringBoot核心原理(自动配置、事件驱动、Condition)

    @ 目录 前言 正文 启动原理 事件驱动 自动配置原理 Condition注解原理 总结 前言 SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本 ...

  6. java面试知识迷你版

    java基础JUC.AQSJVM类加载过程mybatisSpringspringboot设计模式数据库redis网络问题认证授权Nginxlinux其他lombok消息队列ES缓存分库分表设计高并发系 ...

  7. 一张图彻底搞懂Spring循环依赖

    1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...

  8. 高频面试题:一张图彻底搞懂Spring循环依赖

    1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...

  9. Aspect Oriented Programming using Interceptors within Castle Windsor and ABP Framework AOP

    http://www.codeproject.com/Articles/1080517/Aspect-Oriented-Programming-using-Interceptors-wit Downl ...

随机推荐

  1. HashMap中 工具方法tableSizeFor的作用

    [转] https://blog.csdn.net/fan2012huan/article/details/51097331 首先看下该方法的定义以及被使用的地方 static final int t ...

  2. 练手WPF(三)——扫雷小游戏的简易实现(下)

    十四.响应鼠标点击事件    (1)设置对应坐标位置为相应的前景状态 /// <summary> /// 设置单元格图样 /// </summary> /// <para ...

  3. Asp.net MVC 中的TempData对象的剖析

    另一篇文章,也对TempData 做了很详细的介绍,链接地址:https://www.jianshu.com/p/eb7a301bc536   . MVC中的 TempData 可以在Controll ...

  4. Android之okhttp实现socket通讯(非原创)

    文章大纲 一.okhttp基础介绍二.socket通讯代码实战三.项目源码下载四.参考文章   一.okhttp基础介绍 https://www.jianshu.com/p/e3291b7808e7 ...

  5. split("\\,")引起的java.lang.ArrayIndexOutOfBoundsException异常解决方案

    由split("\,")引起的指标越界异常 如果字符串最后分隔符里的字段为空,使用split("\\,")进行切割时,最后的空字段不会切割 例如"a, ...

  6. MySQL 主从复制(实时热备)原理与配置

    MySQL是现在普遍使用的数据库,但是如果宕机了必然会造成数据丢失.为了保证MySQL数据库的可靠性,就要会一些提高可靠性的技术.MySQL主从复制可以做到实时热备数据.本文介绍MySQL主从复制原理 ...

  7. CentOS添加用户,管理员权限

    原文链接:https://www.linuxidc.com/Linux/2012-03/55629.htm 1.添加普通用户 [root@server ~]# useradd admin        ...

  8. 提升ML.NET模型的准确性

    ML.NET是一个面向.NET开发人员的开源.跨平台的机器学习框架. 使用ML.NET,您可以轻松地为诸如情绪分析.价格预测.销售分析.推荐.图像分类等场景构建自定义机器学习模型. ML.NET从0. ...

  9. selenium的web自动化实战

    selenium自动化原理: 1.通过各种语言(python,java,ruby等)调用接口库 2.通过浏览器驱动(web driver)来驱动浏览器 利用Python自动化的环境安装: 1.pyth ...

  10. react中antd的表格自定义展开

    antd的表格官方案例中给出的都是固定的图表展开,在做需求的时候,需要使用点击最后一列,然后出现展开内容,实现效果图如下 在最开始设置一个全局变量 const keys = [];在设置列参数的函数中 ...