AOP框架Dora.Interception 3.0 [2]: 實現原理


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

image_thumb2_thumb

針對類型

如果注冊是提供的服務類型並不是一個接口,而是一個類型,比如服務類型和實現類型都是Foobar,上述的代碼生成機制就不適用了。此時我們要求Foobar必須是一個非封閉(Sealed)的類型,而且攔截器只能應用到它的虛方法上。基於這種假設,我們生成的代理類型FoobarProxy實際上市Foobar的子類,如果攔截器應用到Foobar的某個虛方法上,FoobarProxy只需要重寫這個方法將應用的攔截器注入到方法調用管道中。

image_thumb5_thumb

三、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)))));
        }
    }
}


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]: 自定義攔截器注冊方式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM