本文介紹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則不用,只須要傳入類型便可。