美文网首页Java成长之路
基于 Source Generators 做个 AOP 静态编织

基于 Source Generators 做个 AOP 静态编织

作者: 路人甲java | 来源:发表于2020-07-17 14:32 被阅读0次
image.png

0. 前言

副标题:无价值人生记录.0:浪费 1000% 时间去做一个用来节省 1% 时间的“轮子玩具”(下:AOP实践2 Source Generators)

上接:用 Roslyn 做个 JIT 的 AOP

作为第二篇,我们基于Source Generators做个AOP静态编织小实验。

内容安排如下:

  • source generators 是什么?

  • 做个达到上篇Jit 一样的效果的demo

  • source generators还存在什么问题?

1. Source Generators 是什么?

1.1 核心目的

开启dotnet平台的编译时元编程功能,

让我们能在编译时期动态创建代码,

同时考虑IDE的集成,让体验更舒适。

展开我们思想的翅膀

我们能以此做各种事情:

  • 生成实体json 等序列化器代码

  • AOP

  • 接口定义生成httpclient调用代码

  • 等等

如下是官方认为会受益的部分功能列表:

  • ASP.Net: Improve startup time

  • Blazor and Razor: Massively reduce tooling burden

  • Azure Functions: regex compilation during startup

  • Azure SDK

  • gRPC

  • Resx file generation

  • System.CommandLine

  • Serializers

  • SWIG

1.2 目前其设计和使用准则

  • 允许开发者能在编译时动态创建添加新代码到我们程序里面

  • 只能新增代码,不能修改已有代码

  • 当无法生成源时,生成器应当产生诊断信息,通知用户问题所在。

  • 可能访问其他文件非c#源代码文件。

  • 无序运行模式,每个生成器都只能拥有相同的输入编译,即不能用其他生成器的生成结果进行再次生成。

  • 生成器的运行类似于分析器。

2. 实验:代理模式的静态编织

2.1 创建一个Source Generators项目


<Project  Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

  <TargetFramework>netstandard2.0</TargetFramework>

  <LangVersion>8.0</LangVersion>

  </PropertyGroup>

  <PropertyGroup>

  <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>

  </PropertyGroup>

  <ItemGroup>

  <PackageReference  Include="Microsoft.CodeAnalysis.CSharp"  Version="3.6.0"  PrivateAssets="all"/>

  <PackageReference  Include="Microsoft.CodeAnalysis.Analyzers"  Version="3.0.0"  PrivateAssets="all" />

  </ItemGroup>

</Project>

2.2 创建SourceGenerator

需要继承 Microsoft.CodeAnalysis.ISourceGenerator

namespace  Microsoft.CodeAnalysis

{

  public  interface  ISourceGenerator

 {

  void  Initialize(InitializationContext context);

  void  Execute(SourceGeneratorContext context);

 }

}

并通过[Generator]标识启用

所以我们就可以做一个这样的代理生成器:

using Microsoft.CodeAnalysis;

using Microsoft.CodeAnalysis.CSharp.Syntax;

using Microsoft.CodeAnalysis.Text;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace  AopAnalyzer

{

 [Generator]

  public  class  ProxyGenerator : ISourceGenerator

 {

  public  void  Execute(SourceGeneratorContext context)

 {

  // retreive the populated receiver

  if (!(context.SyntaxReceiver is SyntaxReceiver receiver))

  return;

  try

 {

  // 简单测试aop 生成

 Action<StringBuilder, IMethodSymbol> beforeCall = (sb, method) => { };

 Action<StringBuilder, IMethodSymbol> afterCall = (sb, method) => { sb.Append("r++;"); };

  // 获取生成结果

  var code = receiver.SyntaxNodes

 .Select(i => context.Compilation.GetSemanticModel(i.SyntaxTree).GetDeclaredSymbol(i) as INamedTypeSymbol)

 .Where(i => i != null && !i.IsStatic)

 .Select(i => ProxyCodeGenerator.GenerateProxyCode(i, beforeCall, afterCall))

 .First();

 context.AddSource("code.cs", SourceText.From(code, Encoding.UTF8));

 }

  catch (Exception ex)

 {

  // 失败汇报

 context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("n001", ex.ToString(), ex.ToString(), "AOP.Generate", DiagnosticSeverity.Warning, true), Location.Create("code.cs", TextSpan.FromBounds(0, 0), new LinePositionSpan())));

 }

 }

  public  void  Initialize(InitializationContext context)

 {

  // Register a syntax receiver that will be created for each generation pass

 context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());

 }

  ///  <summary>

  /// 语法树定义收集器,可以在这里过滤生成器所需

  ///  </summary>

  internal  class  SyntaxReceiver : ISyntaxReceiver

 {

  internal List<SyntaxNode> SyntaxNodes { get; } = new List<SyntaxNode>();

  public  void  OnVisitSyntaxNode(SyntaxNode syntaxNode)

 {

  if (syntaxNode is TypeDeclarationSyntax)

 {

 SyntaxNodes.Add(syntaxNode);

 }

 }

 }

 }

}

具体的代理代码生成逻辑:


using Microsoft.CodeAnalysis;

using System;

using System.Linq;

using System.Text;

namespace  AopAnalyzer

{

  public  static  class  ProxyCodeGenerator

 {

  public  static  string  GenerateProxyCode(INamedTypeSymbol type, Action<StringBuilder, IMethodSymbol> beforeCall, Action<StringBuilder, IMethodSymbol> afterCall)

 {

  var sb = new StringBuilder();

 sb.Append($"namespace {type.ContainingNamespace.ToDisplayString()} {{");

 sb.Append($"{type.DeclaredAccessibility.ToString().ToLower()} class {type.Name}Proxy : {type.ToDisplayString()} {{ ");

  foreach (var method in type.GetMembers().Select(i => i as IMethodSymbol).Where(i => i != null && i.MethodKind != MethodKind.Constructor))

 {

 GenerateProxyMethod(beforeCall, afterCall, sb, method);

 }

 sb.Append(" } }");

  return sb.ToString();

 }

  private  static  void  GenerateProxyMethod(Action<StringBuilder, IMethodSymbol> beforeCall, Action<StringBuilder, IMethodSymbol> afterCall, StringBuilder sb, IMethodSymbol method)

 {

  var ps = method.Parameters.Select(p => $"{p.Type.ToDisplayString()}  {p.Name}");

 sb.Append($"{method.DeclaredAccessibility.ToString().ToLower()} override {method.ReturnType.ToDisplayString()}  {method.Name}({string.Join(",", ps)}) {{");

 sb.Append($"{method.ReturnType.ToDisplayString()} r = default;");

 beforeCall(sb, method);

 sb.Append($"r = base.{method.Name}({string.Join(",", method.Parameters.Select(p => p.Name))});");

 afterCall(sb, method);

 sb.Append("return r; }");

 }

 }

}

可以看到和之前jit的代码非常相似

2.3 测试一下

2.3.1 新建测试项目

<Project  Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

  <OutputType>Exe</OutputType>

  <TargetFramework>netcoreapp3.1</TargetFramework>

  <LangVersion>preview</LangVersion> //新版本才有哦,现在还未正式发布

  </PropertyGroup>

  <ItemGroup>

  <ProjectReference  Include="..\AopAnalyzer\AopAnalyzer.csproj"  

  OutputItemType="Analyzer"

  ReferenceOutputAssembly="false" />  //设置为分析器项目

  </ItemGroup>

</Project>

2.3.2 测试代码

using System;

namespace  StaticWeaving_SourceGenerators

{

  static  class  Program

 {

  static  void  Main(string[] args)

 {

  var proxy = new RealClassProxy(); // 对,生成的新代码可以ide里面直接用,就是这么强大,只要编译一次就看的到了

  var i = 5;

  var j = 10;

 Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");

 Console.ReadKey();

 }

 }

  public  class  RealClass

 {

  public  virtual  int  Add(int i, int j)

 {

  return i + j;

 }

 }

}

输出结果:

5 + 10 = 15, but proxy is 16

cpu 和内存,自然完美:

image

3. Source Generators 还有什么严重的缺陷呢?

3.1 不能引入其他程序集,比如nuget包

比如我们引入Newtonsoft.Json,就会造成如下编译异常:

System.IO.FileNotFoundException: 未能加载文件或程序集“Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。系统找不到指定的文件。

这就造成我们很难利用现有的包做各种事情,以及怎么把我们的代码生成器提供给别人使用了

3.2 不能debug(其实我接受这点)

可以通过UT测试 debug的

3.3 生成结果不能查看

对使用生成器的人会比较麻烦,他不知道具体成什么样子了,特别是生成有错误的时候。

4.后记

毕竟该功能还是实验特性,距离完成还有一定距离,

不过这样可以让语言的发展。

相关文章

  • 基于 Source Generators 做个 AOP 静态编织

    0. 前言 副标题:无价值人生记录.0:浪费 1000% 时间去做一个用来节省 1% 时间的“轮子玩具”(下:AO...

  • JVM-SANDBOX

    JVM-SANDBOX的核心功能是什么? 实时无侵入AOP框架 在常见的AOP框架实现方案中,有静态编织和动态编织...

  • 带你初识Java的代理模式

    Spring AOP是基于动态代理设计模式实现的,相对的就有静态代理 动态代理和静态代理 静态代理 对于静态代理,...

  • AOP原理详解

    1. 原理 AOP切面编程分为静态编织和动态代理两种模式。AOP其实就是设计模式中代 理模式,代理类全权代...

  • Spring AOP 实现原理

    Spring AOP 实现原理 静态代理 众所周知 Spring 的 AOP 是基于动态代理实现的,谈到动态代理就...

  • 动态AOP

    动态AOP 上篇文章我们讲到,AOP分为静态AOP和动态AOP。静态AOP在代码编译之后,已经有代理类或者已经改变...

  • Spring AOP中的动态代理:JDK动态代理和CGLIB动态

    静态代理模式  所谓静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。ApsectJ是静态...

  • spring aop 汇总

    静态代理、动态代理和cglib代理 aop 使用 Spring AOP - 注解方式使用介绍spring aop ...

  • 静态动态AOP

    静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的AOP代理类(生成的*.cla...

  • 10.Spring中事务控制

    1.基于XML的AOP实现事务控制 2.基于注解的AOP实现事务控制 基于注解的AOP实现事务控制,方便演示,我们...

网友评论

    本文标题:基于 Source Generators 做个 AOP 静态编织

    本文链接:https://www.haomeiwen.com/subject/ewyghktx.html