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 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 -- ::, [ ] .Interceptors.MeasureDurationInterceptor -
GetCurrentLoginInformations executed in , 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 -- ::, [ ] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: GetCurrentLoginInformations executed in 4.964 milliseconds.
INFO -- ::, [ ] rceptors.MeasureDurationAsyncInterceptor - MeasureDurationAsyncInterceptor: GetCurrentLoginInformations executed in , 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[],
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(); //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示例开始,但请遵循本文的更新以获取具体示例。
ABP AOP 用例的更多相关文章
- spring aop 样例
基于注解的AOP 方式 1.加入jar包 com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver- ...
- .Net中的AOP系列之《间接调用——拦截方法》
返回<.Net中的AOP>系列学习总目录 本篇目录 方法拦截 PostSharp方法拦截 Castle DynamicProxy方法拦截 现实案例--数据事务 现实案例--线程 .Net线 ...
- 借助 AOP 为 Java Web 应用记录性能数据
作为开发者,应用的性能始终是我们最感兴趣的话题之一.然而,不是所有的开发者都对自己维护的应用的性能有所了解,更别说快速定位性能瓶颈并实施解决方案了. 今年北京 Velocity 的赞助商大多从事 AP ...
- Spring的配置文件ApplicationContext.xml配置头文件解析
Spring的配置文件ApplicationContext.xml配置头文件解析 原创 2016年12月16日 14:22:43 标签: spring配置文件 5446 spring中的applica ...
- 这一次搞懂SpringBoot核心原理(自动配置、事件驱动、Condition)
@ 目录 前言 正文 启动原理 事件驱动 自动配置原理 Condition注解原理 总结 前言 SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本 ...
- java面试知识迷你版
java基础JUC.AQSJVM类加载过程mybatisSpringspringboot设计模式数据库redis网络问题认证授权Nginxlinux其他lombok消息队列ES缓存分库分表设计高并发系 ...
- 一张图彻底搞懂Spring循环依赖
1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...
- 高频面试题:一张图彻底搞懂Spring循环依赖
1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...
- 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 ...
随机推荐
- Fira Code:适合程序员的编程字体
#Fira Code Fira 是 Mozilla 公司 主推的字体系列.Fira Code 是其中的一员,专为写程序而生.出来具有等宽等基本属性外,还加入了编程连字特性(ligatures). Fi ...
- JMeter 使用 http长连接 |史上最全
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 疯狂创客圈(笔者尼恩创建的高并发研习社群)Springcloud 高并发系列文章,将为大家介绍三个版本的 高并发秒杀: ...
- Vscode 打字特效插件Power Mode安装使用说明
壹 ❀ 引 我记得在17年使用atom编辑器的时候,使用过一款打字特效的插件,只要我们输入代码,代码上方就会有与代码颜色对应的星星效果,今天脑抽突然想起了这个中二插件,搜索了一番成功安装,大致效果如 ...
- PHP 的异步并行和协程 C 扩展 Swoole (附链接)
PHP的异步.并行.高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列, ...
- C# WebClient,HttpClient,WebRequest
static void WebClientDemo() { string url = "https://www.cnblogs.com/Fred1987/p/11843418.html&qu ...
- C#中对文件File常用操作方法的工具类
场景 C#中File类的常用读取与写入文件方法的使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/99693983 注: 博客 ...
- jQuery之概念以及基本使用
1. jQuery的概述 1.1 jQuery的概念 jQuery是一个快速.简洁的JavaScript库,其设计的宗旨是“Write Less,Do More” jQuery主要是封装了JavaSc ...
- iOS开发时获取第一响应者
上篇中提到键盘相应时间中用到了获取当前第一响应者的方法是苹果的是有方法,无法上传到App Store,本文将介绍一种非常简单的且未用到私有API的方法来获取当前第一响应者. 实现思路:用到的iOS A ...
- nRF24L01+启用自动应答ACK及自动重发的实战分享
ACK模式(自动应答)功能的官方说明 当用W_TX_PAYLOAD命令对发送端TX FIFO写数据时,将数据打包后,数据包中包控制字段NO_ACK 标志位复位.接收端接收到一帧有效数据后, 产生RX_ ...
- [b0019] python 归纳 (五)_类装饰器
总结: 类装饰器, 本质是一个函数,输入一个类,返回一个类 Case 1 啥都没做 def deco(in_class): return in_class @deco class Cat: def _ ...