ABP AOP 用例
介绍
在本文中,我将向您展示如何创建拦截器来实现AOP技术。我将使用ASP.NET Boilerplate(ABP)作为基础应用程序框架和Castle Windsor作为拦截库。这里描述的大多数技术对于使用独立于ABP框架的Castle Windsor也是有效的。
什么是面向方面编程(AOP)和方法拦截?
维基百科:“ 在计算中,面向方面的编程(AOP)是一种编程范式,旨在增加模块性允许的分离横切关注它通过添加额外的行为,以现有的代码(咨询)这样做。无需修改代码而是分别指定哪个代码通过“切入点”规范进行修改。
在应用程序中,我们可能会有一些重复/类似的代码用于日志记录,授权,验证,异常处理等等...
手动方式(无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
更容易写入和读取的属性所代替。
幸运的是,所有这些和更多的由ABP框架自动完成。但是,您可能希望创建一些特定于您自己的应用程序需求的自定义拦截逻辑。这就是为什么我创建了这篇文章。
关于示例项目
我从ABP 启动模板(包括模块零)创建了一个示例项目,并添加到Github仓库。
创建拦截器
我们先来看一个简单的拦截器来测量方法的执行时间:
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 executionvar 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 executionvar 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 executionvar 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 executionvar stopwatch = Stopwatch.StartNew();//Calling the actual method, but execution has not been finished yet invocation.Proceed();//Wait task execution and modify return valueif (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 executionvar 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
示例开始,但请遵循本文的更新以获取具体示例。
转载于:https://www.cnblogs.com/chunjin/p/6801362.html
ABP AOP 用例相关推荐
- aop实现原理_从宏观的实现原理和设计本质入手,带你理解 AOP 框架的原理
点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:FeelsChaotic juejin.im/post/5c57b2d5e51d45 ...
- 详解:设计模式之-单例设计模式
分享一波:程序员赚外快-必看的巅峰干货 前言 近期预计1-2周左右会更新设计模式专题文章. 单例设计模式:保证在一个JVM中,只能存在一个实例. 应用场景:Servlet,Spring IOC,线程池 ...
- Dubbo的SPI机制对比传统的SPI做了哪些改进?Dubbo的IOC和AOP
文章目录 1. JAVA的SPI机制 2. Dubbo的SPI机制 3. Dubbo的SPI源码解析 3.1 获取接口对应的 ExtensionLoader 3.2 根据入参的http获取对应的htt ...
- 从宏观的实现原理和设计本质入手,带你理解 AOP 框架的原理
点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:FeelsChaotic juejin.im/post/5c57b2d5e51d45 ...
- 深入理解Spring两大特性:IoC和AOP
Spring Boot 专栏:https://blog.csdn.net/dkbnull/category_9278145.html Spring Cloud 专栏:https://blog.csdn ...
- 从AOP到Spring AOP
文章目录 一.前言 二.AOP基础知识 2.1 AOP 2.1.1 引子:AOP,AOP是什么? 2.1.2 使用对比的方式理解AOP--AOP与OOP 2.1.3 使用对比的方式理解AOP--静态A ...
- 用c# 实现设计器 DesignSurface
DesignSurface 实现设计器问题? http://topic.csdn.net/u/20090419/02/4c0fe387-c019-4159-ac60-71c04495e2b2.html ...
- Spring-beans架构设计原理
IOC IOC,官方给的定义是依赖注入(Dependency Injection)或者控制反转(Inversion of Control).光从字面理解起来还是比较费劲.但任何一种模式都是来自人的行为 ...
- SpringBoot核心原理:自动配置、事件驱动、Condition
点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/l6108003/article/ details/106966386 前言 SpringBoot是Spring的包装,通过自动 ...
- 面试官:什么是静态代理?什么是动态代理?注解、反射你会吗?
前言 开场 一位穿着蓝色衬衫,牛仔裤,拿着一个白色保温杯的中年男子急匆匆地坐在你对面,看样子是项目上的东西很急,估摸面试时间不会太长,这样一想心情放松了许多-(后来我就被打脸了) 面试开始 面 ...
最新文章
- 清华北大等12校首批开设“未来技术学院”,它们都设置了哪些专业?
- 类的练习2——python编程从入门到实践
- python批量下载网页文件-python使用selenium实现批量文件下载
- 一个设计反模式——究竟什么是轮子理论
- 数据结构与算法之堆排序
- 终于等到放学了的飞鸽传书
- 干货!Kaggle座头鲸识别落下帷幕,冠军方案解读
- 《Python入门到精通》Python基础语法
- 计算机理论python字符串作业_[Python基础 ] Day_07_作业参考答案
- mac下git安装与使用
- android定位!每个程序员都必须掌握的8种数据结构!完整版开放下载
- 腾讯会议共享屏幕,ppt如何使用演讲者模式
- Web应用程序设计(一)——基础开发环境搭建
- ps插件套装imagenomic磨皮滤镜安装教程
- 基于TI Sitara系列AM3352/AM3354/AM3359的Linux常用开发案例分享
- python简单实现爬取小说《天龙八部》,并在页面本地访问
- 推荐一部烂片《东方华尔街》
- WebForm CSS垂直虚线时间轴特效
- 交替打印A1B2C3-Java多线程实现方式
- iterm2连不上阿里云服务器
热门文章
- 渗透测试员完整职业指南
- Dart基础之Isolate
- 1、蛋白质二级结构预测方法
- workerman wss 配置备忘录
- 【WSN通信】基于matlab A_Star改进LEACH多跳传输协议【含Matlab源码 487期】
- python高阶知识之——列表推导式(63)
- 使用spire.doc for java为word添加页码、文字水印、书签(去除警告信息)
- 基于Python+djangoWeb的校园信息化统计平台
- php插入图片适应屏幕,PHPExcel:如何在首页页眉中插入图片并将其放大以适应其内容?...
- tp6 thinkswoole 使用极光curl请求时报错