本文介绍AOP编程的基本概念、Castle DynamicProxy(DP)的基本用法,使用第三方扩展实现对异步(async)的支持,结合Autofac演示如何实现AOP编程。html
百科中关于AOP的解释:git
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点……是函数式编程的一种衍生范型。利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。程序员
在AOP中,咱们关注横切点,将通用的处理流程提取出来,咱们会提供系统通用功能,并在各业务层中进行使用,例如日志模块、异常处理模块等。经过AOP编程实现更加灵活高效的开发体验。github
动态代理是实现AOP的一种方式,即在开发过程当中咱们不须要处理切面中(日志等)的工做,而是在运行时,经过动态代理来自动完成。Castle DynamicProxy是一个实现动态代理的框架,被不少优秀的项目用来实现AOP编程,EF Core、Autofac等。编程
咱们来看两段代码,演示AOP的好处。在使用AOP以前:架构
public class ProductRepository : IProductRepository { private readonly ILogger logger; public ProductRepository(ILogger logger) { this.logger = logger; } public void Update(Product product) { //执行更新操做 //...... //记录日志 logger.WriteLog($"产品{product}已更新"); } }
在使用AOP以后:mvc
public class ProductRepository : IProductRepository { public void Update(Product product) { //执行更新操做 //...... } }
能够明显的看出,在使用以前咱们的ProductRepository依赖于ILogger,并在执行Update操做之后,要写出记录日志的代码;而在使用以后,将日志记录交给动态代理来处理,下降了很多的开发量,即便碰见略微马虎的程序员,也不耽误咱们日志的记录。框架
那该如何实现这样的操做呢?asp.net
Castle.Core
IInterceptor
接口public class LoggerInterceptor : IInterceptor { private readonly ILogger logger; public LoggerInterceptor(ILogger logger) { this.logger = logger; } public void Intercept(IInvocation invocation) { //获取执行信息 var methodName = invocation.Method.Name; //调用业务方法 invocation.Proceed(); //记录日志 this.logger.WriteLog($"{methodName} 已执行"); } }
static void Main(string[] args) { ILogger logger = new ConsoleLogger(); Product product = new Product() { Name = "Book" }; IProductRepository target = new ProductRepository(); ProxyGenerator generator = new ProxyGenerator(); IInterceptor loggerIntercept = new LoggerInterceptor(logger); IProductRepository proxy = generator.CreateInterfaceProxyWithTarget(target, loggerIntercept); proxy.Update(product); }
至此,咱们已经完成了一个日志拦截器,其它业务须要用到日志记录的时候,也可经过建立动态代理的方式来进行AOP编程。异步
可是,调用起来仍是比较复杂,须要怎么改进呢?固然是使用依赖注入(DI)了。
Autofac集成了对DynamicProxy的支持,咱们须要引用Autofac.Extras.DynamicProxy
,而后建立容器、注册服务、生成实例、调用方法,咱们来看下面的代码:
ContainerBuilder builder = new ContainerBuilder(); //注册拦截器 builder.RegisterType<LoggerInterceptor>().AsSelf(); //注册基础服务 builder.RegisterType<ConsoleLogger>().AsImplementedInterfaces(); //注册要拦截的服务 builder.RegisterType<ProductRepository>().AsImplementedInterfaces() .EnableInterfaceInterceptors() //启用接口拦截 .InterceptedBy(typeof(LoggerInterceptor)); //指定拦截器 var container = builder.Build(); //解析服务 var productRepository = container.Resolve<IProductRepository>(); Product product = new Product() { Name = "Book" }; productRepository.Update(product);
对这段代码作一下说明:
AsSelf
,由于服务拦截时使用的是拦截器的实例,这种注册方式能够保证容器可以解析到拦截器。EnableInterfaceInterceptors
方法,表示开启接口拦截;InterceptedBy
方法传入拦截器,指定拦截器的方式有两种,一种是咱们代码中的写法,对服务是无入侵的,所以推荐这种用法。另外一种是经过Intercept
特性来进行关联,例如咱们上面的代码能够写为ProductRepository
类上添加特性[Intercept(typeof(LoggerInterceptor))]
上面咱们说到动态代理只对公共接口方法、类中的虚方法生效,你是否想过为何?
其实,动态代理是在运行时为咱们动态生成了一个代理类,经过Generator
生成的时候返回给咱们的是代理类的实例,而只有接口中的方法、类中的虚方法才能够在子类中被重写。
若是不使用动态代理,咱们的代理服务应该是什么样的呢?来看下面的代码,让咱们手工建立一个代理类:
如下是我对代理类的理解,请你们辩证的看待,若是存在不正确的地方,还望指出。
为接口使用代理:
public class ProductRepositoryProxy : IProductRepository { private readonly ILogger logger; private readonly IProductRepository target; public ProductRepositoryProxy(ILogger logger, IProductRepository target) { this.logger = logger; this.target = target; } public void Update(Product product) { //调用IProductRepository的Update操做 target.Update(product); //记录日志 this.logger.WriteLog($"{nameof(Update)} 已执行"); } } //使用代理类 IProductRepository target = new ProductRepository(); ILogger logger = new ConsoleLogger(); IProductRepository productRepository = new ProductRepositoryProxy(logger, target);
为类使用代理:
public class ProductRepository : IProductRepository { //改写为虚方法 public virtual void Update(Product product) { //执行更新操做 //...... } } public class ProductRepositoryProxy : ProductRepository { private readonly ILogger logger; public ProductRepositoryProxy(ILogger logger) { this.logger = logger; } public override void Update(Product product) { //调用父类的Update操做 base.Update(product); //记录日志 this.logger.WriteLog($"{nameof(Update)} 已执行"); } } //使用代理类 ILogger logger = new ConsoleLogger(); ProductRepository productRepository = new ProductRepositoryProxy(logger);
若是你站在应用程序的角度来看,异步只是微软的一个语法糖,使用异步的方法返回结果为一个Task或Task
Castle.Core.AsyncInterceptor
是帮咱们处理异步拦截的框架,经过使用该框架能够下降异步处理的难度。
咱们本节仍然结合Autofac进行处理,首先对代码进行改造,将ProductRepository.Update
方法改成异步的。
public class ProductRepository : IProductRepository { public virtual Task<int> Update(Product product) { Console.WriteLine($"{nameof(Update)} Entry"); //执行更新操做 var task = Task.Run(() => { //...... Thread.Sleep(1000); Console.WriteLine($"{nameof(Update)} 更新操做已完成"); //返回执行结果 return 1; }); //返回 return task; } }
接下来定义咱们的异步拦截器:
public class LoggerAsyncInterceptor : IAsyncInterceptor { private readonly ILogger logger; public LoggerAsyncInterceptor(ILogger logger) { this.logger = logger; } /// <summary> /// 同步方法拦截时使用 /// </summary> /// <param name="invocation"></param> public void InterceptSynchronous(IInvocation invocation) { throw new NotImplementedException(); } /// <summary> /// 异步方法返回Task时使用 /// </summary> /// <param name="invocation"></param> public void InterceptAsynchronous(IInvocation invocation) { throw new NotImplementedException(); } /// <summary> /// 异步方法返回Task<T>时使用 /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="invocation"></param> public void InterceptAsynchronous<TResult>(IInvocation invocation) { //调用业务方法 invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation); } private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) { //获取执行信息 var methodName = invocation.Method.Name; invocation.Proceed(); var task = (Task<TResult>)invocation.ReturnValue; TResult result = await task; //记录日志 this.logger.WriteLog($"{methodName} 已执行,返回结果:{result}"); return result; } }
IAsyncInterceptor
接口是异步拦截器接口,它提供了三个方法:
InterceptSynchronous
:拦截同步执行的方法InterceptAsynchronous
:拦截返回结果为Task的方法InterceptAsynchronous<TResult>
:拦截返回结果为Task
在咱们上面的代码中,只实现了InterceptAsynchronous<TResult>
方法。
因为IAsyncInterceptor
接口和DP框架中的IInterceptor
接口没有关联,因此咱们还须要一个同步拦截器,此处直接修改旧的同步拦截器:
public class LoggerInterceptor : IInterceptor { private readonly LoggerAsyncInterceptor interceptor; public LoggerInterceptor(LoggerAsyncInterceptor interceptor) { this.interceptor = interceptor; } public void Intercept(IInvocation invocation) { this.interceptor.ToInterceptor().Intercept(invocation); } }
从代码中能够看到,异步拦截器LoggerAsyncInterceptor
具备名为ToInterceptor()
的扩展方法,该方法能够将IAsyncInterceptor
接口的对象转换为IInterceptor
接口的对象。
接下来咱们修改DI的服务注册部分:
ContainerBuilder builder = new ContainerBuilder(); //注册拦截器 builder.RegisterType<LoggerInterceptor>().AsSelf(); builder.RegisterType<LoggerAsyncInterceptor>().AsSelf(); //注册基础服务 builder.RegisterType<ConsoleLogger>().AsImplementedInterfaces(); //注册要拦截的服务 builder.RegisterType<ProductRepository>().AsImplementedInterfaces() .EnableInterfaceInterceptors() //启用接口拦截 .InterceptedBy(typeof(LoggerInterceptor)); //指定拦截器 var container = builder.Build();
以上即是经过IAsyncInterceptor
实现异步拦截器的方式。除了使用这种方式,咱们也能够在在动态拦截器中判断返回结果手工处理,此处再也不赘述。
经过上面的介绍,咱们已经了解了AOP的基本用法,可是如何用在ASP.NET Core
中呢?
咱们知道,AOP的初衷就是对使用者保持黑盒,经过抽取切面进行编程,而这两个问题偏偏须要咱们对使用者进行修改,违背了SOLID原则。
那么,若是咱们要在MVC中使用AOP,有什么方法呢?其实MVC已经为咱们提供了两种实现AOP的方式:
这两种方式更符合咱们的编码习惯,也体现了MVC框架的特性。
综上,不建议在MVC中对Controller使用DP。若是采用NLayer架构,则能够在Application层、Domain层使用DP,来实现相似数据审计、SQL跟踪等处理。
虽然不推荐,但仍是给出代码,给本身多一条路:
services.AddMvc() .AddControllersAsServices();
builder.RegisterType<ProductController>() .EnableClassInterceptors() .InterceptedBy(typeof(ControllerInterceptor));
[HttpPost] public virtual Task<int> Update(Product product) { return this.productRepository.Update(product); }
在建立代理类时(不管是class或interface),都有两种写法:WithTarget和WithoutTarget,这两种写法有必定的区别,withTarget须要传入目标实例,而withoutTarget则不用,只须要传入类型便可。