宝哥软件园

基于的动态编译实现代码 净标准

编辑:宝哥软件园 来源:互联网 时间:2021-08-31

在最后一篇文章的结尾[基于。net core],提到了如何自动便捷地生成微服务的客户端代理,使其对调用方透明,同时将枯燥的东西与框架集成,提高了使用的便利性。在尝试了基于Emit的中间语言之后,最终决定采用生成代码片段然后动态编译的模式。

1.背景:

首先,在前一篇文章中,我们通过框架实现了微服务对用户的透明调用,但是为每个服务编写一个客户端代理是极其繁琐的。第二,在项目中,前端站点使用了传统的。Net框架框架和后端微服务进行了转换。Net Core框架,所以将前端站点调整到。Net Core框架。为了同时支持这两个框架。如何使用?Net标准框架已经成为我们必须解决的问题。

2.问题转化

让我们简单回顾一下我们现在对微服务客户端代理的期望:

通过上面的分析,我们只需要判断服务接口中每个方法是否有返回值。如果有返回值,调用InvokeReturnType方法,如果没有返回值,调用InvokeWithoutReturn方法,然后依次传入接口名、方法名和方法参数。如果你熟悉Java,这个问题很容易解决。您可以使用动态代理创建这样一个匿名类。然而,在。net,动态代理的实现极其麻烦。首先我以为是通过中间语言IL的Emit实现的,但是用起来太不友好了,折腾了很久最终还是选择了放弃。后来我觉得这个代码片段可以动态生成,动态编译,加载到系统汇编中,应该没问题。所以在这个方向的指引下,我们努力一步一步实现这个问题。

3.解决方法

如何生成这个代码片段?通过上面的分析,我们知道我们只需要反映接口来获取公共方法,并原样复制接口的每个方法的签名,然后根据接口方法是否有返回值来调用RemoteServiceProxy基类中的相关方法。然而,需要特别注意通用方法的翻译。以下是生成此代码片段的参考实现。

找出服务接口程序集文件并处理每个文件

私有静态StringBuilder CreateApiProxyCode(){ var path=GetBinPath();var dir=new DirectoryInfo(路径);//获取微服务接口文件varfiles=dir.getfiles ('xzl *)。项目中的api.dll );var codeStringBuilder=new StringBuilder(1024);//添加必要的using code stringbuilder . appendline(' using system;') .附加行('使用系统。集合。泛型;') .附加行('使用系统。文本;') .AppendLine('使用XZL。基础设施. ApiService') .AppendLine('使用XZL。基础设施。定义;') .AppendLine('使用XZL。模型;') .AppendLine('命名空间XZL。ApiClientProxy ')。AppendLine(“{”);//命名空间begin //处理每个文件foreach(file中的var文件)中的接口信息{ create apiproycodefromfile(code stringbuilder,file);} codeStringBuilder。AppendLine(“}”);//命名空间结束返回codeStringBuilder}处理每个文件中的接口类型,找出每个程序集的依赖程序集,以便以后动态编译

私有静态void createapiproxydefromfile(StringBuilder文件代码生成器,FileInfo文件){尝试{ Assembly apiAssembly=Assembly .加载(文件。名称。子字符串(0,文件。名称。长度-4));var types=apiAssembly .GetTypes().其中(c=c.IsInterface c.IsPublic).to list();var apisvcytype=类型(IApiService);bool isNeed=falseforeach(类型中的类型){ //找出期望的接口类型if(!apiSvcType .IsAssignableFrom(类型)){继续} //找出接口的所有方法定义变量方法=类型GetMethods(BindingFlags .公共|绑定标签.扁平化层级|绑定标签。实例);if(!方法任何())(继续;} //定义代理类名,以及实现接口和继承RemoteServiceProxy文件代码生成器附录(美元)公共类{类型.全名。替换('。' _ ')}代理:' $'RemoteServiceProxy,{ 0类型。全名} ')。AppendLine(“{”);//开始上课/处理每个方法foreach(方法中的var mth){ createapiproxydefrom(文件代码生成器,类型,mth);}文件代码生成器.AppendLine(“}”;//类结束isNeed=true } if(isNeed){ var apiRefams=apiAssembly .getreferenceassemblies();重构列表.添加(apiAssembly .GetName());重构列表.AddRange(ApiRefams);} } catch { } }处理接口中的每个方法

私有静态void createapiproxycommmethod(StringBuilder文件代码生成器,类型,方法信息mth){ var is thread=!mth .返回类型。Equals(类型(void));文件代码生成器。追加(' public ');//添加返回值if(IsStreet){ FileCodeBuilder .追加(GetFriendytypename(mth .ReturnType)).追加("");} else { fileCodeBuilder .追加(“void”);} //方法参数开始文件代码生成器。追加(第五届会议.姓名)。追加('(');var mthParams=mth .GetParameters();if (mthParams .any()){ var mthparaList=new Liststring();foreach(mthParams中的var p){ mthparaList .添加(GetFriendlyTypeName(p .参数类型)' ' p . Name);}文件代码生成器.追加(字符串Join(',',mthparaList));} //方法参数结束文件代码生成器。追加(')');//方法体开始文件代码生成器AppendLine(“{”);if(IsM线程){//返回值文件代码生成器。追加("返回调用")。追加(GetFriendytypename(mth .ReturnType)).追加("");} else { fileCodeBuilder .追加(“调用而不返回”);} //拼接接口名及方法名文件代码生成器。追加($'('{type .全名} ','{mth .名称} ' ');//方法本身参数if (mthParams .Any()) { fileCodeBuilder .追加(',')。追加(字符串。联接(',',mthParams .选择(t=t .名称));}文件代码生成器.追加(');');//方法体结束文件代码生成器AppendLine(“}”;}获取泛型类型字符串

私有静态字符串GetFriendlyTypeName(类型类型){ if(!打字IsGenericType){ 0返回类型。全名;}字符串友好名称=类型.名称;int iBacktick=friendlyName .IndexOf(' ` ');if(iBacktick 0){友好名称=友好名称.移除(iBacktick);}友好名称=类型[]类型参数=类型.GetGenericArguments();for(int I=0;我输入参数。长度;I){字符串类型参数名=GetFriendlyTypeName(类型参数[I]);friendlyName=(i==0?typeParamName : ',' typeParamName);} friendlyName=返回友好名称}如何添加依赖

既然是要编译源码,那么源码中的依赖必不可少,在上一步中我们已经将每个程序集的依赖一并找出,接下来我们将这些依赖全部整理出来

//缓存程序集依赖var references=new ListMetadataReference();var RefSMFIles=new ListString();//系统依赖var sysRefLocation=typeof(可枚举)。GetTypeInfo()。组装。位置;重构文件.添加(SysRefLocation);//RefSMFIles原本缓存的程序集依赖重构文件.添加(对象的类型)。GetTypeInfo()。组装。位置);重构文件.添加范围(refAssemblyList .选择(t=装配。负载(t)10 .位置)。独特的.ToList());//传统。框架需要添加mscorlib.dll var Coredir=目录. GetParent(SysRefLocation);var mscorlibFile=coreDir .全名路径directorysparorchar ' mscorlib。dll ';如果(文件。存在(mscorlibFile)){ 0引用。添加(元数据参考.从文件创建(mscorlibFile));} var apiAsms=refAsmFiles .选择(t=元数据引用.CreateFromFile(t)).to list();参考文献添加范围(APIAMs);//当前程序集依赖变量此程序集=程序集GetEntryAssembly();if (thisAssembly!=null){ var referenceassemblies=此程序集.getreferenceassemblies();foreach(var reference Assembly in reference Assembly){ var loaded Assembly=Assembly .加载(引用程序集);参考文献。添加(元数据参考.CreateFromFile(加载程序集。位置));} }编译

有了代码片段,也有了编译程序集依赖,接下来就是最重要的编译了。

//定义编译后文件名变量路径=路径。组合(AppDomain .CurrentDomain。BaseDirectory,"代理");if(!目录。存在(路径)){目录创建目录(路径);} var apiRemoteProxyDllFile=路径。组合(路径,apiRemoteAsmName DateTime .现在。ToString(' yyymmdhmmssfff ')' .dll ');var树=语法工厂.ParseSyntaxTree(代码生成器. ToString());定义变量编译=CSharpCompilation .创建(apiRemoteAsmName).WithOptions(新的csharpciations(OutPut种类.DynamicallyLinkedLibrary)).添加引用(参考文献).添加语法树;//执行编译EmitResult compilationResult=编译发出(ApiRemoteProxylFile);if (compilationResult .成功){ //加载程序集apiRemoteAsm=程序集LoadFrom(ApiRemoteProxyDilfile);} else { foreach(compilationResult中的诊断代码问题。诊断){字符串问题=$ ' ID: {代码问题Id},消息: {代码问题.GetMessage()},“$”位置: {代码问题.位置。GetLineSpan()},“$”严重性: { codeIssue .严重性} ';大约。实例。日志。错误('自动编译代码出现异常,' '发行);}}结语

在经过以上处理后,虽算不上完美,但顺利的实现了我们期望的样子,在之前的服务中,当发现属于远程服务的时候,只需要类似如下形式返回代理对象即可。同时为增加调用更加顺畅,我们将此编译的时机定在了发生在程序启动的时候,ps当然或许还有一些其他更合适的时机。

静态concurrentdictionary字符串,Object svcininstance=new concurrentdictionary字符串,Object();var typeName=' XZL。“APIclientProxy”。(t ServiCe)类型。全名。替换(' . '、"_")"代理";对象obj=nullif (svcInstance .TryGetValue(typeName,out obj) obj!=null){ return(tServiCe)obj;}请尝试{ obj=(TService)apiRemoteAsm .CreateInstance(类型名);svcInstance .TryAdd(typeName,obj);} catch { throw new ICViperception($ '未找到{typeof(TService).全名}的有效代理');}返回(t ServiCe)obj;总结

以上所述是小编给大家介绍的基于。净标准的动态编译实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

更多资讯
游戏推荐
更多+