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

本系列前面的五篇文章主要介绍Dora.Interception的编程模式以及对它的扩展定制,现在我们来聊聊它的设计和实现原理。

目录
一、调用链抽象
二、基于约定的拦截器定义
三、基于调用上下文的依赖注入容器
四、拦截器的提供
五、调用链的构建
六、方法拦截的实现原理
七、依赖注入框架的整合
八、看看生成的代理类

一、调用链抽象

从设计模式来看,Dora.Interception采用了“职责链”模式。我们将应用到同一个方法的多个拦截器以及针对目标方法的调用构建成如下所示的“调用链”。调用链在执行过程中共享同一个“调用上下文”,后者提供当前调用的上下文信息,比如目标对象、调用方法、输出参数和返回值等。每个拦截器不仅可以利用这些上下文信息执行对应的操作,还可以直接利用此上下文修改参数和返回值,并且自行决定是否继续执行后续调用。

63f2b89a0ab36677051fadc7654a6d63.png

我们定义了如下这个抽象类InvocationContext来表示上述的调用上下文。对于参数/返回值的提取,我们设计成抽象方法以避免因装箱/拆箱带来的性能问题。拦截器针对其他服务的依赖是一个基本的需求,所以我们为InvocationContext定义了一个InvocationServices属性来提供针对当前调用的IServiceProvider对象。在默认情况下,我们会为每次调用创建一个服务范围,并利用此范围的IServiceProvider对象作为这个InvocationServices属性的值。但是对于ASP.NET Core应用,我们会直接使用针对当前请求的IServiceProvider对象。

public abstract class InvocationContext
{public object Target { get; } = default!;public abstract MethodInfo MethodInfo { get; }public abstract IServiceProvider InvocationServices { get; }public IDictionary<object, object> Properties { get; }public abstract TArgument GetArgument<TArgument>(string name);public abstract TArgument GetArgument<TArgument>(int index);public abstract InvocationContext SetArgument<TArgument>(string name, TArgument value);public abstract InvocationContext SetArgument<TArgument>(int index, TArgument value);public abstract TReturnValue GetReturnValue<TReturnValue>();public abstract InvocationContext SetReturnValue<TReturnValue>(TReturnValue value);protected InvocationContext(object target);internal InvokeDelegate Next { get; set; } = default!;public ValueTask ProceedAsync() => Next.Invoke(this);
}

既然有了这样一个能够体现当前方法调用上下文的InvocationContext类型,那么上述的“调用量”就可以表示成如下这个InvokeDelegate委托。熟悉ASP.NET Core的读者可以看出Dora.Interception的调用链设计与ASP.NET Core框架的“中间件管道”几乎一致,InvocationContext和InvokeDelegate分别对应后者的HttpContext和RequestDelegate。

public delegate ValueTask InvokeDelegate(InvocationContext context);

既然将ASP.NET Core作为类比,Dora.Interception的拦截器自然就对应着ASP.NET Core的中间件了。我们知道后者体现为一个Func<RequestDelegate, RequestDelegate>委托,作为输入的RequestDelegate代表由后续中间件构建的请求处理管道,每个中间件需要利用此对象将请求分发给后续管道进行处理。Dora.Interception采用了更为简单的设计,我们将拦截器也表示成上述的InvokeDelegate委托,因为针对后续拦截器以及目标方法的调用可以利用代表调用上下文的InvocationContext对象的ProceedAsync方法来完成。如上面的代码片段所示,InvocationContext具有一个名为Next的内部属性用来表示调用调用链的下一个InvokeDelegate对象,每个拦截器在执行之前,此属性都会预先被设置,ProceedAsync方法调用的正式此属性返回的InvokeDelegate对象。

二、基于约定的拦截器定义

虽然拦截器最终由一个InvokeDelegate委托来表示,但是将其定义成一个普通的类型具有更好的编程体验。考虑到动态注入依赖服务的需要,我们并没有为拦截器定义任何的接口和基类,而是采用基于约定的定义方式。这一点与ASP.NET Core基于约定的中间件定义方法类似,由于我们的拦截器委托比中间件委托要简洁,基于约定的拦截器自然比定义中间件要简单。中间件定义按照如下的约定即可:

  • 将中间件定义成一个可以被依赖注入容器实例化的类型,一般定义成公共实例类型即可;

  • 构造函数的选择由依赖注入容器决定,构造函数可以包含任意参数;

  • 拦截操作定义在一个方法类型为ValueTask并被命名为InvokeAsync的异步方法中,该方法必须包含一个表示当前调用上下文的InvocationContext类型的参数,该参数在参数列表的位置可以任意指定。

  • InvokeAsync方法可以注入任意能够从依赖注入容器提供的对象。

按照约定定义的中间件类型或者此类型的对象最终都需要转换成一个InvokeDelegate对象,此项功能体现在IConventionalInterceptorFactory接口的两个CreateInterceptor重载方法上。第一个重载的arguments将被作为调用构造函数的参数,对于依赖注入容器无法提供的参数必须在此指定。内部类型ConventionalInterceptorFactory以表达式树的形式实现了这个接口,具体实现就不在这里展示了,有兴趣的朋友可以查看源代码。

public interface IConventionalInterceptorFactory
{InvokeDelegate CreateInterceptor(Type interceptorType, params object[] arguments);InvokeDelegate CreateInterceptor(object interceptor);
}internal sealed class ConventionalInterceptorFactory : IConventionalInterceptorFactory
{public InvokeDelegate CreateInterceptor(Type interceptorType, params object[] arguments);public InvokeDelegate CreateInterceptor(object interceptor);
}

三、基于调用上下文的依赖注入容器

InvocationContext的InvocationServices属性返回针对当前调用上下文的依赖注入容器。在默认的情况下,我们会在创建InvocationContext上下文的时候创建一个服务范围,并使用此范围的IServiceProvider对象作为其InvocationServices属性。注入到InvokeAsync方法中的依赖服务是在调用时利用此IServiceProvider对象动态提供的,我们也可以在实现的InvokeAsync方法中安全的使用此对象来提供所需的服务实例。由于服务范围会在调用结束之后被自动终结,所以非单例服务实例能够被正常回收。

如下所示的IInvocationServiceScopeFactory接口表示用来创建上述服务范围的工厂,代表服务范围的IServiceScope对象由其CreateInvocationScope方法创建,InvocationServiceScopeFactory是对该接口的默认实现。

public interface IInvocationServiceScopeFactory
{IServiceScope CreateInvocationScope();
}internal class InvocationServiceScopeFactory : IInvocationServiceScopeFactory
{private readonly IApplicationServicesAccessor _applicationServicesAccessor;public InvocationServiceScopeFactory(IApplicationServicesAccessor applicationServicesAccessor) => _applicationServicesAccessor = applicationServicesAccessor ?? throw new ArgumentNullException(nameof(applicationServicesAccessor));public IServiceScope CreateInvocationScope()=> _applicationServicesAccessor.ApplicationServices.CreateScope();
}

如果在一个ASP.NET Core应用中,我们因为针对当前请求的IServiceProvider(RequestServices)对象作为调用上下文的InvocationServices也许更为适合,所以在ASP.NET Core应用中注册的IInvocationServiceScopeFactory实现类型为如下这个RequestServiceScopeFactory 类型。

internal class RequestServiceScopeFactory :  IInvocationServiceScopeFactory
{private readonly InvocationServiceScopeFactory _factory;private readonly IHttpContextAccessor  _httpContextAccessor;private NullServiceScope? _nullServiceScope;public RequestServiceScopeFactory(IServiceProvider  serviceProvider, IHttpContextAccessor httpContextAccessor){_factory = ActivatorUtilities.CreateInstance<InvocationServiceScopeFactory>(serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)));_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));}public IServiceScope CreateInvocationScope(){_nullServiceScope ??= new NullServiceScope (_httpContextAccessor);return _httpContextAccessor.HttpContext == null? _factory.CreateInvocationScope(): _nullServiceScope;}private class NullServiceScope : IServiceScope{private readonly IHttpContextAccessor _httpContextAccessor;public NullServiceScope(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;public IServiceProvider ServiceProvider => _httpContextAccessor.HttpContext?.RequestServices!;public void Dispose() { }}
}

四、拦截器的提供

我们利用如下这个IInterceptorProvider接口来表示拦截器的“提供者”,它定义的GetInterceptors方法为指定类型的方法提供一组可供排序的拦截器,该方法返回一组Sortable<InvokeDelegate>对象,每个Sortable<InvokeDelegate>对象的Value属性代表作为拦截器的InvokeDelegate委托,Order属性用来对拦截器进行排序。

public interface IInterceptorProvider
{bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed);IEnumerable<Sortable<InvokeDelegate>> GetInterceptors(Type targetType, MethodInfo method);void Validate(Type targetType, Action<MethodInfo> methodValidator, Action<PropertyInfo> propertyValidator) ;
}public sealed class Sortable<T>
{public int Order { get; }public T Value { get; set; }public Sortable(int order, T value){Order = order;Value = value;}
}

IInterceptorProvider旨在为指定的方法提供拦截器,所以它体现的是针对拦截器的注册,即采用怎样的方式将拦截器应用到期望的目标方法上。根据Dora.Interception的实现原理,并不是每一个方法都能被拦截,所以我们为IInterceptorProvider定义了一个Validate方法用来验证被应用到指定方法或者属性上的拦截器是否有效。具体的验证逻辑无需自行实现,只需要调用该方法提供的两个作为验证器的参数(methodValidator和propertyValidator)就可以了。这样做的好处是今早确定我们针对某个方法的拦截意图是否能够生效,Dora.Interception提供的两种原生的实现均实现了验证功能,对于自定义的实现,可以根据需要决定是否需要验证。

IInterceptorProvider接口还定义了CanIntercept方法用来确定指定类型的方法能否被拦截。一般来说,如果指定方法上没有注册拦截器,方法自然不会被拦截。但是很多时候我们需要显式屏蔽掉某个方法、属性甚至类型的拦截特性,我们认为这样的设置具有最高优先级,所以即使被注册了拦截器(包括被其他IInterceptorProvider注册)也不能被拦截,输出参数suppressed的作用就体现在这里。

由于拦截器大部分情况下都采用基于约定的类型来定义,所以针对拦截器的注册对于最终用户来说也应该针对拦截器类型或者实例进行,所以我们通过实现IInterceptorProvider接口定义了如下这个InterceptorProviderBase基类,它利用InterceptorFactory属性返回的IConventionalInterceptorFactory方便我们将按照约定定义的拦截器类型或对应的对象转换成标InvokeDelegate。

public abstract class InterceptorProviderBase : IInterceptorProvider
{public IConventionalInterceptorFactory InterceptorFactory { get; }protected InterceptorProviderBase(IConventionalInterceptorFactory interceptorFactory)=> InterceptorFactory = interceptorFactory ?? throw new ArgumentNullException(nameof(interceptorFactory));public abstract bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed);public abstract IEnumerable<Sortable<InvokeDelegate>> GetInterceptors(Type targetType, MethodInfo method);
}

Dora.Interception默认实现了两种拦截器的注册方法就是由对应的IInterceptorProvider实现类型达成的。具体来说,DataAnnotationInterceptorProvider实现了针对特性标注的拦截器注册,基于Lambda表达式的拦截器注册方式由ExpressionInterceptorProvider来完成。

五、调用链的构建

如果我们能够将针对目标方法的调用也转换成一个InvokeDelegate委托,意味着针对整个拦截方法的调用链就由一系列InvokeDelegate委托构建而成,此项工作体现在如下这个IMethodInvokerBuilder接口的Build方法上,该方法旨在为指定的可拦截的方法创建一个代表方法调用链的InvokeDelegate对象,其三个参数分别代表目标类型、方法和用来完成目标方法调用的InvokeDelegate委托。IMethodInvokerBuilder接口还定义了一个CanIntercept方法用来确定指定的方法能否或者是否需要被拦截。

public interface IMethodInvokerBuilder
{InvokeDelegate Build(Type targetType, MethodInfo method, InvokeDelegate targetMethodInvoker);bool CanIntercept(Type targetType, MethodInfo method);
}

DefaultMethodInvokerBuilder实现了上面这个接口。如代码片段所示,注册的所有IInterceptorProvider对象被注入构造函数之中。在实现了Build方法中,它利用这些IInterceptorProvider对象得到注册到指定方法的所有拦截器,并按照顺序构建成一个由InvokeDelegate委托表示的调用链。确保在拦截器执行之前对“下一个InvokeDelegate”进行设置也是在这里完成的。另一个CanIntercept方法的实现就更简单了,按照提供的逻辑:如果任何一个IInterceptorProvider对象将指定方法的拦截功能屏蔽掉,该方法就返回false,否则此方法的方法值体现的是是否由任何一个IInterceptorProvider对象决定拦截指定的方法。

internal class DefaultMethodInvokerBuilder : IMethodInvokerBuilder
{private readonly IEnumerable<IInterceptorProvider> _interceptorProviders;private readonly Dictionary<Tuple<Type, MethodInfo>, Sortable<InvokeDelegate>[]> _cache = new();public DefaultMethodInvokerBuilder(IEnumerable<IInterceptorProvider> interceptorProviders){_interceptorProviders = interceptorProviders ?? throw new ArgumentNullException(nameof(interceptorProviders));}public InvokeDelegate Build(Type targetType, MethodInfo method, InvokeDelegate targetMethodInvoker){Guard.ArgumentNotNull(targetType);Guard.ArgumentNotNull(method);Guard.ArgumentNotNull(targetMethodInvoker);if (!CanIntercept(targetType, method)){throw new InterceptionException($"The method '{method.Name}' of '{targetType}' cannot be interceptable.");}var key = new Tuple<Type, MethodInfo>(targetType, method);var interceptors = _cache.TryGetValue(key, out var value)? value!: _cache[key] = _interceptorProviders.SelectMany(it => it.GetInterceptors(targetType, method)).OrderBy(it => it.Order).ToArray();var length = interceptors.Length;interceptors = Enumerable.Range(0, length).Select(it => new Sortable<InvokeDelegate>(it, interceptors[it].Value)).ToArray();Array.ForEach(interceptors, Wrap);return interceptors[0].Value;void Wrap(Sortable<InvokeDelegate> sortable){var index = sortable.Order;var interceptor = sortable.Value;sortable.Value = context =>{context.Next = index < length - 1 ? interceptors![index + 1].Value : targetMethodInvoker;return interceptor(context);};}}public bool CanIntercept(Type targetType, MethodInfo method){Guard.ArgumentNotNull(targetType);Guard.ArgumentNotNull(method);bool interceptable = false;foreach (var provider in _interceptorProviders){if (provider.CanIntercept(targetType, method, out var suppressed)){interceptable = true;}if (suppressed){return false;}}return interceptable;}
}

便于生成的代码使用这个IMethodInvokerBuilder,我们把应用当前使用的IMethodInvokerBuilder对象赋值给如下这个MethodInvokerBuilder类型的静态属性Instance。

public static class MethodInvokerBuilder
{public static IMethodInvokerBuilder Instance { get; internal set; } = default!;
}

六、方法拦截的实现原理

实现AOP需要将应用到某个方法的拦截器“注入”到针对该方法的调用中,其注入方式大体分两类,一种是静态注入,另一种动态注入。静态注入是在编译的时候直接将针对拦截器的调用代码注入到目标方法中,这种注入方式对应用程序的运行不会带来任何负担,所以具有最好的性能,缺点就是无法应用一些动态的拦截策略。

动态注入则是在运行时注入拦截代码,它同样具有多种实现方式。很早之前,我们利用基于.NET Remoting的TranparentPoxy/RealProxy的方法动态分发机制可以很容易地实现针对指定方法的拦截,但是这种实现方式的性能堪忧。目前使用得最多就是采用IL Emit,它在IL语言的层面生成可被拦截的动态代理类。这种方式由于是直接面向IL编程,所以对于大部分编程人员来说都是一个不小的挑战,Dora.Interception之前的版本即是采用这种实现方式。动态编译还具有另一种方式,那就是利用CLR Profiler直接修改JIT生成的机器代码,著名的APM框架DataDog就是利用这种方式实现针对各种组件的分布式跟踪的。由于这种方式得用C++来写,对.NET开发人员的要求就更高了。

最新版本的Dora.Interception放弃了基于IL Emit的实现方案,因为这样的实现方式太过繁琐,开发、维护、诊断和升级都是巨大的挑战。一般来说,进行IL Emit编程都会先写出生成代码的C#形式,然后再将其转换成IL代码,如果我们能够直接将C#代码编译成IL代码,一切将会变得容易。实际上.NET的编译平台Roslyn本就可以将C#代码编程成对应的程序集,所以Dora.Interception直接利用了这个能力。

不论是上面提到的针对TranparentPoxy/RealProxy的实现,还是基于IL Emit,我们都需要利用一个“容器”来生成一个代理对象(如果直接使用目标类型的实例,其方法调用自然无法被拦截)。对于.NET (Core)来说,依赖注入容器无疑就是这个代理对象最好的创建者,所以Dora.Interception选择建立在依赖注入框架之上。对于注册到每个服务,如果目标类型的方法上注册了拦截器,我们会为它生成相应的代理类,并为此代理类生成对应的服务注册来替换原来的服务注册。

如果服务注册采用接口+实现类型(IFoobar/Foobar)的形式,代码生成器会采用如下的方式生成一个实现接口(IFoobar)同时封装目标对象(Foobar)的代理类(FoobarProxy)。FoobarProxy会实现定义在接口中的所有成员,如果方法调用需要被拦截,针对拦截器的调用会实现在该方法中,否则它只需要直接调用封装的对象即可。

d127c70acd69213507cb3236101f798b.png

如果服务注册并未使用接口,那么Flight.Interception只能采用方法重写的方式实现对方法调用的拦截,这意味着被拦截的方法只能是虚方法。如下图所示,如果给定服务注册的服务类型和实现类型均为Foobar,代码生成器生成的代理类FoobarProxy是Foobar的子类,它会重写需要拦截的方法来调用注册的拦截器。

e61d38fa38f4f994568dce2ccf3c46ce.png

如果对于如下这个基于接口的服务注册,如果某个需要拦截的方法并非接口方法。

public interface IFoobar
{Task InvokeAsync(int x, int y);
}public class Foobar : IFoobar
{[Interceptor(typeof(Interceptor1))]public Task InvokeAsync(int x, int y) => InvokeCoreAsync(x, y);[Interceptor(typeof(Interceptor2))]protected virtual Task InvokeCoreAsync(int x, int y) => Task.CompletedTask;
}

此时需要生成两个代理类。其中FoobarProxy1派生于Foobar,利用重写的InvokeCoreAsync方法解决针对非接口方法的拦截。FoobarProxy2实现IFoobar接口,并封装FoobarProxy1对象,解决接口方法的拦截。

c51a0776fd24171a8ef4035dfb88bb52.png

七、依赖注入框架的整合

我们为代理类型的生成定义了如下这个ICodeGenerator接口作为代码生成器。该接口的TryGenerate会为指定的服务注册(ServiceDescriptor)生成的代理类型。如果需要生成代理类(可被拦截的方法上被注册了任意拦截器)该方法返回True,生成的C#代码写入代表代码生成上下文的CodeGenerationContext 对象,输出参数proxyTypeNames返回生成的一个或者两个代理类的全名。至于名一个RegiserProxyType方法则使用针对生成的代理类型来替换现有的服务注册。

public interface ICodeGenerator
{bool TryGenerate(ServiceDescriptor serviceDescriptor, CodeGenerationContext codeGenerationContext, out string[]? proxyTypeNames);void RegisterProxyType(IServiceCollection services, ServiceDescriptor serviceDescriptor, Type[] proxyTypes);
}public sealed class CodeGenerationContext
{public ISet<Assembly> References { get; }public int IndentLevel { get; private set; }public string SourceCode { get; }public CodeGenerationContext WriteLines(params string[] lines);public IDisposable CodeBlock(string? start = null, string? end = null) ;public IDisposable Indent() ;
}

Dora.Interception提供了针对ICodeGenerator接口的两个内部实现类型(InterfaceProxyGenerator和VirtualMethodProxyGenerator),正式它们帮助我们生成了针对接口和虚方法的代理类型。由于涉及的实现比较繁琐,具体实现就不再这里提供了,有兴趣的朋友可以查看源代码。

我们知道依赖注入框架可以利用自定义的IServiceProviderFactory实现类型整合第三方依赖注入框架,Dora.Interception针对依赖注入框架的整合也是基于这样的实现。具体的实现类型就是如下这个InterceptableServiceProviderFactory 。

internal class InterceptableServiceProviderFactory : IServiceProviderFactory<InterceptableContainerBuilder>
{private readonly ServiceProviderOptions _options;private readonly Action<InterceptionBuilder>? _setup;public InterceptableServiceProviderFactory(ServiceProviderOptions options, Action<InterceptionBuilder>? setup){_options = options ?? throw new ArgumentNullException(nameof(options));_setup = setup;}public InterceptableContainerBuilder CreateBuilder(IServiceCollection services) => new(services, _options, _setup);public IServiceProvider CreateServiceProvider(InterceptableContainerBuilder containerBuilder) => containerBuilder.CreateServiceProvider();
}

InterceptableServiceProviderFactory 实现了IServiceProviderFactory<InterceptableContainerBuilder>接口,具体的实现体现作为泛型适配类型的InterceptableContainerBuilder类型上,如下就是该类型的定义。如代码片段所示,在创建最终的IServiceProvider对象之前,InterceptableContainerBuilder会利用提供的ICodeGenerator针对每个服务注册进行可拦截代理类型的生成。如果某个ICodeGenerator真正生成相应的代理类型,它最终还会负责完成该代理类型的注册。最终的IServiceProvider对象根据调整好的服务注册构建而成。

public sealed class InterceptableContainerBuilder
{private readonly IServiceCollection _services;private readonly ServiceProviderOptions _serviceProviderOptions;public InterceptableContainerBuilder(IServiceCollection services, ServiceProviderOptions serviceProviderOptions, Action<InterceptionBuilder>? setup){_services = Guard.ArgumentNotNull(services);services.AddInterception(setup);services.AddSingleton<IServiceLifetimeProvider>(new ServiceLifetimeProvider(services));_serviceProviderOptions = serviceProviderOptions ?? throw new ArgumentNullException(nameof(serviceProviderOptions));}public IServiceProvider CreateServiceProvider(){var provider = _services.BuildServiceProvider();try{var applicationServiceAccessor = provider.GetRequiredService<IApplicationServicesAccessor>();((ApplicationServicesAccessor) applicationServiceAccessor).ApplicationServices = provider;MethodInvokerBuilder.Instance = provider.GetRequiredService<IMethodInvokerBuilder>();var logger = provider.GetRequiredService<ILogger<InterceptableContainerBuilder>>();var log4GenerateCode = LoggerMessage.Define<string>(LogLevel.Information, 0, "Interceptable proxy classes are generated. " + Environment.NewLine + Environment.NewLine + "{0}");var codeGenerators = provider.GetServices<ICodeGenerator>();return CreateServiceProviderCore(codeGenerators, logger, log4GenerateCode);}finally{(provider as IDisposable)?.Dispose();}}private IServiceProvider CreateServiceProviderCore(IEnumerable<ICodeGenerator> codeGenerators, ILogger logger, Action<ILogger, string, Exception> log4GenerateCode){var generatedTypes = new List<GeneratedTypeEntry>();var generationContext = new CodeGenerationContext();generationContext.WriteLines("using System;");generationContext.WriteLines("using System.Reflection;");generationContext.WriteLines("using System.Threading.Tasks;");generationContext.WriteLines("using Microsoft.Extensions.DependencyInjection;");generationContext.WriteLines("");generationContext.WriteLines("namespace Dora.Interception.CodeGeneration");using (generationContext.CodeBlock()){foreach (var service in _services){foreach (var generator in codeGenerators){if (generator.TryGenerate(service, generationContext, out var proxyTypeNames)){generatedTypes.Add(new GeneratedTypeEntry(service, proxyTypeNames!, generator));break;}}}}log4GenerateCode(logger, generationContext.SourceCode, null!);if (generatedTypes.Any()){var compilation = CSharpCompilation.Create("Dora.Interception.CodeGeneration").WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)).AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(generationContext.SourceCode)).AddReferences(generationContext.References.Select(it => MetadataReference.CreateFromFile(it.Location)));Assembly outputAssembly;using var stream = new MemoryStream();var compilationResult = compilation.Emit(stream);if (!compilationResult.Success){var error = string.Join(Environment.NewLine, compilationResult.Diagnostics);throw new InterceptionException($"It fails to generate proxy class. \n {error}");}var bytes = stream.ToArray();outputAssembly = Assembly.Load(bytes);foreach (var entry in generatedTypes){var proxyTypes = entry.ProxyTypeNames.Select(it => outputAssembly.GetType(it)).ToArray();entry.CodeGenerator.RegisterProxyType(_services, entry.ServiceDescriptor, proxyTypes!);}}_services.Replace(ServiceDescriptor.Singleton<IServiceLifetimeProvider> (new ServiceLifetimeProvider(_services)));var serviceProvider = _services.BuildServiceProvider(_serviceProviderOptions);((ApplicationServicesAccessor)serviceProvider.GetRequiredService<IApplicationServicesAccessor>()).ApplicationServices = serviceProvider;MethodInvokerBuilder.Instance = serviceProvider.GetRequiredService<IMethodInvokerBuilder>();return serviceProvider;}private class GeneratedTypeEntry{public ServiceDescriptor ServiceDescriptor { get; }public string[] ProxyTypeNames { get; }public ICodeGenerator CodeGenerator { get; }public GeneratedTypeEntry(ServiceDescriptor serviceDescriptor, string[] proxyTypeNames, ICodeGenerator codeGenerator){ServiceDescriptor = serviceDescriptor;ProxyTypeNames = proxyTypeNames;CodeGenerator = codeGenerator;}}
}

前面广泛使用的BuildInterceptableServiceProvider扩展方法定义如下,它直接使用了InterceptionBuilder对象来创建返回的IServiceProvider对象。

public static class ServiceCollectionExtensions
{public static IServiceCollection AddInterception(this IServiceCollection services, Action<InterceptionBuilder>? setup = null);public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder>? setup = null)=> BuildInterceptableServiceProvider(services, new ServiceProviderOptions(), setup);public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, ServiceProviderOptions serviceProviderOptions, Action<InterceptionBuilder>? setup = null){Guard.ArgumentNotNull(services);Guard.ArgumentNotNull(serviceProviderOptions);var factory = new InterceptableServiceProviderFactory(serviceProviderOptions, setup);var builder = factory.CreateBuilder(services);return builder.CreateServiceProvider();}
}public sealed class InterceptionBuilder
{public IServiceCollection Services { get; }
}

用于整合ASP.NET Core的UseInterception扩展方法定义如下,它注册了上述的InterceptionServiceProviderFactory 类型,同时使用RequestServiceScopeFactory替换了默认的InvocationServiceScopeFactory,实现了将针对请求的IServiceProvider对象作为调用上下文的依赖注入容器。

public static class HostBuilderExtensions
{public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, Action<InterceptionBuilder>? setup = null)=> UseInterception(hostBuilder, new ServiceProviderOptions(), setup);public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, ServiceProviderOptions serviceProviderOptions, Action<InterceptionBuilder>? setup = null){if (hostBuilder == null) throw new ArgumentNullException(nameof(hostBuilder));if (serviceProviderOptions == null) throw new ArgumentNullException(nameof(serviceProviderOptions));hostBuilder.ConfigureServices((_, services) => services.AddHttpContextAccessor());Action<InterceptionBuilder> configure = builder =>{builder.Services.Replace(ServiceDescriptor.Singleton<IInvocationServiceScopeFactory, RequestServiceScopeFactory>());setup?.Invoke(builder);};return hostBuilder.UseServiceProviderFactory(new InterceptionServiceProviderFactory(serviceProviderOptions ?? new ServiceProviderOptions(), configure));}
}

八、看看生成的代理类

我们现在看看Dora.Interception生成的代理类型是拦截目标方法并执行注册的拦截器的。目标类型或者方法是否为泛型、及方法的返回类型(Void、一般类型、Task、Value、Task<TResult>和Value<TResult>)以及是否包含ref/in/out参数都会影响最终生成的代理类型,所以这里我们之谈论简单的形式。我们先来看看针对接口的服务注册最终会生成怎样的代理类型。如下面的代码片段所示,Foobar类型实现了IFoobar接口,对于实现的两个方法,InvokeAsync方法上注册了一个拦截器,Invoke方法则没有。

var foobar = new ServiceCollection().AddSingleton<IFoobar, Foobar>().BuildInterceptableServiceProvider().GetRequiredService<IFoobar>();public interface IFoobar
{Task InvokeAsync(int x, string y);void Invoke(int x, int y);
}public class Foobar : IFoobar
{[FakeInterceptor]public virtual Task InvokeAsync(int x, string y) => throw new NotImplementedException();public void Invoke(int x, int y) => throw new NotImplementedException();
}

对于如上基于IFoobar/Foobar的服务注册,最终会生成如下的代理类型FoobarProxy1479038137 (“1479038137”为随机生成的确保命名不会重读的后缀)。该类型实现了IFoobar接口,并利用封装的Foobar对象来实现该接口的两个方法。对于需要被拦截的InvokeAsync方法,会生成对应的方法调用上下文类型InvokeAsyncContext351732220,具体的实现方法上述的MethodInvokerBuilder对象生成与方法对应的调用管道来完成针对注册拦截器和目标方法的调用。至于另一个不需要被拦截的Invoke方法,直接调用目标对象Foobar对应的方法即可。生成的FoobarProxy1479038137 类型的服务注册将会覆盖原来的服务注册。

using Microsoft.Extensions.DependencyInjection;
using System.Reflection;namespace Dora.Interception.CodeGeneration
{public class FoobarProxy1479038137 : App.IFoobar, IInterfaceProxy{private readonly App.IFoobar _target;private readonly IInvocationServiceScopeFactory _scopeFactory;private static readonly Lazy<MethodInfo> _methodOfInvokeAsync607503395 = new Lazy<MethodInfo>(() => ProxyHelper.GetMethodInfo<App.Foobar>(100663305));private static readonly Lazy<InvokeDelegate> _invokerOfInvokeAsync951608024 = new Lazy<InvokeDelegate>(() => MethodInvokerBuilder.Instance.Build(typeof(App.Foobar), ProxyHelper.GetMethodInfo<App.Foobar>(100663305), InvokeAsync791452913));public FoobarProxy1479038137(IServiceProvider provider, IInvocationServiceScopeFactory scopeFactory){_target = ActivatorUtilities.CreateInstance<App.Foobar>(provider);_scopeFactory = scopeFactory;}public System.Threading.Tasks.Task InvokeAsync(System.Int32 x, System.String y){using var scope = _scopeFactory.CreateInvocationScope();var method = _methodOfInvokeAsync607503395.Value;var context = new InvokeAsyncContext351732220(_target, x, y, method, scope.ServiceProvider);var valueTask = _invokerOfInvokeAsync951608024.Value.Invoke(context);return valueTask.AsTask();}public static ValueTask InvokeAsync791452913(InvocationContext invocationContext){var context = (InvokeAsyncContext351732220)invocationContext;var target = (App.IFoobar)invocationContext.Target;var returnValue = target.InvokeAsync(context._x, context._y);context._returnValue = returnValue;return new ValueTask(returnValue);}public void Invoke(System.Int32 x, System.Int32 y)=> _target.Invoke(x, y);private class InvokeAsyncContext351732220 : InvocationContext{internal System.Int32 _x;internal System.String _y;internal System.Threading.Tasks.Task _returnValue;public override MethodInfo MethodInfo { get; }public override IServiceProvider InvocationServices { get; }public InvokeAsyncContext351732220(object target, System.Int32 x, System.String y, MethodInfo method, IServiceProvider invocationServices) : base(target){_x = x;_y = y;MethodInfo = method;InvocationServices = invocationServices;}public override TArgument GetArgument<TArgument>(string name){return name switch{"x" => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),"y" => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override TArgument GetArgument<TArgument>(int index){return index switch{0 => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),1 => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override InvocationContext SetArgument<TArgument>(string name, TArgument value){return name switch{"x" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext351732220, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),"y" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext351732220, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override InvocationContext SetArgument<TArgument>(int index, TArgument value){return index switch{0 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext351732220, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),1 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext351732220, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override TReturnValue GetReturnValue<TReturnValue>() => ProxyHelper.GetArgumentOrReturnValue<System.Threading.Tasks.Task, TReturnValue>(_returnValue);public override InvocationContext SetReturnValue<TReturnValue>(TReturnValue value) => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext351732220, System.Threading.Tasks.Task, TReturnValue>(this, value, (ctx, val) => ctx._returnValue = val);}}
}

现在我们将针对接口的服务注册替换成基于Foobar类型自身的注册。

var foobar = new ServiceCollection().AddSingleton< Foobar>().AddLogging(logging=>logging.AddConsole()).BuildInterceptableServiceProvider().GetRequiredService<Foobar>();

这次会生成如下所示的代理类FoobarProxy1224512711 。该类型将Foobar作为基类,通过重写InvokeAsync方法完成针对拦截器和目标方法的执行。至于不需要被拦截的Invoke方法则不需要考虑。最终针对Foobar/FoobarProxy1224512711的服务注册将用来替换掉现有针对Foobar的服务注册。

using System.Reflection;namespace Dora.Interception.CodeGeneration
{public sealed class FoobarProxy1224512711 : App.Foobar, IVirtualMethodProxy{private readonly IInvocationServiceScopeFactory _scopeFactory;private static readonly Lazy<MethodInfo> _methodOfInvokeAsync1812877040 = new Lazy<MethodInfo>(() => ProxyHelper.GetMethodInfo<App.Foobar>(100663305));private readonly Lazy<InvokeDelegate> _invokerOfInvokeAsync11225071;public FoobarProxy1224512711(IInvocationServiceScopeFactory scopeFactory) : base(){_scopeFactory = scopeFactory;_invokerOfInvokeAsync11225071 = new Lazy<InvokeDelegate>(() => MethodInvokerBuilder.Instance.Build(typeof(App.Foobar), ProxyHelper.GetMethodInfo<App.Foobar>(100663305), InvokeAsync243545362));}public override System.Threading.Tasks.Task InvokeAsync(System.Int32 x, System.String y){using var scope = _scopeFactory.CreateInvocationScope();var method = _methodOfInvokeAsync1812877040.Value;var context = new InvokeAsyncContext1151816636(this, x, y, method, scope.ServiceProvider);var valueTask = _invokerOfInvokeAsync11225071.Value.Invoke(context);return valueTask.AsTask();}public ValueTask InvokeAsync243545362(InvocationContext invocationContext){var context = (InvokeAsyncContext1151816636)invocationContext;var returnValue = base.InvokeAsync(context._x, context._y);context._returnValue = returnValue;return new ValueTask(returnValue);}private class InvokeAsyncContext1151816636 : InvocationContext{internal System.Int32 _x;internal System.String _y;internal System.Threading.Tasks.Task _returnValue;public override MethodInfo MethodInfo { get; }public override IServiceProvider InvocationServices { get; }public InvokeAsyncContext1151816636(object target, System.Int32 x, System.String y, MethodInfo method, IServiceProvider invocationServices) : base(target){_x = x;_y = y;MethodInfo = method;InvocationServices = invocationServices;}public override TArgument GetArgument<TArgument>(string name){return name switch{"x" => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),"y" => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override TArgument GetArgument<TArgument>(int index){return index switch{0 => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),1 => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override InvocationContext SetArgument<TArgument>(string name, TArgument value){return name switch{"x" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1151816636, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),"y" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1151816636, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override InvocationContext SetArgument<TArgument>(int index, TArgument value){return index switch{0 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1151816636, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),1 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1151816636, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override TReturnValue GetReturnValue<TReturnValue>() => ProxyHelper.GetArgumentOrReturnValue<System.Threading.Tasks.Task, TReturnValue>(_returnValue);public override InvocationContext SetReturnValue<TReturnValue>(TReturnValue value) => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1151816636, System.Threading.Tasks.Task, TReturnValue>(this, value, (ctx, val) => ctx._returnValue = val);}}
}

对于针对接口的服务注册,如果拦截器被应用的方法并没有定义定义在该接口中,此时会综合应用上述两种代码生成方案。为了演示我们对IFoobar和Foobar类型做了如下的修改。

public interface IFoobar
{Task InvokeAsync(int x, string y);
}[FakeInterceptor]
public class Foobar : IFoobar
{public virtual Task InvokeAsync(int x, string y) => throw new NotImplementedException();public virtual void Invoke(int x, int y) => throw new NotImplementedException();
}

由于Foobar的两个方法都注册了拦截器,但是Invoke属于Foobar独有的方法,此时会生成如下两个代理类FoobarProxy1796625286和FoobarProxy1493741432 ,前者继承Foobar类型,后者实现IFoobar接口。基于IFoobar/FoobarProxy1493741432 的服务注册会用来替换现有的服务注册。

public sealed class FoobarProxy1796625286 : App.Foobar, IVirtualMethodProxy
{private readonly IInvocationServiceScopeFactory _scopeFactory;private static readonly Lazy<MethodInfo> _methodOfInvoke717038218 = new Lazy<MethodInfo>(() => ProxyHelper.GetMethodInfo<App.Foobar>(100663305));private readonly Lazy<InvokeDelegate> _invokerOfInvoke1480842411;public FoobarProxy1796625286(IInvocationServiceScopeFactory scopeFactory) : base(){_scopeFactory = scopeFactory;_invokerOfInvoke1480842411 = new Lazy<InvokeDelegate>(() => MethodInvokerBuilder.Instance.Build(typeof(App.Foobar), ProxyHelper.GetMethodInfo<App.Foobar>(100663305), Invoke880402619));}public override void Invoke(System.Int32 x, System.Int32 y){using var scope = _scopeFactory.CreateInvocationScope();var method = _methodOfInvoke717038218.Value;var context = new InvokeContext307659875(this, x, y, method, scope.ServiceProvider);var valueTask = _invokerOfInvoke1480842411.Value.Invoke(context);valueTask.GetAwaiter().GetResult();}public ValueTask Invoke880402619(InvocationContext invocationContext){var context = (InvokeContext307659875)invocationContext;base.Invoke(context._x, context._y);return ValueTask.CompletedTask;}private class InvokeContext307659875 : InvocationContext{internal System.Int32 _x;internal System.Int32 _y;public override MethodInfo MethodInfo { get; }public override IServiceProvider InvocationServices { get; }public InvokeContext307659875(object target, System.Int32 x, System.Int32 y, MethodInfo method, IServiceProvider invocationServices) : base(target){_x = x;_y = y;MethodInfo = method;InvocationServices = invocationServices;}public override TArgument GetArgument<TArgument>(string name){return name switch{"x" => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),"y" => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_y),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override TArgument GetArgument<TArgument>(int index){return index switch{0 => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),1 => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_y),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override InvocationContext SetArgument<TArgument>(string name, TArgument value){return name switch{"x" => ProxyHelper.SetArgumentOrReturnValue<InvokeContext307659875, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),"y" => ProxyHelper.SetArgumentOrReturnValue<InvokeContext307659875, System.Int32, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override InvocationContext SetArgument<TArgument>(int index, TArgument value){return index switch{0 => ProxyHelper.SetArgumentOrReturnValue<InvokeContext307659875, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),1 => ProxyHelper.SetArgumentOrReturnValue<InvokeContext307659875, System.Int32, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override TReturnValue GetReturnValue<TReturnValue>() => default;public override InvocationContext SetReturnValue<TReturnValue>(TReturnValue value) => this;}
}
public class FoobarProxy1493741432 : App.IFoobar, IInterfaceProxy
{private readonly App.IFoobar _target;private readonly IInvocationServiceScopeFactory _scopeFactory;private static readonly Lazy<MethodInfo> _methodOfInvokeAsync209693810 = new Lazy<MethodInfo>(() => ProxyHelper.GetMethodInfo<App.Foobar>(100663304));private static readonly Lazy<InvokeDelegate> _invokerOfInvokeAsync2048425446 = new Lazy<InvokeDelegate>(() => MethodInvokerBuilder.Instance.Build(typeof(App.Foobar), ProxyHelper.GetMethodInfo<App.Foobar>(100663304), InvokeAsync1286715673));public FoobarProxy1493741432(IServiceProvider provider, IInvocationServiceScopeFactory scopeFactory){_target = ActivatorUtilities.CreateInstance<Dora.Interception.CodeGeneration.FoobarProxy1796625286>(provider);_scopeFactory = scopeFactory;}public System.Threading.Tasks.Task InvokeAsync(System.Int32 x, System.String y){using var scope = _scopeFactory.CreateInvocationScope();var method = _methodOfInvokeAsync209693810.Value;var context = new InvokeAsyncContext1177601686(_target, x, y, method, scope.ServiceProvider);var valueTask = _invokerOfInvokeAsync2048425446.Value.Invoke(context);return valueTask.AsTask();}public static ValueTask InvokeAsync1286715673(InvocationContext invocationContext){var context = (InvokeAsyncContext1177601686)invocationContext;var target = (App.IFoobar)invocationContext.Target;var returnValue = target.InvokeAsync(context._x, context._y);context._returnValue = returnValue;return new ValueTask(returnValue);}private class InvokeAsyncContext1177601686 : InvocationContext{internal System.Int32 _x;internal System.String _y;internal System.Threading.Tasks.Task _returnValue;public override MethodInfo MethodInfo { get; }public override IServiceProvider InvocationServices { get; }public InvokeAsyncContext1177601686(object target, System.Int32 x, System.String y, MethodInfo method, IServiceProvider invocationServices) : base(target){_x = x;_y = y;MethodInfo = method;InvocationServices = invocationServices;}public override TArgument GetArgument<TArgument>(string name){return name switch{"x" => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),"y" => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override TArgument GetArgument<TArgument>(int index){return index switch{0 => ProxyHelper.GetArgumentOrReturnValue<System.Int32, TArgument>(_x),1 => ProxyHelper.GetArgumentOrReturnValue<System.String, TArgument>(_y),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override InvocationContext SetArgument<TArgument>(string name, TArgument value){return name switch{"x" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1177601686, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),"y" => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1177601686, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentException($"Invalid argument name {name}.", nameof(name))};}public override InvocationContext SetArgument<TArgument>(int index, TArgument value){return index switch{0 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1177601686, System.Int32, TArgument>(this, value, (ctx, val) => ctx._x = val),1 => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1177601686, System.String, TArgument>(this, value, (ctx, val) => ctx._y = val),_ => throw new ArgumentOutOfRangeException(nameof(index))};}public override TReturnValue GetReturnValue<TReturnValue>() => ProxyHelper.GetArgumentOrReturnValue<System.Threading.Tasks.Task, TReturnValue>(_returnValue);public override InvocationContext SetReturnValue<TReturnValue>(TReturnValue value) => ProxyHelper.SetArgumentOrReturnValue<InvokeAsyncContext1177601686, System.Threading.Tasks.Task, TReturnValue>(this, value, (ctx, val) => ctx._returnValue = val);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完成登录与注册页面的前端

完成登录与注册页面的HTMLCSSJS&#xff0c;其中的输入项检查包括&#xff1a; 用户名6-12位 首字母不能是数字 只能包含字母和数字 密码6-12位 注册页两次密码是否一致 JS&#xff1a; function fnLogin() {var uSer document.getElementById("user");var pAss do…

WPF效果第二百零一篇之实现合并单元格

早一段时间又一次出差青海省西宁市;回来又是总结又是各种琐事,也没顾得上去分享点东西;大周末的就在家分享一下,这二天再次基于ListBox实现的合并单元格的效果:1、ListBox嵌套ListBox的前台布局:<ListBox ItemsSource"{Binding LCPListData}" x:Name"Manufac…

ASP.NET Core中使用EasyCaching作为缓存抽象层

简介做后端开发&#xff0c;缓存应该是天天在用&#xff0c;很多时候我们的做法是写个帮助类&#xff0c;然后用到的时候调用一下。这种只适合简单层次的应用&#xff1b;一旦涉及到接口实现调整之类的&#xff0c;这种强耦合的做法很不合适。有些其他的功能又要去重复造轮子。…

visual studio开启多核编译方法

先按http://blog.csdn.net/acaiwlj/article/details/50240625的方法进行了VS多线程的启动。 原本以为按以下步骤设置就OK了&#xff0c;但是编译中无意间发些了一个warning&#xff1a;“/Gm”与多处理不兼容&#xff1b;忽略 /MP 开关&#xff01;&#xff01;&#xff01;&am…

聊聊storm nimbus的LeaderElector

为什么80%的码农都做不了架构师&#xff1f;>>> 序 本文主要研究一下storm nimbus的LeaderElector Nimbus org/apache/storm/daemon/nimbus/Nimbus.java public static void main(String[] args) throws Exception {Utils.setupDefaultUncaughtExceptionHandler();…

如果我去深圳,你会见我吗

▲图/ 深圳夜景初次见易小姐&#xff0c;还是21年的春节回老家的时候。想来20年因为疫情没有回家&#xff0c;家母几次三番电话里头表达的思念以及建议一些不靠谱的回家计划&#xff0c;着实有些不忍&#xff0c;确实有似“儿行千里母担忧”之理&#xff0c;索性拿着年假和加班…

开源轻量的 .NET 监控工具 - 看门狗

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具或组件&#xff0c;希望对您有用&#xff01;简介WatchDog 是一个使用 C# 开发的开源的轻量监控工具&#xff0c;它可以记录和查看 ASP.Net Core Web 和 WebApi 的实时消息、事件、…

BZOJ 3231: [Sdoi2008]递归数列 (JZYZOJ 1353) 矩阵快速幂

http://www.lydsy.com/JudgeOnline/problem.php?id3231和斐波那契一个道理在最后加一个求和即可1 #include<cstdio>2 #include<cstring>3 #include<iostream>4 //using namespace std;5 const int maxn10010;6 const double eps1e-8;7 long long modn;8 lon…

马斯克的火箭上天了,SpaceX开源项目也登上了热榜!

python知识手册SpaceX于美国东部时间5月30日下午3&#xff1a;22分将两位美国宇航员送往国际空间站&#xff0c;虽然这只是Demo任务&#xff0c;但SpaceX已经以其卓越工程优势、低廉的发射成本赢得了全球航天产业的信赖。同时也是除美俄中这些航天国家队以外&#xff0c;唯一独…

机器视觉Halcon教程(1.介绍)

前言本期教程主要教大家如何使用Halcon机器视觉&#xff0c;通过使用Halcon, 我们可以实现一些机器视觉的应用开发。例如: OCR识别、视觉定位、缺陷检测等内容。什么是halcon&#xff1f;简单来说, Halcon就是一款应用于机器视觉的软件&#xff0c;它提供了一套开发工具&#x…

网络时间的那些事及 ntpq 详解

2019独角兽企业重金招聘Python工程师标准>>> GMT (Greenwich Mean Time)格林威治时间 UTC (Coordinated Universal Time) 协调世界时 IAT (International Atomic Time),TAI 国际原子时 CST (Chinese Standard Time), 北京时间Gentoo&#xff08;也许其他发行版也是&…

【前端芝士树】Javascript的原型与原型链

【前端芝士树】Javascript的原型、原型链以及继承机制 前端的面试中经常会遇到这个问题&#xff0c;自己也是一直似懂非懂&#xff0c;趁这个机会整理一下0. 为什么会出现原型和原型链的概念 1994年&#xff0c;网景公司&#xff08;Netscape&#xff09;发布了Navigator浏览器…

C# 反射之Activator用法举例

概述程序运行时&#xff0c;通过反射可以得到其它程序集或者自己程序集代码的各种信息&#xff0c;包括类、函数、变量等来实例化它们&#xff0c;执行它们&#xff0c;操作它们&#xff0c;实际上就是获取程序在内存中的映像&#xff0c;然后基于这个映像进行各种操作。Activa…

MyBatis批量插入

转载于:https://blog.51cto.com/12701034/1929672

狐狸文│区块链发展的正路

&#xff08;图片出自网络&#xff0c;版权归原作者所有&#xff09;最近看了一本书&#xff1a;《美国增长的起落》。这本书是大部头&#xff0c;但看起来很过瘾。通过对这本书的阅读&#xff0c;我更新了自己对区块链发展的理解。这一年&#xff0c;“区块链”很热&#xff0…

Qt之水平/垂直布局(QBoxLayout、QHBoxLayout、QVBoxLayout)

简述 QBoxLayout可以在水平方向或垂直方向上排列控件&#xff0c;由QHBoxLayout、QVBoxLayout所继承。 QHBoxLayout&#xff1a;水平布局&#xff0c;在水平方向上排列控件&#xff0c;即&#xff1a;左右排列。 QVBoxLayout&#xff1a;垂直布局&#xff0c;在垂直方向上排列控…

Optaplanner终于支持多线程并行运行 - Multithreaded incremental solving

Optaplanner 7.9.0.Final之前&#xff0c;启动引擎开始对一个Problem进行规划的时候&#xff0c;只能是单线程进行的。也就是说&#xff0c;当引擎对每一个possible solution进行分数计算的过程中&#xff0c;细化到每个步骤(Caculation)&#xff0c;都只能排队在同一个线程中依…

python棋盘格_干货必看 | Python的turtle库之经典棋盘格

国际棋盘格是一个由9横9纵的线组成的格子正方形&#xff0c;用Python的turtle库进行绘制的时候&#xff0c;先做9横9纵的线&#xff0c;再填上灰色小正方形&#xff0c;这就可以完成一个棋盘格了&#xff0c;下面是具体的操作步骤。(一)整体代码1、import turtleimport turtle2…

ResourceManager中的Resource Estimator框架介绍与算法剖析

欢迎大家前往腾讯云社区&#xff0c;获取更多腾讯海量技术实践干货哦~ 本文由宋超发表于云社区专栏 本文首先介绍了Hadoop中的ResourceManager中的estimator service的框架与运行流程&#xff0c;然后对其中用到的资源估算算法进行了原理剖析。 一. Resource Estimator Service…

几十款 WPF 控件 - UI 库,总有一款适合你

几十款 WPF 控件 - UI 库&#xff0c;总有一款适合你独立观察员 2022 年 10 月 16 日引言众所周知&#xff0c;使用 WPF 框架能够开发出功能强大、界面美观的桌面端应用。能够达到这个效果&#xff0c;各种 WPF 的控件库、UI 库功不可没。所以&#xff0c;想着能不能收集一下目…