翼度科技»论坛 编程开发 .net 查看内容

Fireasy3 揭秘 -- 使用 SourceGeneraor 实现动态代理(AOP)

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
目录


  • Fireasy3 揭秘 -- 依赖注入与服务发现
  • Fireasy3 揭秘 -- 自动服务部署
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 改进服务发现
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 实现动态代理(AOP)
  • Fireasy3 揭秘 -- 使用 Emit 构建程序集
  • Fireasy3 揭秘 -- 使用缓存提高反射性能
  • Fireasy3 揭秘 -- 动态类型及扩展支持
  • Fireasy3 揭秘 -- 线程数据共享的实现
  • Fireasy3 揭秘 -- 配置管理及解析处理
  • Fireasy3 揭秘 -- 数据库适配器
  • Fireasy3 揭秘 -- 解决数据库之间的语法差异
  • Fireasy3 揭秘 -- 获取数据库的架构信息
  • Fireasy3 揭秘 -- 数据批量插入的实现
  • Fireasy3 揭秘 -- 使用包装器对数据读取进行兼容
  • Fireasy3 揭秘 -- 数据行映射器
  • Fireasy3 揭秘 -- 数据转换器的实现
  • Fireasy3 揭秘 -- 通用序列生成器和雪花生成器的实现
  • Fireasy3 揭秘 -- 命令拦截器的实现
  • Fireasy3 揭秘 -- 数据库主从同步的实现
  • Fireasy3 揭秘 -- 大数据分页的策略
  • Fireasy3 揭秘 -- 数据按需更新及生成实体代理类
  • Fireasy3 揭秘 -- 用对象池技术管理上下文
  • Fireasy3 揭秘 -- Lambda 表达式解析的原理
  • Fireasy3 揭秘 -- 扩展选择的实现
  • Fireasy3 揭秘 -- 按需加载与惰性加载的区别与实现
  • Fireasy3 揭秘 -- 自定义函数的解析与绑定
  • Fireasy3 揭秘 -- 与 MongoDB 进行适配
  • Fireasy3 揭秘 -- 模块化的实现原理
  实现 AOP(面向切面编程)的实现方式有很多种,但无外乎静态纺织和动态编织两种。
  动态编织 在 Fireasy 2 中使用 Emit 实现了动态编织。Emit 的缺点很明显,首先就是要求对 IL 语言必须比较熟悉且考虑较全面,其次由于是动态编织,需要使用内存缓存来对代理类进行管理。
  静态编织 是指在代码编译时就将拦截器相关的代码插入到代码内部,运行时不耗用时间和内存空间。常用的有 MSBuild(如PostSharp) 和 Code Analyzers(本文中用到的 ISourceGenerator)。
  首先,定义拦截器的接口,Initialize/InitializeAsync 方法用于首次初始化,Intercept/InterceptAsync 用于拦截方法的执行和属性的访问。如下:
  1.     /// <summary>
  2.     /// 提供对类成员进行拦截的方法。
  3.     /// </summary>
  4.     public interface IInterceptor
  5.     {
  6.         /// <summary>
  7.         /// 使用上下文对象对当前的拦截器进行初始化。
  8.         /// </summary>
  9.         /// <param name="context">包含拦截定义的上下文。</param>
  10.         void Initialize(InterceptContext context);
  11.         /// <summary>
  12.         /// 将自定义方法注入到当前的拦截点。
  13.         /// </summary>
  14.         /// <param name="info">拦截调用信息。</param>
  15.         void Intercept(InterceptCallInfo info);
  16.     }
  17.     /// <summary>
  18.     /// 提供对类成员进行拦截的异步方法。
  19.     /// </summary>
  20.     public interface IAsyncInterceptor
  21.     {
  22.         /// <summary>
  23.         /// 使用上下文对象对当前的拦截器进行初始化。
  24.         /// </summary>
  25.         /// <param name="context">包含拦截定义的上下文。</param>
  26.         ValueTask InitializeAsync(InterceptContext context);
  27.         /// <summary>
  28.         /// 将自定义方法注入到当前的拦截点。
  29.         /// </summary>
  30.         /// <param name="info">拦截调用信息。</param>
  31.         ValueTask InterceptAsync(InterceptCallInfo info);
  32.     }
复制代码
  InterceptCallInfo 是拦截的调用信息,其中,Arguments 属性是调用的入参,ReturnValue 属性是返回的值。如下:
  1.     /// <summary>
  2.     /// 用于通知客户端的拦截信息。无法继承此类。
  3.     /// </summary>
  4.     public sealed class InterceptCallInfo
  5.     {
  6.         /// <summary>
  7.         /// 获取或设置定义的类型。
  8.         /// </summary>
  9.         public Type? DefinedType { get; set; }
  10.         /// <summary>
  11.         /// 获取或设置当前被拦截的方法或属性。
  12.         /// </summary>
  13.         public MemberInfo? Member { get; set; }
  14.         /// <summary>
  15.         /// 获取或设置方法的返回类型。
  16.         /// </summary>
  17.         public Type? ReturnType { get; set; }
  18.         /// <summary>
  19.         /// 获取或设置当前被拦截的目标对象。
  20.         /// </summary>
  21.         public object? Target { get; set; }
  22.         /// <summary>
  23.         /// 获取或设置拦截的类型。
  24.         /// </summary>
  25.         public InterceptType InterceptType { get; set; }
  26.         /// <summary>
  27.         /// 获取或设置方法的参数数组。
  28.         /// </summary>
  29.         public object[]? Arguments { get; set; }
  30.         /// <summary>
  31.         /// 获取或设置方法的返回值。
  32.         /// </summary>
  33.         public object? ReturnValue { get; set; }
  34.         /// <summary>
  35.         /// 获取或设置触发的异常信息。
  36.         /// </summary>
  37.         public Exception? Exception { get; set; }
  38.         /// <summary>
  39.         /// 获取或设置取消 Before 事件之后调用基类的方法。
  40.         /// </summary>
  41.         public bool Cancel { get; set; }
  42.         /// <summary>
  43.         /// 获取或设置是否中断后继拦截器的执行。
  44.         /// </summary>
  45.         public bool Break { get; set; }
  46.     }
复制代码
  最后定义一个特性 InterceptAttribute 用于指定类或方法上的拦截器类型,只需要在类、属性或方法上指定即可:
  1. [InterceptAttribute(typeof(SampleInterceptor))]
复制代码
  接下来,我们定义好注入代码的原型,以便下一步生成代码。比如方法的切面示例:
  1. public override string Test(string str)
  2. {
  3.     //定义由 InterceptAttribute 指定的拦截器实例
  4.     var interceptors = new List<IInterceptor> { new SampleInterceptor() };
  5.     var info = new InterceptCallInfo();
  6.     //拦截的对象为当前对象
  7.     info.Target = this;
  8.     //方法的入参
  9.     info.Arguments = new object[] { str };
  10.     //当前拦截的成员(方法)
  11.     info.Member = ((MethodInfo)MethodBase.GetCurrentMethod()).GetBaseDefinition();
  12.     try
  13.     {
  14.         //初始化
  15.         _Initialize(interceptors, info);
  16.         //通知拦截器方法即将被调用
  17.         _Intercept(interceptors, info, InterceptType.BeforeMethodCall);
  18.         //如果拦截器告知要取消执行
  19.         if (info.Cancel)
  20.         {
  21.             //返回值,如果方法为 void,那么直接 return
  22.             //在拦截器中可以给 ReturnValue 进行赋值,然后 Cancel = true
  23.             return info.ReturnValue == null ? default : (string)info.ReturnValue;
  24.         }
  25.         //调用父方法
  26.         info.ReturnValue = base.Test(str);
  27.         //通知拦截器方法已调用完成
  28.         _Intercept(interceptors, info, InterceptType.AfterMethodCall);
  29.     }
  30.     catch (System.Exception exp)
  31.     {
  32.         info.Exception = exp;
  33.         //通知拦截器,有异常抛出
  34.         _Intercept(interceptors, info, InterceptType.Catching);
  35.         throw exp;
  36.     }
  37.     finally
  38.     {
  39.         //任何时候,都会走到这一步
  40.         _Intercept(interceptors, info, InterceptType.Finally);
  41.     }
  42.     //返回值
  43.     return info.ReturnValue == null ? default : (string)info.ReturnValue;
  44. }
复制代码
  属性的切面也差不多,只是对 get 和 set 分别处理。我们来看一下 _Initialize 方法要实现的目的:
  1. private void _Initialize(List<IInterceptor> interceptors, InterceptCallInfo callInfo)
  2. {
  3.     if (!this._initMarks.Contains(callInfo.Member))
  4.     {
  5.         for (int i = 0; i < interceptors.Count; i++)
  6.         {
  7.             InterceptContext context = new InterceptContext(callInfo.Member, this);
  8.             interceptors[i].Initialize(context);
  9.         }
  10.         this._initMarks.Add(callInfo.Member);
  11.     }
  12. }
复制代码
  它的目的是让拦截器只调用一次 Initialize 方法。而 Intercept 方法用于通知拦截器进行拦截,在拦截器里给定 Break = true 时,后续的拦截器将不会被调用。
  1. private void _Intercept(List<IInterceptor> interceptors, InterceptCallInfo callInfo, InterceptType interceptType)
  2. {
  3.     callInfo.InterceptType = interceptType;
  4.     callInfo.Break = false;
  5.     for (int i = 0; i < interceptors.Count; i++)
  6.     {
  7.         if (callInfo.Break)
  8.         {
  9.             break;
  10.         }
  11.         interceptors[i].Intercept(callInfo);
  12.     }
  13. }
复制代码
  接下来,我们用 ISourceGenretor 来实现代码的生成。
  上篇 使用 SourceGeneraor 改进服务发现 已经讲解过 ISourceGenerator 的用法了,需要分别定义一个 ISyntaxContextReceiver 和 ISourceGenerator 的实现。
  定义 DynamicProxySyntaxReceiver 类,用来接收语法节点,并进行分析,找出可以拦截的方法和属性,以及拦截器等内容。这里,我着重讲解一下 AnalyseClassSyntax 方法的实现,如下:
  1.         /// <summary>
  2.         /// 分析类型语法。
  3.         /// </summary>
  4.         /// <param name="model"></param>
  5.         /// <param name="syntax"></param>
  6.         private void AnalyseClassSyntax(SemanticModel model, ClassDeclarationSyntax syntax)
  7.         {
  8.             var typeSymbol = (ITypeSymbol)model.GetDeclaredSymbol(syntax)!;
  9.             if (typeSymbol.IsSealed)
  10.             {
  11.                 return;
  12.             }
  13.             var interceptorMetadataOfClass = FindInterceptorMetadata(typeSymbol);
  14.             var metadata = new ClassMetadata(typeSymbol);
  15.             //获取所有成员
  16.             foreach (var memberSymbol in typeSymbol.GetMembers())
  17.             {
  18.                 //如果不是方法或属性
  19.                 if (memberSymbol.Kind != SymbolKind.Method && memberSymbol.Kind != SymbolKind.Property)
  20.                 {
  21.                     continue;
  22.                 }
  23.                 if (memberSymbol is IMethodSymbol method)
  24.                 {
  25.                     //构造器需要重载,所以也要记录下来
  26.                     if (method.MethodKind == MethodKind.Constructor)
  27.                     {
  28.                         metadata.AddConstructor(method);
  29.                         continue;
  30.                     }
  31.                     if (method.MethodKind != MethodKind.Ordinary)
  32.                     {
  33.                         continue;
  34.                     }
  35.                 }
  36.                 //方法定义为 virtual 并且是公共的
  37.                 if (!memberSymbol.IsVirtual || memberSymbol.DeclaredAccessibility != Accessibility.Public)
  38.                 {
  39.                     continue;
  40.                 }
  41.                 //查找方法上的 InterceptAttribute 特性
  42.                 if (memberSymbol.GetAttributes().Any(s => s.AttributeClass!.ToDisplayString() == InterceptorAttributeName))
  43.                 {
  44.                     var interceptorMetadataOfMember = FindInterceptorMetadata(memberSymbol);
  45.                     if (interceptorMetadataOfMember != null)
  46.                     {
  47.                         metadata.AddMember(memberSymbol, interceptorMetadataOfMember);
  48.                     }
  49.                 }
  50.                 //没找到方法上的特性,则使用类上定义的 InterceptAttribute 特性
  51.                 else if (interceptorMetadataOfClass != null)
  52.                 {
  53.                     var hasIgnoreThrowExpAttr = HasIgnoreThrowExceptionAttribute(memberSymbol);
  54.                     metadata.AddMember(memberSymbol, interceptorMetadataOfClass.Clone(!hasIgnoreThrowExpAttr));
  55.                 }
  56.             }
  57.             if (metadata.IsValid)
  58.             {
  59.                 _metadata.Add(FindUsings(syntax, metadata));
  60.             }
  61.         }
复制代码
  FindInterceptorMetadata 方法用于查找在类或方法、属性上定义的 InterceptAttribute 特性,并记录下拦截器的类型。如下:
  1.         /// <summary>
  2.         /// 获取拦截器的元数据。
  3.         /// </summary>
  4.         /// <param name="symbol"></param>
  5.         /// <returns></returns>
  6.         private InterceptorMetadata? FindInterceptorMetadata(ISymbol symbol)
  7.         {
  8.             var types = new List<ITypeSymbol>();
  9.             foreach (AttributeData classAttr in symbol.GetAttributes().Where(s => s.AttributeClass!.ToDisplayString() == InterceptorAttributeName))
  10.             {
  11.                 var interceptorType = GetInterceptorType(classAttr.ConstructorArguments[0].Value);
  12.                 if (interceptorType != null)
  13.                 {
  14.                     types.Add(interceptorType);
  15.                 }
  16.             }
  17.             if (!types.Any())
  18.             {
  19.                 return null;
  20.             }
  21.             var hasIgnoreThrowExpAttr = HasIgnoreThrowExceptionAttribute(symbol);
  22.             return new InterceptorMetadata(types, !hasIgnoreThrowExpAttr);
  23.         }
复制代码
  在 ClassMetadata 里,定义了可被拦截的方法、属性,以及构造器、引用的命名空间等信息,如下:
  1.     /// <summary>
  2.     /// 类的元数据。
  3.     /// </summary>
  4.     public class ClassMetadata
  5.     {
  6.         /// <summary>
  7.         /// 初始化 <see cref="ClassMetadata"/> 类的新实例。
  8.         /// </summary>
  9.         /// <param name="type">类型符号。</param>
  10.         public ClassMetadata(ITypeSymbol type)
  11.         {
  12.             Type = type;
  13.         }
  14.         /// <summary>
  15.         /// 获取类型符号。
  16.         /// </summary>
  17.         public ITypeSymbol Type { get; }
  18.         /// <summary>
  19.         /// 获取命名空间。
  20.         /// </summary>
  21.         public string Namespace => Type.ContainingNamespace.ToDisplayString();
  22.         /// <summary>
  23.         /// 获取类型的全名。
  24.         /// </summary>
  25.         public string TypeFullName => Type.ToDisplayString();
  26.         /// <summary>
  27.         /// 获取代理类的名称。
  28.         /// </summary>
  29.         public string ProxyTypeName => $"{Type.Name}_proxy_";
  30.         /// <summary>
  31.         /// 获取代理类的全名。
  32.         /// </summary>
  33.         public string ProxyTypeFullName => $"{Namespace}.{ProxyTypeName}";
  34.         /// <summary>
  35.         /// 获取源代码名称。
  36.         /// </summary>
  37.         public string SourceCodeName => Type.ToDisplayString().Replace(".", "_") + ".cs";
  38.         /// <summary>
  39.         /// 获取构造函数列表。
  40.         /// </summary>
  41.         public List<IMethodSymbol> Constructors { get; } = new();
  42.         /// <summary>
  43.         /// 获取可拦截的方法。
  44.         /// </summary>
  45.         public Dictionary<IMethodSymbol, InterceptorMetadata> Methods { get; } = new();
  46.         /// <summary>
  47.         /// 获取可拦截的属性。
  48.         /// </summary>
  49.         public Dictionary<IPropertySymbol, InterceptorMetadata> Properties { get; } = new();
  50.         /// <summary>
  51.         /// 获取引用的命名空间列表。
  52.         /// </summary>
  53.         public List<string> Usings { get; } = new();
  54.         /// <summary>
  55.         /// 添加可拦截的成员。
  56.         /// </summary>
  57.         /// <param name="symbol"></param>
  58.         /// <param name="metadata"></param>
  59.         public void AddMember(ISymbol symbol, InterceptorMetadata metadata)
  60.         {
  61.             if (symbol is IMethodSymbol method)
  62.             {
  63.                 Methods.Add(method, metadata);
  64.             }
  65.             else if (symbol is IPropertySymbol property && property.Parameters.Count() == 0) //忽略索引器
  66.             {
  67.                 Properties.Add(property, metadata);
  68.             }
  69.         }
  70.         /// <summary>
  71.         /// 添加构造方法。
  72.         /// </summary>
  73.         /// <param name="symbol"></param>
  74.         public void AddConstructor(IMethodSymbol symbol)
  75.         {
  76.             Constructors.Add(symbol);
  77.         }
  78.         /// <summary>
  79.         /// 添加引用的命名空间列表。
  80.         /// </summary>
  81.         /// <param name="usings"></param>
  82.         public void AddUsings(IEnumerable<string> usings)
  83.         {
  84.             Usings.AddRange(usings);
  85.         }
  86.         /// <summary>
  87.         /// 若有可拦截的方法或属性,则此元数据有效。
  88.         /// </summary>
  89.         public bool IsValid => Methods.Any() || Properties.Any();
  90.     }
复制代码
  接下来,在 DynamicProxyGenerator 类的 Execute 方法里,获取到以上记录到的元数据,分别创建 DynamicProxyClassBuilder 对象来生成代码,如下:
  1.         void ISourceGenerator.Execute(GeneratorExecutionContext context)
  2.         {
  3.             var mappers = new Dictionary<string, string>();
  4.             if (context.SyntaxContextReceiver is DynamicProxySyntaxReceiver receiver)
  5.             {
  6.                 var metadatas = receiver.GetMetadatas();
  7.                 metadatas.ForEach(s =>
  8.                 {
  9.                     context.AddSource(s.SourceCodeName, new DynamicProxyClassBuilder(s).BuildSource());
  10.                     mappers.Add(s.TypeFullName, s.ProxyTypeFullName);
  11.                 });
  12.                 //代码生成完毕后,还需要在部署器中,将父类和代理类添加到 Container 容器中
  13.                 if (mappers.Count > 0)
  14.                 {
  15.                     context.AddSource("DynamicProxyServicesDeployer.cs", BuildDeploySourceCode(mappers));
  16.                 }
  17.             }
  18.         }
复制代码
  DynamicProxyClassBuilder 类的核心的实现,在这里,将对构造函数进行重载,对所有的方法、属性进行切面代码的生成。由于篇幅有限,这里只贴上类的构建方法,其他的对照原型进行参悟,也是比较容易理解的。如下:
  1.         /// <summary>
  2.         /// 生成源代码。
  3.         /// </summary>
  4.         /// <returns></returns>
  5.         public SourceText BuildSource()
  6.         {
  7.             var sb = new StringBuilder();
  8.             foreach (var u in _metadata.Usings)
  9.             {
  10.                 sb.AppendLine(u.ToString());
  11.             }
  12.             sb.AppendLine("using System.Reflection;");
  13.             sb.AppendLine($@"
  14. namespace {_metadata.Namespace}
  15. {{
  16.     public class {_metadata.ProxyTypeName} : {_metadata.TypeFullName}, IDynamicProxyImplemented
  17.     {{
  18.         private List<System.Reflection.MemberInfo> _initMarks = new ();
  19.         
  20.         {BuildConstructors()}
  21.         {BuildInitializeMethod()}
  22.         {BuildInterceptMethod()}
  23.         {BuildMethods()}
  24.         {BuildProperties()}
  25.     }}
  26. }}");
  27.             return SourceText.From(sb.ToString(), Encoding.UTF8);
  28.         }
复制代码
  那么同样的,项目编译后,会生成一个 __DynamicProxyServicesDeployer 的部署器,目的是往 Container 里添加代理映射。如下:
  1. [assembly: ServicesDeployAttribute(typeof(__DynamicProxyNs.__DynamicProxyServicesDeployer))]
  2. internal class __DynamicProxyServicesDeployer : IServicesDeployer
  3. {
  4.     void IServicesDeployer.Configure(IServiceCollection services)
  5.     {
  6.         Container.TryAdd(typeof(DependencyInjectionTests.TestDynamicProxyClass), typeof(TestDynamicProxyClass_proxy_));
  7.         Container.TryAdd(typeof(DynamicProxyTests.TestProxy), typeof(TestProxy_proxy_));
  8.         Container.TryAdd(typeof(ObjectActivatorTests.TestServiceProxy2), typeof(TestServiceProxy2_proxy_));
  9.     }
  10. }
复制代码
  单元测试就不再赘述了,可以去这里查看 动态代理单元测试


  最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
  本文相关代码请参考:
  https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/DynamicProxy
  https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common.Analyzers/DynamicProxy
  https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/DynamicProxyTests.cs
  更多内容请移步官网 http://www.fireasy.cn 。

来源:https://www.cnblogs.com/fireasy/archive/2023/03/05/17179651.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具