和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链。Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们就来聊聊大致的实现原理。

一、与依赖注入框架的无缝集成

由于Dora.Interception是为.NET Core定制的AOP框架,而依赖注入是.NET Core基本的编程方式,所以Dora.Interception最初就是作为一个依赖注入框架的扩展而涉及的。我们知道.NET Core的依赖注入框架支持三种服务实例提供方式。由于Dora.Interception最终会利用IL Emit的方式动态生成目标实例的类型,所以它只适合第一种服务注册方式

  • 如果注册的是一个服务类型,最终会选择一个匹配的构造函数来创建服务实例;
  • 如果注册的是一个服务实例创建工厂,那么目标服务实例就由该工厂来创建;
  • 如果注册的是一个服务实例,那么它会直接作为目标服务实例。

二、两种拦截方式

.NET Core的依赖注入框架采用ServiceDescriptor对象来描述服务注册。拦截器最终会注册到ImplementationType 属性表示的实现类型上,所以Dora.Interception需要根据该类型生成一个可以被拦截的代理类型。针对ServiceType属性表示的服务类型的不同,我们会采用不同的代码生成方式。

针对接口

如果注册服务时提供的是一个接口和它的实现类型,我们会按照如下的方式来生成可被拦截的代理类型。假设接口和实现类型分别为IFoobar和Foobar,那么我们会生成一个同样实现IFoobar接口的FoobarProxy类型。FoobarProxy对象是对Foobar对象的封装,对于它实现的方法来说,如果没有拦截器应用到Foobar类型对应的方法上,它只需要调用封装的这个Foobar对象对应的方法就可以了。反之,针对拦截器的调用将会注入到FoobarProxy实现的方法中。

针对类型

如果注册是提供的服务类型并不是一个接口,而是一个类型,比如服务类型和实现类型都是Foobar,上述的代码生成机制就不适用了。此时我们要求Foobar必须是一个非封闭(Sealed)的类型,而且拦截器只能应用到它的虚方法上。基于这种假设,我们生成的代理类型FoobarProxy实际上市Foobar的子类,如果拦截器应用到Foobar的某个虚方法上,FoobarProxy只需要重写这个方法将应用的拦截器注入到方法调用管道中。

三、ICodeGenerator & ICodeGeneratorFactory

上述针对IL Emit的动态代理类型生成体现在如下这个ICodeGenerator接口上,该接口唯一的方法GenerateInterceptableProxyClass会根据提供的上下文信息生成可被拦截的代理类型。作为代码生成上下文的的CodeGenerationContext对象来说,它除了提供服务注册的类型和实现类型之外,它还提供了IInterceptorRegistry对象。

public interface ICodeGenerator
{
    Type GenerateInterceptableProxyClass(CodeGenerationContext  context);
}

public class CodeGenerationContext
{
    public Type InterfaceOrBaseType { get; }
    public Type TargetType { get; }
    public IInterceptorRegistry Interceptors { get; }

    public CodeGenerationContext(Type baseType, IInterceptorRegistry interceptors );
    public CodeGenerationContext(Type @interface, Type targetType, IInterceptorRegistry interceptors);
}

IInterceptorRegistry接口在Dora.Interception中表示某个类型针对拦截器的注册。它的IsEmpty表示拦截器是否应用到目标类型的任意成员中;IsInterceptable方法帮助我们确定指定的方法是否应用了拦截器;应用到某个方法的所有拦截器可以通过GetInterceptor方法提取出来。

public interface IInterceptorRegistry
{
    bool IsEmpty { get; }
    InterceptorDelegate GetInterceptor(MethodInfo methodInfo);
    bool IsInterceptable(MethodInfo methodInfo);
    MethodInfo GetTargetMethod(MethodInfo methodInfo);
}

如果我们需要得到针对某个类型的IInterceptorRegistry对象,可以调用IInterceptorResolver接口的如下两个GetInterceptors方法重载。

public interface IInterceptorResolver
{
    IInterceptorRegistry GetInterceptors(Type initerfaceType, Type targetType);
    IInterceptorRegistry GetInterceptors(Type targetType);
}

与代码生成相关的还具有如下这个ICodeGeneratorFactory接口,它是创建ICodeGenerator的工厂。

四、ServiceDescriptor的转换

由于服务实例最终是通过依赖注入框架提供的,而最终得到怎样的服务实例则由最初的服务注册决定。为了让依赖注入框架能够提供一个可被拦截的代理对象,而不是原始的目标对象,我们必须改变初始的服务注册,为此我们定义了如下这个InterceptableServiceDescriptor。如下面的代码片段所示,InterceptableServiceDescriptor实际是一个基于工厂的ServiceDescriptor,创建代理对象的逻辑体现在GetImplementationFactory方法返回Func<IServiceProvider, object>对象上。

public sealed class InterceptableServiceDescriptor : ServiceDescriptor, IInterceptableServiceDescriptor
{
    private readonly Type _targetType;
        : base(serviceType, GetImplementationFactory(serviceType, implementationType), lifetime)
    {
        if (serviceType.IsGenericTypeDefinition)
        {
            throw new ArgumentException("Open generic type (generic type definition) is not support", nameof(serviceType));
        }
        _targetType = implementationType;
    }

    Type IInterceptableServiceDescriptor.TargetType => _targetType;

    private static Func<IServiceProvider, object> GetImplementationFactory(Type serviceType, Type implementationType)
    {
        return serviceProvider =>
        {
            var interceptorResolver = serviceProvider.GetRequiredService<IInterceptorResolver>();
            var codeGeneratorFactory = serviceProvider.GetRequiredService<ICodeGeneratorFactory>();
            var factoryCache = serviceProvider.GetRequiredService<IInterceptableProxyFactoryCache>();
            if (serviceType.IsInterface)
            {
                var interceptors = interceptorResolver.GetInterceptors(serviceType, implementationType);
                if (interceptors.IsEmpty)
                {
                    return ActivatorUtilities.CreateInstance(serviceProvider, implementationType);
                }
                else
                {
                    var target = ActivatorUtilities.CreateInstance(serviceProvider, implementationType);
                    return factoryCache.GetInstanceFactory(serviceType, implementationType).Invoke(target);
                }
            }
            else
            {
                var interceptors = interceptorResolver.GetInterceptors(implementationType);
                if (interceptors.IsEmpty)
                {
                    return ActivatorUtilities.CreateInstance(serviceProvider, implementationType);
                }
                else
                {
                    return factoryCache.GetTypeFactory(implementationType).Invoke(serviceProvider);
                }
            }
        };
    }
}

我们可以利用提供的如下的扩展方法直接创建InterceptableServiceDescriptor 对象作为服务注册。

public static class AddInterceptionExtensions
{
    public static IServiceCollection AddInterceptable(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime);

    public static IServiceCollection AddTransientInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddScopedInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddSingletonInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection AddInterceptable<TService, TImplementation>(this IServiceCollection services, ServiceLifetime lifetime);
    public static IServiceCollection AddTransientInterceptable<TService, TImplementation>(this IServiceCollection services);
    public static IServiceCollection AddScopedInterceptable<TService, TImplementation>(this IServiceCollection services);
    public static IServiceCollection AddSingletonInterceptable<TService, TImplementation>(this IServiceCollection services);

    public static IServiceCollection TryAddInterceptable(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime);
    public static IServiceCollection TryAddTransientInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection TryAddScopedInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection TryAddSingletonInterceptable(this IServiceCollection services, Type serviceType, Type implementationType);
    public static IServiceCollection TryAddInterceptable<TService, TImplementation>(this IServiceCollection services, ServiceLifetime lifetime);
    public static IServiceCollection TryAddInterceptable<TService, TImplementation>(this IServiceCollection services);
    public static IServiceCollection TryAddScopedInterceptable<TService, TImplementation>(this IServiceCollection services);
    public static IServiceCollection TryAddSingletonInterceptable<TService, TImplementation>(this IServiceCollection services);

    public static IServiceCollection TryAddEnumerableInterceptable(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime);
    public static IServiceCollection TryAddEnumerableInterceptable<TService, TImplementation>(this IServiceCollection services, ServiceLifetime lifetime)
}

五、另一种改变服务注册的方式

如果我们依然希望采用默认提供的服务注册API,那么我们可以将服务注册的转换实现在利用IServiceCollection集合创建IServiceProvider对象的时候,为此我们定义了如下这个BuildInterceptableServiceProvider扩展方法。顺便说一下,另一个AddInterception扩展方法用来注册Dora.Interception框架自身的一些核心服务。BuildInterceptableServiceProvider方法内部会调用这个方法,如果没有采用这种方式来创建IServiceProvider对象,AddInterception扩展方法必须显式调用。

public static class ServiceCollectionExtensions
{
    public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder> configure = null);
    public static IServiceCollection AddInterception(this IServiceCollection services, Action<InterceptionBuilder> configure = null);
}

六、InterceptableServiceProviderFactory

.NET Core依赖注入框架利用自定义的IServiceProviderFactory<TContainerBuilder>实现与第三方依赖注入框架的整合。如下这个的InterceptableServiceProviderFactory是我们为Dora.Interception定义的实现类型。

public sealed class InterceptableServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
    public InterceptableServiceProviderFactory(ServiceProviderOptions options, Action<InterceptionBuilder> configure);
    public IServiceCollection CreateBuilder(IServiceCollection services);
    public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder);
}

为了在服务承载应用(含ASP.NET Core应用)更好地使用Dora.Interception,可以调用我们为IHostBuilder定义的UseInterceptableServiceProvider扩展方法,该方法会帮助我们完成针对InterceptableServiceProviderFactory的注册。

public static class HostBuilderExtensions
{
    public static IHostBuilder UseInterceptableServiceProvider(this IHostBuilder builder,ServiceProviderOptions options = null,Action<InterceptionBuilder> configure = null);
}

我们在《AOP框架Dora.Interception 3.0 [1]: 编程体验》提供的演示程序(如下所示)正是调用了这个UseInterceptableServiceProvider方法。

public class Program
{
    public static void Main(string[] args)
    {
        Host.CreateDefaultBuilder()
            .UseInterceptableServiceProvider(configure: Configure)
            .ConfigureWebHostDefaults(buider => buider.UseStartup<Startup>())
            .Build()
            .Run();

        static void Configure(InterceptionBuilder interceptionBuilder)
        {
            interceptionBuilder.AddPolicy(policyBuilder => policyBuilder
                .For<CacheReturnValueAttribute>(order: , cache => cache
                    .To<SystemClock>(target => target
                        .IncludeMethod(clock => clock.GetCurrentTime(default)))));
        }
    }
}

AOP框架Dora.Interception 3.0 [1]: 编程体验
AOP框架Dora.Interception 3.0 [2]: 实现原理
AOP框架Dora.Interception 3.0 [3]: 拦截器设计
AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册
AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册
AOP框架Dora.Interception 3.0 [6]: 自定义拦截器注册方式

AOP框架Dora.Interception 3.0 [2]: 实现原理的更多相关文章

  1. AOP框架Dora.Interception 3.0 [1]: 编程体验

    .NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...

  2. AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式

    注册拦截器旨在解决如何将拦截器应用到目标方法的问题.在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少.如果注册的方式过于模 ...

  3. AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册

    按照单一职责的原则,拦截器只负责需要的拦截操作的执行,至于它采用何种方式应用到目标方法上,以及它在整个拦截器管道中的位置则属于“拦截器注册”的范畴.Dora.Interception提供了几种典型的注 ...

  4. AOP框架Dora.Interception 3.0 [3]: 拦截器设计

    对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上.这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用.从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的 ...

  5. 全新升级的AOP框架Dora.Interception[1]: 编程体验

    多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...

  6. 全新升级的AOP框架Dora.Interception[2]: 基于&ldquo;约定&rdquo;的拦截器定义方式

    Dora.Interception有别于其他AOP框架的最大的一个特点就是采用针对"约定"的拦截器定义方式.如果我们为拦截器定义了一个接口或者基类,那么拦截方法将失去任意注册依赖服 ...

  7. 全新升级的AOP框架Dora.Interception[3]: 基于特性标注的拦截器注册方式

    在Dora.Interception(github地址,觉得不错不妨给一颗星)中按照约定方式定义的拦截器可以采用多种方式注册到目标方法上.本篇文章介绍最常用的基于"特性标注"的拦截 ...

  8. 全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理

    本系列前面的五篇文章主要介绍Dora.Interception(github地址,觉得不错不妨给一颗星)的编程模式以及对它的扩展定制,现在我们来聊聊它的设计和实现原理.(拙著<ASP.NET C ...

  9. 全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式

    如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截 ...

随机推荐

  1. js数组增删元素

    操作数组的方法 push() 结尾添加 数组.push(元素) 参数 描述 newelement1 必需.要添加到数组的第一个元素. newelement2 可选.要添加到数组的第二个元素. newe ...

  2. Redis小白入门系列

    一.从NoSQL说起 NoSQL 是 Not only SQL 的缩写,大意为"不只是SQL",说明这项技术是传统关系型数据库的补充而非替代.在整个NoSQL技术栈中 MemCac ...

  3. EventBus 消息的线程切换模型与实现原理

    一. 序 EventBus 是一个基于观察者模式的事件订阅/发布框架,利用 EventBus 可以在不同模块之间,实现低耦合的消息通信. EventBus 因为其使用简单且稳定,被广泛应用在一些生产项 ...

  4. jquery easyui dialog一进来直接最大化

    扩展自 $.fn.window.defaults.通过 $.fn.dialog.defaults 重写默认的 defaults. 对话框(dialog)是一个特殊类型的窗口,它在顶部有一个工具栏,在底 ...

  5. ASP.NET Core 2.2 : 二十二. 多样性的配置方式

    大多数应用都离不开配置,本章将介绍ASP.NET Core中常见的几种配置方式及系统内部实现的机制. 说到配置,第一印象可能就是“.config”类型的xml文件或者“.ini”类型的ini文件,在A ...

  6. Java使用Optional与Stream来取代if判空逻辑(JDK8以上)

    Java使用Optional与Stream来取代if判空逻辑(JDK8以上) 通过本文你可以用非常简短的代码替代业务逻辑中的判null校验,并且很容易的在出现空指针的时候进行打日志或其他操作. 注:如 ...

  7. SpringBoot——Web开发(静态资源映射)

    静态资源映射 SpringBoot对于SpringMVC的自动化配置都在WebMVCAutoConfiguration类中. 其中一个静态内部类WebMvcAutoConfigurationAdapt ...

  8. Mongodb关于查询返回指定字段的方法记录

    //通常指定字段由前端传入后台,例如params 前端以逗号分隔 //后端获取字段后操作如下: Query query = new Query(); if (params != null) { Str ...

  9. 前端初探 Gitlab CI/CD

    前言 纵观人类历史的发展以及三次工业革命,你会发现利用机器来替代部分人力劳动,将重复的工作自动化从而解放生产力都是发展的必然趋势,在软件工程领域也不例外,其中 CI/CD 就是其中一项,那么什么是 C ...

  10. 判断dom原始是否在可视区域内

    isElementInViewport (el, offset = 0) { const box = el.getBoundingClientRect(), top = (box.top >= ...