国内开源社区巨作AspectCore-Framework入门
前些天和張隊(duì)(善友),lemon(浩洋),斌哥(項(xiàng)斌)等MVP大咖一塊兒吃飯,大家聊到了lemon名下的AOP這個(gè)項(xiàng)目,我這小白聽(tīng)得一臉懵逼,后面回來(lái)做了一下功課,查了下資料,在lemon的Github上把這個(gè)項(xiàng)目學(xué)習(xí)了一下,收獲頗豐,讓我這個(gè)沒(méi)有接觸過(guò)AOP的Coder嘆為觀止,陷入了對(duì)lemon的深深崇拜,在這里把學(xué)習(xí)的新的體會(huì)分享給大家.
什么是AOP?
在軟件業(yè),AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。
有點(diǎn)深?yuàn)W, 舉個(gè)栗子
如果說(shuō)之前做的一個(gè)系統(tǒng)專門給內(nèi)部的服務(wù)提供接口的,因?yàn)槭窃趦?nèi)網(wǎng)中訪問(wèn),所以就沒(méi)有加上認(rèn)證服務(wù),現(xiàn)在這個(gè)系統(tǒng)要公開(kāi)出來(lái),同樣的那套接口要給外部系統(tǒng)服務(wù)了,那么此時(shí),就要進(jìn)行認(rèn)證,認(rèn)證通過(guò)才能獲取接口的數(shù)據(jù).
傳統(tǒng)的做法是,修改每一個(gè)接口.這樣就會(huì)造成代碼改動(dòng)過(guò)大,很恐怖.
?
這個(gè)時(shí)候AOP就可以登場(chǎng)了,我們可以在這一類服務(wù)的前面,加上一個(gè)一系列上一刀,在切出來(lái)的裂縫里面插入認(rèn)證方法.
?
然而,怎么插入這個(gè)切面是關(guān)鍵.AOP 實(shí)現(xiàn)會(huì)采用一些常見(jiàn)方法:
- 使用預(yù)處理器(如 C++ 中的預(yù)處理器)添加源代碼。
- 使用后處理器在編譯后的二進(jìn)制代碼上添加指令。
- 使用特殊編譯器在編譯時(shí)添加代碼。
- 在運(yùn)行時(shí)使用代碼攔截器攔截執(zhí)行并添加所需的代碼。
但是常見(jiàn)還是后處理和代碼攔截兩種方式
-
后處理,或者叫 靜態(tài)織入
指使用 AOP 框架提供的命令進(jìn)行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時(shí)增強(qiáng)或靜態(tài)織入。
在dotnet 中一般在編譯時(shí)通過(guò)在MSBiuld執(zhí)行自定義的Build Task來(lái)攔截編譯過(guò)程,在生成的程序集里插入自己的IL。
dotnet 框架代表: PostSharp
?
-
代碼攔截,或者叫 動(dòng)態(tài)代理
在運(yùn)行時(shí)在內(nèi)存中“臨時(shí)”生成 AOP 動(dòng)態(tài)代理類,因此也被稱為運(yùn)行時(shí)增強(qiáng)或動(dòng)態(tài)代理。
在dotnet 中一般在運(yùn)行時(shí)通過(guò)Emit技術(shù)生成動(dòng)態(tài)程序集和動(dòng)態(tài)代理類型從而對(duì)目標(biāo)方法進(jìn)行攔截。
dotnet 框架代表: Castle DynamicProxy 和 AspectCore
引用https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/0.AOP%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D.md
AspectCore-Framework的代碼攔截
我這里直接應(yīng)用AOP Demo中的一段代碼來(lái)說(shuō)說(shuō)這個(gè)攔截.
public class CustomInterceptor : AbstractInterceptor{public async override Task Invoke(AspectContext context, AspectDelegate next){try{Console.WriteLine("Before service call");await next(context);}catch (Exception){Console.WriteLine("Service threw an exception!");throw;}finally{Console.WriteLine("After service call");}}}?
代碼中,其實(shí)執(zhí)行到 await next(context)的時(shí)候,才會(huì)真正去調(diào)用那個(gè)被攔截的方法.
這樣,我們就可以靈活地在代碼調(diào)用前,調(diào)用后做我們想做的事情了.甚至可以把代碼包在一個(gè)try…catch...中來(lái)捕獲異常.
開(kāi)始AspcetCore的表演
新建一個(gè)web應(yīng)用程序后,從 Nuget 安裝 AspectCore.Extensions.DependencyInjection 包.
PM> Install-Package AspectCore.Extensions.DependencyInjection然后.我們就可以來(lái)定義我們的攔截器了,我定義了一個(gè)這樣的攔截器.
public override async Task Invoke(AspectContext context, AspectDelegate next){var logger = context.ServiceProvider.GetService<ILogger<AuthenticateInterceptor>>();try{var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();if (apiRequest == null || apiRequest.Name != "admin"){logger.LogWarning("unauthorized");return;}logger.LogInformation(apiRequest.Message);await next(context);}catch (Exception e){logger?.LogWarning("Service threw an exception!");throw;}finally{logger.LogInformation("Finished service call");}}當(dāng)ApiRequest為空或者Name不等于admin的時(shí)候之家返回并記錄未授權(quán).
否則,調(diào)用該調(diào)用的方法并記錄ApiRequest中的Message.
然后,我定義一個(gè)UserService.
using System;namespace AspceptCoreDemo {public interface IUserService{String GetUserName(ApiRequest req);}public class UserService : IUserService{public string GetUserName(ApiRequest req){if (req == null)return null;Console.WriteLine($"The User Name is {req.Name}");return req.Name;}} }在Controler中調(diào)用注入U(xiǎn)serServce并調(diào)用該Service.
using Microsoft.AspNetCore.Mvc;namespace AspceptCoreDemo.Controllers {[Route("api/[controller]")][ApiController]public class ValuesController : ControllerBase{private readonly IUserService _userService;public ValuesController(IUserService userService){_userService = userService;}[HttpPost]public ActionResult<string> Post(ApiRequest req){return _userService.GetUserName(req);}} }注冊(cè)IUserservice并在ConfigureServices中配置創(chuàng)建代理類型的容器:
public IServiceProvider ConfigureServices(IServiceCollection services){services.AddSingleton<IUserService, UserService>();services.AddMvc();services.AddDynamicProxy(config =>{config.Interceptors.AddTyped<AuthenticateInterceptor>();});return services.BuildAspectInjectorProvider();}需要注意的是紅色背景處,默認(rèn)的ConfigureService返回類型是空的,我們要修改成為返回類型是IServiceProvider.
1.全局?jǐn)r截
我們?cè)谏厦娴腃onfigureService配置的AuthenticateInterceptor默認(rèn)情況下是全局的,即這里的IUserService它會(huì)攔截,當(dāng)然如果新增了一個(gè)IRoleServce它也是會(huì)攔截的.
我把程序運(yùn)行起來(lái)用PostMan訪問(wèn)Api進(jìn)行測(cè)試.下圖是Post的數(shù)據(jù)和返回結(jié)果.
說(shuō)明接口是正常工作的,成功地把傳過(guò)去的Name原樣返回.
那么攔截器有沒(méi)有生效呢?我看看CMD的輸出.
如果我們修改一下Name不等于Admin,預(yù)期應(yīng)該是返回空,并且日志打印出未授權(quán),是不是這樣呢?
完美,與預(yù)期完全相同.
可以發(fā)現(xiàn),這正是我們?cè)跀r截器中所作的工作,說(shuō)明攔截器對(duì)該UserService生效了.
2.作用于特定的Service或者M(jìn)ethod的全局?jǐn)r截器
如果我們不想對(duì)所有Servce或是Method都攔截,只攔截指定的Servce或者M(jìn)ethod呢?
其實(shí),我們是可以配置全局?jǐn)r截器的作用域的.
services.AddDynamicProxy(config =>{//支持通配符,只對(duì)IRole開(kāi)頭的Servce有效config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForService("IRole*"));});我用以上方法配置為該過(guò)濾器只對(duì)IRole開(kāi)頭的Servce有效,那么,當(dāng)我們讓問(wèn)IUserServce時(shí),該攔截器肯定是不會(huì)生效的,事實(shí)是不是這樣呢?
即使Name不是admin,結(jié)果也返回了,說(shuō)明確實(shí)是沒(méi)有生效的.
還可以用以下方法指定過(guò)濾器作用于的Method.
//支持通配符,只對(duì)Name結(jié)尾的方法有效 config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForMethod("*Name"));?
3.全局過(guò)濾器忽略配置
忽略配置有兩種方法
一種是為Service或者M(jìn)ethod打上[NonAspect] 標(biāo)簽,那個(gè)過(guò)濾器就不會(huì)對(duì)該處生效了.
public interface IUserService{[NonAspect]String GetUserName(ApiRequest req);}此時(shí),即使Name不等于Admin,也是有結(jié)果返回會(huì)的.
說(shuō)明此時(shí)配置器對(duì)GetUserName方法確實(shí)沒(méi)有生效.
?
另外一種是 全局忽略配置,亦支持通配符:
services.AddDynamicProxy(config => {//App1命名空間下的Service不會(huì)被代理config.NonAspectPredicates.AddNamespace("App1");//最后一級(jí)為App1的命名空間下的Service不會(huì)被代理config.NonAspectPredicates.AddNamespace("*.App1");//ICustomService接口不會(huì)被代理config.NonAspectPredicates.AddService("ICustomService");//后綴為Service的接口和類不會(huì)被代理config.NonAspectPredicates.AddService("*Service");//命名為Query的方法不會(huì)被代理config.NonAspectPredicates.AddMethod("Query");//后綴為Query的方法不會(huì)被代理config.NonAspectPredicates.AddMethod("*Query"); });?
4.攔截器中的依賴注入
對(duì)攔截器中有g(shù)et和set權(quán)限的屬性標(biāo)記[AspectCore.Injector.FromContainerAttribute]特性,即可自動(dòng)注入該屬性.
[NonAspect]public class AuthenticateInterceptor : AbstractInterceptor{[FromContainer] public ILogger<AuthenticateInterceptor> Logger { get; set ; }public override async Task Invoke(AspectContext context, AspectDelegate next){try{var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();if (apiRequest == null || apiRequest.Name != "admin"){Logger.LogWarning("unauthorized");return;}Logger.LogInformation(apiRequest.Message);await next(context);}catch (Exception e){Logger?.LogWarning("Service threw an exception!");throw;}finally{Logger.LogInformation("Finished service call");}}}也可以攔截器上下文AspectContext可以獲取當(dāng)前Scoped的ServiceProvider
利用該ServiceProvider來(lái)對(duì)依賴項(xiàng)賦值.
[NonAspect]public class AuthenticateInterceptor : AbstractInterceptor{public override async Task Invoke(AspectContext context, AspectDelegate next){ var logger = context.ServiceProvider.GetService<ILogger<AuthenticateInterceptor>> ();try{var apiRequest = (ApiRequest) context.Parameters.FirstOrDefault();if (apiRequest == null || apiRequest.Name != "admin"){logger.LogWarning("unauthorized");return;}logger.LogInformation(apiRequest.Message);await next(context);}catch (Exception e){logger?.LogWarning("Service threw an exception!");throw;}finally{logger.LogInformation("Finished service call");}}}?
本文只是對(duì)AsceptCore最簡(jiǎn)單的一套流程end to end 地進(jìn)行了敘述,這還只是AsceptCore的冰山一角.在此向開(kāi)發(fā)處如此牛逼AOP框架的小伙致敬!!
?
?
歡迎訪問(wèn)
https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/1.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md
解鎖更多新姿勢(shì)!!!
?
本博客Demo地址
https://github.com/liuzhenyulive/AspceptCoreDemo
AsceptCore地址
https://github.com/dotnetcore/AspectCore-Framework
?
轉(zhuǎn)載于:https://www.cnblogs.com/CoderAyu/p/9906349.html
總結(jié)
以上是生活随笔為你收集整理的国内开源社区巨作AspectCore-Framework入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux ccenteros 部署
- 下一篇: css 样式通用样式