aop in .net
AOP是所有现代OOP语言开发框架中都会具备的东西,随着Spring框架的普及,这个东西已经被玩烂了。可是很多人仍旧处于知其然不知其所以然的状态。本文将基于.NET环境探讨实现AOP的底层原理。
文中部分代码样例截图摘自Matthew D. Groves的《AOP in .NET》,推荐大家购买阅读。
中间件与过滤器原理截图摘自微软官方文档,请查看文中链接。
本文主要分为以下部分:
- 基础概念
- ASP.NET Core框架内置的AOP
- 中间件
- 过滤器
- AOP in .NET
- 织入
- 代理模式
- PostSharp与动态代理的区别
- 动态代理模式
- Castle.DynamicProxy
- Autofac + Castle.DynamicProxy
基础概念
面向对象编程通过类的继承机制来复用代码,这在大多数情况下这很有用。但是随着软件系统的越来复杂,出现了一些通过OOP处理起来相当费力的关注点,比如:日志记录,权限控制,缓存,数据库事务提交等等。它们的处理逻辑分散于各个模块,各个函数之中,这违反了Don‘t Repeat Yourself以及关注度点分离原则,不利于后期的代码维护。所谓AOP(面向切面编程),就是将这些关注点,看作一个个切面,捕获这些切面并将其处理程序模块化的过程。
ASP.NET Core框架内置的AOP
在.ASP.NET Core框架中,微软内置了一些处理AOP逻辑的机制。可惜的是它内置的IoC框架,不支持语言级别的AOP。
中间件机制
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write
ASP.NET Core框架本身就是由一系列中间件组成的,它本身内置的异常处理,路由转发,权限控制,也就是在上述图中的请求管道中实现的。所以我们也完全可以基于中间件机制,实现AOP。
以异常处理为例,我可以将try catch加入到next方法的前后,以捕获后续运行过程中未处理的异常,并进行统一处理。代码如下:
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlerMiddleware(RequestDelegate next )
{
_next = next;
}
public async Task Invoke(HttpContext context, IHostingEnvironment env,ILogger<ExceptionHandlerMiddleware> logger)
{
try
{
await _next(context);
}
catch (Exception ex)
{
logger.LogError(new EventId(ex.HResult), ex, ex.Message);
await context.HandleExceptionAsync(ex, env.IsDevelopment());
}
}
}
过滤器机制
https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
过滤器本质上是由路由中间件(Routing Middleware)的请求管道实现的,如下图所示。
开发者通过定义并注册相应的过滤器,就能基于这个请求管道,来处理对应的关注点。Asp.NET Core 的过滤器执行顺序如下图:
我们可以基于中间件或者过滤器机制,完成简单的开发。可惜的是,这些并不是语言级别的aop。asp.net core是一个开发框架,它为了方便你开发,给你内置了一些条条框框,你照着做确实能够解决大部分问题。但是脱离了它,该如何实现AOP呢?
AOP in .NET
下面我们开始真正进入主题。
织入
要实现AOP,关键在于织入(“weaving”)。如上图代码中,LogAspect被织入到BusinessModule1中,Mehtod1在执行前后,就会分别调用BeginMethod以及EndMethod方法来处理日志记录逻辑。LogAspect如果能够织入到所有需要日志记录的方法中,我们就将分散的日志处理代码模块化成了一个统一的切面处理程序:LogAspect。这就是AOP。实现织入的方式分为两种:编译时织入、运行时织入。
当你使用C#创建.NET项目时,该项目将被编译为CIL(也称为MSIL,IL和bytecode)作为程序集(DLL或EXE文件)。 下图说明了这个过程。然后,公共语言运行时(CLR)可以将CIL转换成真实的机器指令(通过称为即时编译的过程,或JIT)。
《aop in .net》
所谓编译时织入,就是对编译产生的CIL做手脚,通过修改编译好的CIL文件,来达到织入的效果,如下图所示。编译时织入可通过PostSharp实现。
运行时织入则是在程序运行时来完成的织入,一般是通过DynamicProxy(动态代理)程序(Castle.Core)配合IoC容器(Autofac,StructureMap等)来实现的。更多样例可查看:https://github.com/TylerBrinks/Snap
这两种织入模式各有利弊,下面总结几点:
- PostSharp是在编译时进行的,DynamicProxy在运行时进行。所以一个会增加编译时间,一个会降低运行效率。
- 由于PostSharp需要安装额外的编译程序,这意味着没有安装PostSharp的机器,无法正确编译你开发的程序。这不利于应用在开源项目中,也不利于部署CI/CD的自动化编译服务。
- PostSharp为收费的商业项目,需要付费使用。而动态织入所需的IoC框架,以及动态代理组件Castle.Core都是开源免费的。
- DynamicProxy必须使用IoC容器,对于UI对象或领域对象,并不适合或不可能通过容器获取实例。PostSharp没有这个问题。
- DynamicProxy比PostSharp更易于进行单元测试。
- DynamicProxy在运行时执行,因此在编译完成后,你仍可以通过修改配置文件来修改切面配置。PostSharp做不到这一点。
- DynamicProxy的拦截器被附加到类的所有方法中,而PostSharp能够更精准的拦截。
- PostSharp能够在static方法、private方法、属性中织入AOP,而DynamicProxy做不到这一点。
你可以根据自己的需要选择合适的织入方式,不过由于PostSharp为商业付费项目,我后面不再对其进行过多讲解,需要的朋友可自行阅读《AOP in .NET》中的相关内容,或查阅PostSharp官网。
本文后面将主要通过代码样例讲述如何基于动态代理实现运行时织入。
(未完待续)
原文地址:https://www.cnblogs.com/wswind/p/aop_in_dotnet.html