39

AOP框架Dora.Interception 3.0 [2]: 实现原理

 4 years ago
source link: https://www.tuicool.com/articles/2yuYBzy
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

和所有的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实现的方法中。

7r2aaeE.png!web

针对类型

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

zM3uiqR.png!web

三、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: 1, cache => cache
                    .To<SystemClock>(target => target
                        .IncludeMethod(clock => clock.GetCurrentTime(default)))));
        }
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK