Castle DynamicProxy基本用法(AOP)
本文介绍AOP编程的基本概念、Castle DynamicProxy(DP)的基本用法,使用第三方扩展实现对异步(async)的支持,结合Autofac演示如何实现AOP编程。
AOP
百科中关于AOP的解释:
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点……是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在AOP中,我们关注横切点,将通用的处理流程提取出来,我们会提供系统通用功能,并在各业务层中进行使用,例如日志模块、异常处理模块等。通过AOP编程实现更加灵活高效的开发体验。
DynamicProxy的基本用法
动态代理是实现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之后:
public class ProductRepository : IProductRepository
{
public void Update(Product product)
{
//执行更新操作
//......
}
}
可以明显的看出,在使用之前我们的ProductRepository依赖于ILogger,并在执行Update操作以后,要写出记录日志的代码;而在使用之后,将日志记录交给动态代理来处理,降低了不少的开发量,即使遇见略微马虎的程序员,也不耽误我们日志的记录。
那该如何实现这样的操作呢?
- 首先,引用
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的集成
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))] - 拦截器的注册,可以注册为类型拦截器,也可以注册为命名的拦截器,使用上会有一些差异,主要在拦截器的关联上,此部分可以参考Autofac官方文档。我们示例用的是类型注册。
- 拦截器只对公共的接口方法、类中的虚方法有效,使用时需要特别注意。
DynamicProxy的基本原理
上面我们说到动态代理只对公共接口方法、类中的虚方法生效,你是否想过为什么?
其实,动态代理是在运行时为我们动态生成了一个代理类,通过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);
异步(async/await)的支持
如果你站在应用程序的角度来看,异步只是微软的一个语法糖,使用异步的方法返回结果为一个Task或Task的对象,这对于DP来说和一个int类型并无差别,但是如果我们想要在拦截中获取到真实的返回结果,就需要添加一些额外的处理。
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实现异步拦截器的方式。除了使用这种方式,我们也可以在在动态拦截器中判断返回结果手工处理,此处不再赘述。
探讨:ASP.NET MVC中的切面编程
通过上面的介绍,我们已经了解了AOP的基本用法,但是如何用在ASP.NET Core中呢?
- MVC控制器的注册是在Services中完成的,而Services本身不支持DP。这个问题可以通过整合Autofac重新注册控制器来完成,但是这样操作真的好吗?
- MVC中的控制器是继承自ControllerBase,Action方法是我们自定义的,不是某个接口的实现,这对实现AOP来说存在一定困难。这个问题可以通过将Action定义为虚方法来解决,但是这样真的符合我们的编码习惯吗?
我们知道,AOP的初衷就是对使用者保持黑盒,通过抽取切面进行编程,而这两个问题恰恰需要我们对使用者进行修改,违背了SOLID原则。
那么,如果我们要在MVC中使用AOP,有什么方法呢?其实MVC已经为我们提供了两种实现AOP的方式:
- 中间件(Middleware),这是MVC中的大杀器,提供了日志、Cookie、授权等一系列内置的中间件,从中可以看出,MVC并不想我们通过DP实现AOP,而是要在管道中做文章。
- 过滤器(Filter),Filter是 ASP.NET MVC的产物,曾经一度帮助我们解决了异常、授权等逻辑,在Core时代我们仍然可以采用这种方式。
这两种方式更符合我们的编码习惯,也体现了MVC框架的特性。
综上,不建议在MVC中对Controller使用DP。如果采用NLayer架构,则可以在Application层、Domain层使用DP,来实现类似数据审计、SQL跟踪等处理。
虽然不推荐,但还是给出代码,给自己多一条路:
- MVC控制器注册为服务
services.AddMvc()
.AddControllersAsServices();
- 重新注册控制器,配置拦截
builder.RegisterType<ProductController>()
.EnableClassInterceptors()
.InterceptedBy(typeof(ControllerInterceptor));
- 控制器中的Action定义为虚方法
[HttpPost]
public virtual Task<int> Update(Product product)
{
return this.productRepository.Update(product);
}
补充内容
- 2019年7月24日补充
在创建代理类时(无论是class或interface),都有两种写法:WithTarget和WithoutTarget,这两种写法有一定的区别,withTarget需要传入目标实例,而withoutTarget则不用,只需要传入类型即可。
参考文档
- Castle Dynamic 官方文档
- Autofac Type Interceptors 官方文档
- 使用Castle DynamicProxy(AOP)
- 框架学习与探究之AOP--Castle DynamicProxy
- 异步支持库 Castle.Core.AsyncInterceptor
- Cross Cutting Concerns in .NET Applications
Castle DynamicProxy基本用法(AOP)的更多相关文章
- 使用Castle DynamicProxy (AOP)
在本文中,我将引导您了解.NET环境中的面向方面编程(AOP)概念,以及如何使用Castle DynamicProxy创建和附加方面.在我们开始之前,让我快速介绍AOP和 IoC.如果您已经熟悉这些 ...
- 在 CAP 中使用 AOP ( Castle.DynamicProxy )
简介 本篇文章主要介绍如何在 CAP 中集成使用 Castle.DynamicProxy,Castle DynamicProxy 是一个用于在运行时动态生成轻量级.NET代理的库.代理对象允许在不修改 ...
- 基于Autofac, Castle.DynamicProxy的动态WCF解决方案(原创)
本方案解决了下面3个主要的问题: 1.减少配置,为了避免每次新增service都需要去修改配置文件,包括服务器端跟各个客户端的. 2.能够使用函数重载,泛型函数,以及泛型类. 3.使项目能够快速地在w ...
- Castle DynamicProxy
Introduction¶ Castle DynamicProxy is a library for generating lightweight .NET proxies on the fly at ...
- Castle.DynamicProxy Part 1: ClassProxy
1.Castle中代理对象的分类 总的来说,代理对象大概可以分为2大类: 1.继承类型的代理对象 一类是继承类型的代理类.即:有一个类A,它的代理类是B.B是继承自A的.调用代理类B中的方法时,可以通 ...
- castle.dynamicProxy学习笔记
目的: 可以将castle.dynamicProxy当成代码生成器,快速的生成自己想的代码.这个库经历了这么多年的测试,应该可以用了:D 概念: IInterceptor:拦截器 当方法(属性的本质是 ...
- AOP之Castle DynamicProxy 动态代理
这里主要介绍使用castle这个动态代理,在.net一些开源的框架里可以找到它的影子,就连微软的rchard也是使用这个进行方法拦截等可以基于这个进行方法拦截,在这个方面PostSharp算是比较好用 ...
- 几种Aop实现及Castle.DynamicProxy的使用
AoP(Aspect Oriented Programming,面向切面编程) .Net平台AOP技术研究 简单实现 通过继承实现 public interface ICoding { void Do ...
- Castle中AdditionalInterfaces用法介绍
首先见下图(图一),其中FooController是一个没有实现任何Interface的空类.需要实现的效果是:通过FooController对象调用FooService的Do方法.设置这一不常见的场 ...
随机推荐
- Django总结目录
Django总结目录 1. django框架简介及自定义简易版框架 2. 路由层 3. 视图层 4. 模板层 5. 模型层 5.1 基本操作 5.2 多表操作 5.3 进阶相关 6. 组件 6.1 a ...
- Unity3D 卡通描边之控制线条粗细
一.前言 之前我发表过一篇Unity3D 卡通渲染 基于退化四边形的实时描边,最重要的实时描边已经实现了,本文接下来要完善一下它. 在之前的实时描边中,使用了几何着色器中的LineStream来进行绘 ...
- ef6+mysql的bug
entityFramework6在mysql数据库下,用linq进行排序会出现一个bug. Expression<Func<blog, bool>> expr_filter=p ...
- 阿里巴巴 Service Mesh 落地的架构与挑战
点击下载<不一样的 双11 技术:阿里巴巴经济体云原生实践> 本文节选自<不一样的 双11 技术:阿里巴巴经济体云原生实践>一书,点击上方图片即可下载! 作者 | 方克明(溪翁 ...
- vue axios 总结篇
1.npm --save 和 --save-dev 有什么区别 发布到线上的叫生产环境~,在本地开发的时候叫开发环境,--save就是会打包到线上去并且在线上环境能用到的,比如你npm install ...
- js 防抖、截流
突发奇想,在触发事件的时候,一些会频繁触发的事件会不会造成资源的浪费或者大量的计算造成页面卡顿,比如onresize,onscroll,onmousemove等事件. 然后就引出了一个新知识点:防抖. ...
- XML与JSON解析
[XML简介] XML在线校验工具: http://tool.oschina.net/codeformat/xml 可扩展标记语言(EXtensible Markup Language) 一种标记语言 ...
- Codeforces Round #605 (Div. 3) E - Nearest Opposite Parity
题目链接:http://codeforces.com/contest/1272/problem/E 题意:给定n,给定n个数a[i],对每个数输出d[i]. 对于每个i,可以移动到i+a[i]和i-a ...
- Balls in the Boxes
Description Mr. Mindless has many balls and many boxes,he wants to put all the balls into some of th ...
- solr索引基本原理
solr是一个全局检索引擎,能够快速地从大量的文本数据中选出你所需要的数据,而你只需要提供相应的关键词进行检索.solr的高效率查询靠的是底层强大的索引库,所以solr最关键的技术也是其底层的索引设计 ...