前些天和张队(善友),lemon(浩洋),斌哥(项斌)等MVP大咖一起吃饭,你们聊到了lemon名下的AOP这个项目,我这小白听得一脸懵逼,后面回来作了一下功课,查了下资料,在lemon的Github上把这个项目学习了一下,收获颇丰,让我这个没有接触过AOP的Coder叹为观止,陷入了对lemon的深深崇拜,在这里把学习的新的体会分享给你们.git
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。github
若是说以前作的一个系统专门给内部的服务提供接口的,由于是在内网中访问,因此就没有加上认证服务,如今这个系统要公开出来,一样的那套接口要给外部系统服务了,那么此时,就要进行认证,认证经过才能获取接口的数据.编程
传统的作法是,修改每个接口.这样就会形成代码改动过大,很恐怖.api
这个时候AOP就能够登场了,咱们能够在这一类服务的前面,加上一个一系列上一刀,在切出来的裂缝里面插入认证方法.框架
然而,怎么插入这个切面是关键.AOP 实现会采用一些常见方法:async
可是常见仍是后处理和代码拦截两种方式ide
后处理,或者叫 静态织入post
指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,所以也称为编译时加强或静态织入。学习
在dotnet 中通常在编译时经过在MSBiuld执行自定义的Build Task来拦截编译过程,在生成的程序集里插入本身的IL。
dotnet 框架表明: PostSharp
代码拦截,或者叫 动态代理
在运行时在内存中“临时”生成 AOP 动态代理类,所以也被称为运行时加强或动态代理。
在dotnet 中通常在运行时经过Emit技术生成动态程序集和动态代理类型从而对目标方法进行拦截。
dotnet 框架表明: Castle DynamicProxy 和 AspectCore
我这里直接应用AOP Demo中的一段代码来讲说这个拦截.
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"); } } }
代码中,其实执行到 await next(context)的时候,才会真正去调用那个被拦截的方法.
这样,咱们就能够灵活地在代码调用前,调用后作咱们想作的事情了.甚至能够把代码包在一个try…catch...中来捕获异常.
新建一个web应用程序后,从 Nuget 安装 AspectCore.Extensions.DependencyInjection
包.
PM> Install-Package AspectCore.Extensions.DependencyInjection
而后.咱们就能够来定义咱们的拦截器了,我定义了一个这样的拦截器.
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"); } }
当ApiRequest为空或者Name不等于admin的时候之家返回并记录未受权.
不然,调用该调用的方法并记录ApiRequest中的Message.
而后,我定义一个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中调用注入UserServce并调用该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); } } }
注册IUserservice并在ConfigureServices
中配置建立代理类型的容器:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddSingleton<IUserService, UserService>(); services.AddMvc(); services.AddDynamicProxy(config => { config.Interceptors.AddTyped<AuthenticateInterceptor>(); }); return services.BuildAspectInjectorProvider(); }
须要注意的是红色背景处,默认的ConfigureService返回类型是空的,咱们要修改为为返回类型是IServiceProvider.
咱们在上面的ConfigureService配置的AuthenticateInterceptor默认状况下是全局的,即这里的IUserService它会拦截,固然若是新增了一个IRoleServce它也是会拦截的.
我把程序运行起来用PostMan访问Api进行测试.下图是Post的数据和返回结果.
说明接口是正常工做的,成功地把传过去的Name原样返回.
那么拦截器有没有生效呢?我看看CMD的输出.
若是咱们修改一下Name不等于Admin,预期应该是返回空,而且日志打印出未受权,是否是这样呢?
完美,与预期彻底相同.
能够发现,这正是咱们在拦截器中所做的工做,说明拦截器对该UserService生效了.
若是咱们不想对全部Servce或是Method都拦截,只拦截指定的Servce或者Method呢?
其实,咱们是能够配置全局拦截器的做用域的.
services.AddDynamicProxy(config => { //支持通配符,只对IRole开头的Servce有效 config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForService("IRole*")); });
我用以上方法配置为该过滤器只对IRole开头的Servce有效,那么,当咱们让问IUserServce时,该拦截器确定是不会生效的,事实是否是这样呢?
即便Name不是admin,结果也返回了,说明确实是没有生效的.
还能够用如下方法指定过滤器做用于的Method.
//支持通配符,只对Name结尾的方法有效 config.Interceptors.AddTyped<AuthenticateInterceptor>(Predicates.ForMethod("*Name"));
忽略配置有两种方法
一种是为Service或者Method打上[NonAspect] 标签,那个过滤器就不会对该处生效了.
public interface IUserService { [NonAspect] String GetUserName(ApiRequest req); }
此时,即便Name不等于Admin,也是有结果返回会的.
说明此时配置器对GetUserName方法确实没有生效.
另一种是 全局忽略配置,亦支持通配符:
services.AddDynamicProxy(config => { //App1命名空间下的Service不会被代理 config.NonAspectPredicates.AddNamespace("App1"); //最后一级为App1的命名空间下的Service不会被代理 config.NonAspectPredicates.AddNamespace("*.App1"); //ICustomService接口不会被代理 config.NonAspectPredicates.AddService("ICustomService"); //后缀为Service的接口和类不会被代理 config.NonAspectPredicates.AddService("*Service"); //命名为Query的方法不会被代理 config.NonAspectPredicates.AddMethod("Query"); //后缀为Query的方法不会被代理 config.NonAspectPredicates.AddMethod("*Query"); });
对拦截器中有get和set权限的属性标记[AspectCore.Injector.FromContainerAttribute]
特性,便可自动注入该属性.
[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
能够获取当前Scoped的ServiceProvider
利用该ServiceProvider来对依赖项赋值.
[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"); } } }
本文只是对AsceptCore最简单的一套流程end to end 地进行了叙述,这还只是AsceptCore的冰山一角.在此向开发处如此牛逼AOP框架的小伙致敬!!
欢迎访问
解锁更多新姿式!!!
本博客Demo地址
https://github.com/liuzhenyulive/AspceptCoreDemo
AsceptCore地址