全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式
如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截器。如果对那个的程序集是由第三方提供的呢?此时我们可以采用提供的第二种基于表达式的拦截器应用方式。这里的拦截器是一个调用目标类型某个方法或者提取某个属性的Lambda表达式,我们采用这种强类型的编程方式得到目标方法,并提升编程体验。(拙著《ASP.NET Core 6框架揭秘》于日前上市,加入读者群享6折优惠)
目录
一、IInterceptorRegistry
二、将拦截器应用到某个类型
三、应用到指定的方法和属性
四、指定构建拦截器的参数
五、拦截屏蔽
六、两个后备方法
一、IInterceptorRegistry
以表达式采用强类型的方式将指定类型的拦截器应用到目标方法上是借助如下这个IInterceptorRegistry接口完成的。IInterceptorRegistry接口提供了一个For<TInterceptor>方法以待注册的拦截器类型关联,参数arguments用来提供构建拦截器对象的参数。该方法会返回一个IInterceptorRegistry<TInterceptor>对象,它提供了一系列的方法帮助我们将指定的拦截器应用到指定目标类型(通过泛型参数类型TTarget表示)相应的方法上。
public interface IInterceptorRegistry
{ IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
...
} public interface IInterceptorRegistry<TInterceptor>
{
IInterceptorRegistry<TInterceptor> ToAllMethods<TTarget>(int order);
IInterceptorRegistry<TInterceptor> ToMethod<TTarget>(int order, Expression<Action<TTarget>> methodCall);
IInterceptorRegistry<TInterceptor> ToMethod(int order, Type targetType, MethodInfo method);
IInterceptorRegistry<TInterceptor> ToGetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry<TInterceptor> ToSetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry<TInterceptor> ToProperty<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
}
封装了IServiceCollection集合的InterceptionBuilder提供了一个RegisterInterceptors扩展方法,我们可以利用该方法定义的Action<IInterceptorRegistry>类型的参数来使用上述的这个IInterceptorRegistry接口。不论是IServiceCollection接口的BuildInterceptableServiceProvider扩展方法,还是IHostBuilder接口的UseInterception方法均提供了一个可选的Action<InterceptionBuilder>委托类型的参数。
public sealed class InterceptionBuilder
{
public IServiceCollection Services { get; } public InterceptionBuilder(IServiceCollection services);
} public static class Extensions
{
public static InterceptionBuilder RegisterInterceptors(this InterceptionBuilder builder, Action<IInterceptorRegistry> register); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder>? setup = null);
public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, Action<InterceptionBuilder>? setup = null);
}
二、将拦截器应用到某个类型
类似与将InterceptorAttribute标注到某个类型上,我们也可以采用这种方式将指定的拦截器应用到目标类型上,背后的含义就是应用到该类型可以被拦截的所以方法上(含属性方法)。
public class FoobarInterceptor
{
public ValueTask InvokeAsync(InvocationContext invocationContext)
{
var method = invocationContext.MethodInfo;
Console.WriteLine($"{method.DeclaringType!.Name}.{method.Name} is intercepted.");
return invocationContext.ProceedAsync();
}
} public class Foobar
{
public virtual void M() { }
public virtual object? P { get; set; }
}
我们可以采用如下的方式将调用IInterceptorRegistry<TInterceptor>的ToAllMethods<TTarget>方法将上面定义的拦截器FoobarInterceptor应用到Foobar类型的所有方法上。
var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>(); foobar.M();
foobar.P = null;
_ = foobar.P; static void RegisterInterceptors(IInterceptorRegistry registry)
{
var foobar = registry.For<FoobarInterceptor>();
foobar.ToAllMethods<Foobar>(order: 1);
}
从如下所示的执行结果可以看出,Foobar类型的M方法和P属性均被FoobarInterceptor拦截下来(源代码)。

三、应用到指定的方法和属性
我们可以通过指定调用方法或者获取属性的表达式来指定拦截器应用的目标方法。我们将目标类型Foobar定义成如下的形式,两个重载的M方法和三个属性均是可以拦截的。
public class Foobar
{
public virtual void M(int x, int y) { }
public virtual void M(double x, double y) { }
public virtual object? P1 { get; set; }
public virtual object? P2 { get; set; }
public virtual object? P3 { get; set; }
}
我们利用如下的代码将上面定义的FoobarInterceptor应用到Foobar类型相应的成员上。具体来说,我们调用ToMethod<TTarget>方法应用到两个重载的M方法,调用ToProperty<TTarget>方法应用到P1属性的Get和Set方法上,调用ToGetMethod<TTarget>和ToSetMethod<TTarget>方法应用到P2属性的Get方法和P3属性的Set方法。
var provider = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)); var foobar = provider.GetRequiredService<Foobar>(); foobar.M(1, 1);
foobar.M(3.14, 3.14);
foobar.P1 = null;
_ = foobar.P1;
foobar.P2 = null;
_ = foobar.P2;
foobar.P3 = null;
_ = foobar.P3;
Console.ReadLine(); static void RegisterInterceptors(IInterceptorRegistry registry)
{
var foobar = registry.For<FoobarInterceptor>();
foobar
.ToMethod<Foobar>(order: 1, it => it.M(default(int), default(int)))
.ToMethod<Foobar>(order: 1, it => it.M(default(double), default(double)))
.ToProperty<Foobar>(order: 1, it => it.P1)
.ToGetMethod<Foobar>(order: 1, it => it.P2)
.ToSetMethod<Foobar>(order: 1, it => it.P3)
;
}
程序运行后,针对Foobar相应成员的拦截体现在如下所示的输出结果上(源代码)。

四、指定构建拦截器的参数
如果应用的拦截器类型构造函数指定了参数,我们采用这种注册方式的时候也可以指定参数。以如下这个FoobarInterceptor为例,其构造函数中指定了两个参数,一个是代表拦截器名称的name参数,另一个是IFoobar对象。
public class FoobarInterceptor
{
public FoobarInterceptor(string name, IFoobar foobar)
{
Name = name;
Foobar = foobar;
} public string Name { get; }
public IFoobar Foobar { get; }
public ValueTask InvokeAsync(InvocationContext invocationContext)
{
Console.WriteLine($"{invocationContext.MethodInfo.Name} is intercepted by FoobarInterceptor {Name}.");
Console.WriteLine($"Foobar is '{Foobar.GetType()}'.");
return invocationContext.ProceedAsync();
}
}
public interface IFoobar { }
public class Foo : IFoobar { }
public class Bar: IFoobar { } public class Invoker
{
public virtual void M1() { }
public virtual void M2() { }
}
由于字符串参数name无法从依赖注入容器提取,所以在注册FoobarInterceptor是必须显式指定。如果容器能够提供IFoobar对象,但是希望指定一个不通过的对象,也可以在注册的时候显式指定一个IFoobar对象。我们按照如下的方式将两个不同的FoobarInterceptor对象分别应用到Invoker类型的Invoke1和Invoke2方法上,并分别将名称设置为Interceptor1和Interceptor2,第二个拦截器还指定了一个Bar对象作为参数(容器默认提供的IFoobar对象的类型为Foo)。
var invoker = new ServiceCollection()
.AddSingleton<Invoker>()
.AddSingleton<IFoobar, Foo>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Invoker>(); invoker.M1();
Console.WriteLine();
invoker.M2(); static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>("Interceptor1").ToMethod<Invoker>(order: 1, it => it.M1());
registry.For<FoobarInterceptor>("Interceptor2", new Bar()).ToMethod<Invoker>(order: 1, it => it.M2());
}
程序运行之后,两个FoobarInterceptor对象的名称和依赖的IFoobar对象的类型以如下的形式输出到控制台上(源代码)。

五、拦截屏蔽
除了用来注册指定拦截器的For<TInterceptor>方法,IInterceptorRegistry接口还定义了如下这些用来屏蔽拦截的SuppressXxx方法。
public interface IInterceptorRegistry
{
IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
IInterceptorRegistry SupressType<TTarget>();
IInterceptorRegistry SupressTypes(params Type[] types);
IInterceptorRegistry SupressMethod<TTarget>(Expression<Action<TTarget>> methodCall);
IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
IInterceptorRegistry SupressProperty<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry SupressSetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry SupressGetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
}
我们可以采用如下的方式会将屏蔽掉Foobar类型所有成员的拦截特性,虽然拦截器FoobarInterceptor被注册到了这个类型上(源代码)。
var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>();
... static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
registry.SupressType<Foobar>();
}
下面的程序明确屏蔽掉Foobar类型如下这些方法的拦截能力:M方法,P1属性的Get和Set方法(如果有)以及P属性的Get方法(源代码)。
var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>(); ... static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
registry.SupressMethod<Foobar>(it=>it.M());
registry.SupressProperty<Foobar>(it => it.P1);
registry.SupressGetMethod<Foobar>(it => it.P2);
}
六、两个后备方法
通过指定调用目标方法或者提取属性的表达式来提供拦截器应用的方法和需要屏蔽的方法提供了较好的编程体验,但是能够提供这种强类型编程模式的前提是目标方法或者属性是公共成员。对于受保护(protected)的方法和属性,我们只能使用如下两个后备方法,指定代表目标方法的MethodInfo对象。
public interface IInterceptorRegistry<TInterceptor>
{
IInterceptorRegistry<TInterceptor> ToMethods<TTarget>(int order, params MethodInfo[] methods);
} public interface IInterceptorRegistry
{
IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
}
全新升级的AOP框架Dora.Interception[1]: 编程体验
全新升级的AOP框架Dora.Interception[2]: 基于约定的拦截器定义方式
全新升级的AOP框架Dora.Interception[3]: 基于“特性标注”的拦截器注册方式
全新升级的AOP框架Dora.Interception[4]: 基于“Lambda表达式”的拦截器注册方式
全新升级的AOP框架Dora.Interception[5]: 实现任意的拦截器注册方式
全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理
全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式的更多相关文章
- 全新升级的AOP框架Dora.Interception[3]: 基于特性标注的拦截器注册方式
在Dora.Interception(github地址,觉得不错不妨给一颗星)中按照约定方式定义的拦截器可以采用多种方式注册到目标方法上.本篇文章介绍最常用的基于"特性标注"的拦截 ...
- 全新升级的AOP框架Dora.Interception[2]: 基于“约定”的拦截器定义方式
Dora.Interception有别于其他AOP框架的最大的一个特点就是采用针对"约定"的拦截器定义方式.如果我们为拦截器定义了一个接口或者基类,那么拦截方法将失去任意注册依赖服 ...
- 全新升级的AOP框架Dora.Interception[1]: 编程体验
多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...
- 全新升级的AOP框架Dora.Interception[6]: 实现任意的拦截器注册方式
Dora.Interception提供了两种拦截器注册方式,一种是利用标注在目标类型.属性和方法上的InterceptorAttribute特性,另一种采用基于目标方法或者属性的调用表达式.通过提供的 ...
- 全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理
本系列前面的五篇文章主要介绍Dora.Interception(github地址,觉得不错不妨给一颗星)的编程模式以及对它的扩展定制,现在我们来聊聊它的设计和实现原理.(拙著<ASP.NET C ...
- 全新升级的AOP框架Dora.Interception[汇总,共6篇]
多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...
- AOP框架Dora.Interception 3.0 [1]: 编程体验
.NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...
- AOP框架Dora.Interception 3.0 [2]: 实现原理
和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...
- AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式
注册拦截器旨在解决如何将拦截器应用到目标方法的问题.在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少.如果注册的方式过于模 ...
随机推荐
- 利用js获取不同页面间跳转需要传递的参数
获取参数的js函数如下: function GetQueryValue(queryName) { var query = decodeURI(window.location.search.substr ...
- NodeJS学习日报day4——模块化
// console.log(module); // 执行顺序不同,结果也不同 // module.exports = { // name : 'Cra2iTeT', // hi() { // con ...
- JavaWeb学习day4-Maven&IDEA中的使用
1.创建本地仓库 ,因为使用apache官方的下载方式需要挂梯,下载速度慢且容易出错,可以配置阿里的下载路径,同时配置如下图的仓库路径 2.创建原型项目 3.等待jar包下载导入完成,出现下图即代表成 ...
- JavaWeb学习day1-web入门&服务器安装
初步了解了web的发展历程,对比分析学习了动态页面与静态页面的优缺点. 选择安装使用最广泛的Tomcat 安装流程: 1.打开官网:https://tomcat.apache.org/ 2.选择下载版 ...
- JavaWeb和WebGIS学习笔记(四)——使用uDig美化地图,并叠加显示多个图层
系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...
- 让交互更加生动!有意思的鼠标跟随 3D 旋转动效
今天,群友问了这样一个问题,如下所示的鼠标跟随交互效果,如何实现: 简单分析一下,这个交互效果主要有两个核心: 借助了 CSS 3D 的能力 元素的旋转需要和鼠标的移动相结合 本文,就将讲述如何使用纯 ...
- 【VMware】在移动硬盘或U盘中安装便携linux系统
背景: 操作系统课需要在Linux环境下进行编程.本来是给了个傻瓜式的Ubuntu虚拟机镜像,但奈何虚拟机这东西我这老本子跑起来巨卡,装双系统又卡,只能选择把系统装进便携设备里了. 前期准备: 一个2 ...
- Android 4.4系统,User模式adb默认开启,取消授权,开启root调试记录
开启User模式adb,取消授权,修改如下: 1. /build/core/main.mk 修改以下内容 ifeq (true,$(strip $(enable_target_debugging)) ...
- PyScript:让Python在HTML中运行
大家好,我是DD,已经是封闭在家的第51天了! 最近一直在更新Java新特性和IDEA Tips两个专栏,其他方向内容的动态关注少了.昨天天晚上刷推的时候,瞄到了这个神奇的东西,觉得挺cool的,拿出 ...
- 数据管理技术发展,数据库应用发展史,数据库分类,MySQL
计算机数据管理技术发展 1. 自由管理阶段 用户以文件形式将数据组织起来,并附属在各自的应用程序下. 1.数据不保存 当时计算机主要用于科学计算,一般不需要将数据长期保存,只是计算某一课 ...