前些天和张队(善友),lemon(浩洋),斌哥(项斌)等MVP大咖一块儿吃饭,大家聊到了lemon名下的AOP这个项目,我这小白听得一脸懵逼,后面回来做了一下功课,查了下资料,在lemon的Github上把这个项目学习了一下,收获颇丰,让我这个没有接触过AOP的Coder叹为观止,陷入了对lemon的深深崇拜,在这里把学习的新的体会分享给大家.

什么是AOP?

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

有点深奥, 举个栗子

如果说之前做的一个系统专门给内部的服务提供接口的,因为是在内网中访问,所以就没有加上认证服务,现在这个系统要公开出来,同样的那套接口要给外部系统服务了,那么此时,就要进行认证,认证通过才能获取接口的数据.

传统的做法是,修改每一个接口.这样就会造成代码改动过大,很恐怖.

这个时候AOP就可以登场了,我们可以在这一类服务的前面,加上一个一系列上一刀,在切出来的裂缝里面插入认证方法.

然而,怎么插入这个切面是关键.AOP 实现会采用一些常见方法:

  • 使用预处理器(如 C++ 中的预处理器)添加源代码。
  • 使用后处理器在编译后的二进制代码上添加指令。
  • 使用特殊编译器在编译时添加代码。
  • 在运行时使用代码拦截器拦截执行并添加所需的代码。

但是常见还是后处理和代码拦截两种方式

  • 后处理,或者叫 静态织入

    指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强或静态织入。

    在dotnet 中一般在编译时通过在MSBiuld执行自定义的Build Task来拦截编译过程,在生成的程序集里插入自己的IL。

    dotnet 框架代表: PostSharp

  • 代码拦截,或者叫 动态代理

    在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强或动态代理。

    在dotnet 中一般在运行时通过Emit技术生成动态程序集和动态代理类型从而对目标方法进行拦截。

    dotnet 框架代表: Castle DynamicProxyAspectCore

引用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的代码拦截

我这里直接应用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...中来捕获异常.

开始AspcetCore的表演

新建一个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.

1.全局拦截

我们在上面的ConfigureService配置的AuthenticateInterceptor默认情况下是全局的,即这里的IUserService它会拦截,当然如果新增了一个IRoleServce它也是会拦截的.

我把程序运行起来用PostMan访问Api进行测试.下图是Post的数据和返回结果.

说明接口是正常工作的,成功地把传过去的Name原样返回.

那么拦截器有没有生效呢?我看看CMD的输出.

如果我们修改一下Name不等于Admin,预期应该是返回空,并且日志打印出未授权,是不是这样呢?

完美,与预期完全相同.

可以发现,这正是我们在拦截器中所作的工作,说明拦截器对该UserService生效了.

2.作用于特定的Service或者Method的全局拦截器

如果我们不想对所有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"));

3.全局过滤器忽略配置

忽略配置有两种方法

一种是为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");
});

4.拦截器中的依赖注入

对拦截器中有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框架的小伙致敬!!

欢迎访问

https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/1.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md

解锁更多新姿势!!!

本博客Demo地址

https://github.com/liuzhenyulive/AspceptCoreDemo

AsceptCore地址

https://github.com/dotnetcore/AspectCore-Framework

国内开源社区巨作AspectCore-Framework入门的更多相关文章

  1. 500G !!史上最全的JAVA全套教学视频网盘分享 (JEECG开源社区)

    500 G JAVA视频网盘分享(JEECG开源社区) [涵盖从java入门到深入架构,Linux.云计算.分布式.大数据Hadoop.ios.Android.互联网技术应有尽有] JEECG开源社区 ...

  2. 500 G JAVA视频网盘分享(JEECG开源社区)

    500 G JAVA视频网盘分享(JEECG开源社区)   [涵盖从java入门到深入架构,Linux.云计算.分布式.大数据Hadoop.ios.Android.互联网技术应有尽有]   [转载:h ...

  3. 500G JAVA视频网盘分享 (JEECG开源社区)

    500 G JAVA视频网盘分享(JEECG开源社区)   [涵盖从java入门到深入架构,Linux.云计算.分布式.大数据Hadoop.ios.Android.互联网技术应有尽有]       J ...

  4. 做一名开源社区的扫地僧——从Bug report到Google Summer of Code(GSoC):从200个bug到5000美金

    今年的软件自由日(SFD),我在广州Linux用户组的线下活动上做了一个分享,主题叫做<做一名开源社区的扫地僧(上)>.我把演讲的内容重新整理扩充, 写出了文字版, 希望可以跟更多朋友分享 ...

  5. 让 Python 带你进入开源的世界——Git 从入门到与他人协作开发

    让 Python 带你进入开源的世界--Git 从入门到与他人协作开发 我认为开源社区中有很多优秀的资源,并且可以帮助进阶中的程序员提高编程能力和水平.所以,我发起了<HelloGitHub&g ...

  6. 积极拥抱.NET Core开源社区

    潘正磊在上海的Tech Summit 2018 大会上给我们的.NET Core以及开源情况带来了最新信息. .Net Core 开源后取得了更加快速的发展,目前越活跃用户高达400万人,每月新增开发 ...

  7. 我们将要建立的EasyDarwin开源社区

    从12年12月我开始建立EasyDarwin开源项目,已经三年多的时间了,从开始最简单的一个开源流媒体服务器项目,如今已经发展成为目前国内最大的一个流媒体开源社区,截至目前已经有十几个项目在Githu ...

  8. 备战春招!开源社区系统 Echo 超全文档助力面试

    博主东南大学硕士在读,寒假前半个月到现在差不多一个多月,断断续续做完了这个项目,现在终于可以开源出来了,我的想法是为这个项目编写一套完整的教程,包括技术选型分析.架构分析.业务逻辑分析.核心技术点分析 ...

  9. DevOps|乱谈开源社区、开源项目与企业内部开源

    之前的一篇文章<从特拉斯辞职风波到研发效能中的荒唐事>中关于企业内源的内容在研发效能群内引起了大家的热烈讨论.有的小伙伴不同意,有的小伙伴非常不同意,我觉得这都是非常正常的反馈,话不说不透 ...

随机推荐

  1. [Java算法分析与设计]--顺序栈的实现

    在程序的世界,栈的应用是相当广泛的.其后进先出的特性,我们可以应用到诸如计算.遍历.代码格式校对等各个方面.但是你知道栈的底层是怎么实现的吗?现在跟随本篇文章我们来一睹它的庐山真面目吧. 首先我们先定 ...

  2. MySQL无法存储emoji表情方案

    今天学习爬虫爬伯乐在线的文章,由于在文章中有emoji表情,导致有emoji表情的文章都爬取不下来 经过一番搜索之后终于解决了问题. 原文可参考: 1. MySQL无法存储Emoji表情问题 2. m ...

  3. 基于 HTML5 WebGL 的低碳工业园区监控系统

    前言 低碳工业园区的建设与推广是我国推进工业低碳转型的重要举措,低碳工业园区能源与碳排放管控平台是低碳工业园区建设的关键环节.如何对园区内的企业的能源量进行采集.计量.碳排放核算,如何对能源消耗和碳排 ...

  4. 原生aspx页面如何引用公共js和css

    项目过程中遇到一个问题,每个页面需要引用很多的js和css文件,其中很多都是控件,而且大部分都是一样的,造成很多重复引用. 针对这种情况,参考了mvc的BundleConfig,思路是建立一个公用的用 ...

  5. 并发库应用之五 & ReadWriteLock场景应用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  6. spring 切入点表达式

    spring表达式有多种的指示符,如: 切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下: exe ...

  7. jquery-layer弹框在火狐浏览器中弹框不显示的问题

    在使用layer控件设置弹框时, 谷歌浏览器中能正常弹出, 显示在页面中央位置. 而在火狐浏览器中, 弹框只显示标题, 并且弹框内容不显示. 在火狐浏览器中弹框的效果如下图红色方框中的弹出框所示, 但 ...

  8. 网络编程之select

    一.select函数简介 select一般用在socket网络编程中,在网络编程的过程中,经常会遇到许多阻塞的函数,网络编程时使用的recv, recvfrom.connect函数都是阻塞的函数,当函 ...

  9. Python基本类常用方法

    数学函数 abs(x) 返回数字的绝对值,如abs(-10) 返回 10 ceil(x) 返回数字的上入整数,如math.ceil(4.1) 返回 5 cmp(x, y)如果 x < y 返回 ...

  10. 【阿里聚安全·安全周刊】Python库现后门 可窃取用户SSH信息|Facebook再曝300万用户数据泄露

    本周七个关键词:Python库现后门丨Facebook再曝数据泄露丨加密协议被曝严重漏洞丨英国报摊将出售"色情通行证"丨HTTPS的绿色锁图标丨机器学习和预测应用的API丨Ecli ...