|
目录
在 Fireasy3 揭秘 -- 依赖注入与服务发现 这篇中,我们通过遍列程序集中的所有类,来查找三个类型的服务接口,这样应用在启动时会消耗一定的时间来处理这些事情。今天,我们将用 ISourceGenerator 来对它进行改进。
ISourceGenerator 是 Microsoft.CodeAnalysis.Analyzers 中的一项技术,它是基于代码分析的原理,在语法树中查找所需要的内容,通过这些内容再构造一段源代码,使得我们在编译程序集的时候,把这些代码一并编译进去。使用它的好处在于,它是在编译时生成的,而不像 Emit 或其他反射等方法来构建的动态代码一样,在运行时将耗费一定的性能。
需要新建一个 .net standard 2.0 的项目,并引入 Microsoft.CodeAnalysis.Analyzers 和 Microsoft.CodeAnalysis.CSharp,见 Fireasy.Common.Analyzers。
在项目里添加一个类,实现 ISourceGenerator 接口,如下:- [Generator]
- public class ServiceDiscoverGenerator : ISourceGenerator
- {
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>void ISourceGenerator.Initialize(GeneratorInitializationContext context)
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>{
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project> Debugger.Launch();
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project> context.RegisterForSyntaxNotifications(() => new ServiceDiscoverSyntaxReceiver());
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>}
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>void ISourceGenerator.Execute(GeneratorExecutionContext context)
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>{
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>}
- }
复制代码 Initialize 方法用于初始化生成器,使用 RegisterForSyntaxNotifications 方法向上下文注入一个语法接收器,以便用来分析语法树。这里的语法接收器有两种,分别是 ISyntaxReceiver 和 ISyntaxContextReceiver,后者可以从上下文中获取到 SemanticModel 对象,这样的话能够从语法节点中获取到定义的符号模型。使用符号模型相对于语法节点来说要更方便一些。下面是基于 ISyntaxContextReceiver 接口的语法接收器。- internal class ServiceDiscoverSyntaxReceiver : ISyntaxContextReceiver
- {
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>private const string SingletonServiceName = "Fireasy.Common.DependencyInjection.ISingletonService";
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>private const string TransientServiceName = "Fireasy.Common.DependencyInjection.ITransientService";
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>private const string ScopedServiceName = "Fireasy.Common.DependencyInjection.IScopedService";
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>private const string RegisterAttributeName = "Fireasy.Common.DependencyInjection.ServiceRegisterAttribute";
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>private List<ClassMetadata> _metadatas = new();
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>void ISyntaxContextReceiver.OnVisitSyntaxNode(GeneratorSyntaxContext context)
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>{
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project> if (context.Node is ClassDeclarationSyntax classSyntax)
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project> {
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project><Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>AnalyseClassSyntax(context.SemanticModel, classSyntax);
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project> }
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>}
- }
复制代码 OnVisitSyntaxNode 方法正如 lambda 表达式树的 ExpressionVisitor 一样,语法树中的每一个节点都会被它访问到。我们需要分析的是类,因此只需要处理 ClassDeclarationSyntax 语法即可。AnalyseClassSyntax 方法如下: 至此,我们就得到了一份可注册的元数据,它由一个实现类对应多个服务类。ClassMetadata 的定义如下: 好了,得到这一份元数据后,我们转到 ServiceDiscoverGenerator,看看下一步它要做什么。 在 Execute 方法中,拿到接收器分析出来的元数据,通过 BuildDiscoverSourceCode 方法去生成一段源代码。它是一个服务部署类,在 Configure 方法中,会把所有的服务描述添加到 IServiceCollection 容器内,如下: 到这里,源代码生成器就算是完成了,那接下来怎么让它工作呢?
首先,我们需要找到一个“宿主”,我之所以这么称呼,是因为 nuget 打包时,需要将分析器依附到一个包内,因此我选择 Fireasy.Common,在 Fireasy.Common 的项目文件中,加下以下一段代码,它的目的是当 Fireasy.Common 打包时,Fireasy.Common.Analyzers.dll 会自动打包到 analyzers 目录下,引用 Fireasy.Common 包时,会自动使用该分析器来生成代码。如下:- <Project Sdk="Microsoft.NET.Sdk">
- <Target Name="_IncludeAllDependencies" BeforeTargets="_GetPackageFiles">
- <ItemGroup>
- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project><None Include="..\Fireasy.Common.Analyzers\bin\$(Configuration)\**\*.dll" Pack="True" PackagePath="analyzers\dotnet\cs" />
- </ItemGroup>
- </Target>
- </Project>
复制代码 我们测试的时候,因为是直接引用的项目,因此需要引用包含分析器的项目,而且要加上 OutputItemType 和 ReferenceOutputAssembly,如下:- <Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <ProjectReference Include="..\..\libraries\Fireasy.Common.Analyzers\Fireasy.Common.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
- </ItemGroup>
- </Project>
复制代码 好了,编译测试项目,使用 ILSpy 反编译 dll 文件,你会发现,实现了 ISingletonService、ITransientService 或 IScopedService 的类自动注册进来了:- // __ServiceDiscoverNs.__ServiceDiscoverServicesDeployer
- using Fireasy.Common.DependencyInjection;
- using Fireasy.Common.Tests;
- using Microsoft.Extensions.DependencyInjection;
- void IServicesDeployer.Configure(IServiceCollection services)
- {
- services.AddSingleton(typeof(DependencyInjectionTests.ITestSingletonService), typeof(DependencyInjectionTests.TestSingletonServiceImpl));
- services.AddTransient(typeof(DependencyInjectionTests.ITestTransientService), typeof(DependencyInjectionTests.TestTransientServiceImpl));
- services.AddScoped(typeof(DependencyInjectionTests.ITestScopedService), typeof(DependencyInjectionTests.TestScopedServiceImpl));
- services.AddTransient(typeof(DependencyInjectionTests.ITestWithRegisterAttr), typeof(DependencyInjectionTests.TestWithRegisterAttrImpl));
- services.AddTransient(typeof(DependencyInjectionTests.TestWithRegisterAttrNonIntefaceImpl), typeof(DependencyInjectionTests.TestWithRegisterAttrNonIntefaceImpl));
- services.AddTransient(typeof(DependencyInjectionTests.IGenericService<, >), typeof(DependencyInjectionTests.GenericService<, >));
- services.AddTransient(typeof(DependencyInjectionTests.TestDynamicProxyClass), typeof(DependencyInjectionTests.TestDynamicProxyClass));
- services.AddTransient(typeof(ObjectActivatorTests.ITestService), typeof(ObjectActivatorTests.TestService));
- }
复制代码 另外还有一个小窍门,在测试项目的“依赖项”--“分析器”下,你会看到一个属于自己的分析器,依次展开,也会找到所生成的那个代码文件。
最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
本文相关代码请参考:
https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common.Analyzers/ServiceDiscover
https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/DependencyInjectionTests.cs
更多内容请移步官网 http://www.fireasy.cn 。
来源:https://www.cnblogs.com/fireasy/archive/2023/03/03/17174121.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|