0. 前言

上接:AOP有几种实现方式

接下来说说怎么做AOP的demo,先用csharp 说下动态编织和静态编织,有时间再说点java的对应内容。

第一篇先说Roslyn 怎么做个JIT的AOP demo。

为啥这样讲呢?

实际是因为Roslyn 已经包含了JIT的全部部分,那我也就不用说任何JIT的实现了。(真爽)

所以本篇实际说的是以下这些内容:

怎么引入Roslyn做JIT编译代码

代理模式的AOP是什么样

为什么不推荐在生产环境不做优化就这样玩?

1. JIT编译代码

Roslyn 是.NET的编译器,感兴趣的可以参见文档 https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/

实际上Roslyn已经做的非常简单了,几行代码引入进来就可以编译csharp代码了。

不信我们就手把手写一个

1.1 引入Roslyn包

 <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>

1.2 代码转化为语法树

public SyntaxTree ParseToSyntaxTree(string code)
{
var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, preprocessorSymbols: new[] { "RELEASE" });
// 有许多其他配置项,最简单这些就可以了
return CSharpSyntaxTree.ParseText(code, parseOptions);
}

1.3 准备编译器实例

public CSharpCompilation BuildCompilation(SyntaxTree syntaxTree)
{
var compilationOptions = new CSharpCompilationOptions(
concurrentBuild: true,
metadataImportOptions: MetadataImportOptions.All,
outputKind: OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe: true,
platform: Platform.AnyCpu,
checkOverflow: false,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
// 有许多其他配置项,最简单这些就可以了
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(i => !i.IsDynamic && !string.IsNullOrWhiteSpace(i.Location))
.Distinct()
.Select(i => MetadataReference.CreateFromFile(i.Location));
// 获取编译时所需用到的dll, 这里我们直接简单一点 copy 当前执行环境的
return CSharpCompilation.Create("code.cs", new SyntaxTree[] { syntaxTree }, references, compilationOptions);
}

1.4 编译到内存中

public Assembly ComplieToAssembly(CSharpCompilation compilation)
{
using (var stream = new MemoryStream())
{
var restult = compilation.Emit(stream);
if (restult.Success)
{
stream.Seek(0, SeekOrigin.Begin);
return AssemblyLoadContext.Default.LoadFromStream(stream);
}
else
{
throw new Exception(restult.Diagnostics.Select(i => i.ToString()).DefaultIfEmpty().Aggregate((i, j) => i + j));
}
}
}

1.5 测试一下

static void TestJIT()
{
var code = @"
public class HiJ
{
public void Test(string v)
{
System.Console.WriteLine($""Hi, {v}!"");
}
}";
var jit = new Jit();
var syntaxTree = jit.ParseToSyntaxTree(code);
var compilation = jit.BuildCompilation(syntaxTree);
var assembly = jit.ComplieToAssembly(compilation);
// test
foreach (var item in assembly.GetTypes())
{
Console.WriteLine(item.FullName);
item.GetMethod("Test").Invoke(Activator.CreateInstance(item), new object[] { "joker" });
}
}

运行结果:

HiJ

Hi, joker!

就这么简单,你就可以JIT了,想干什么都可以了。

2. 用代理方式实现AOP

2.1 回顾代理是什么

这里单独再说一下代理是什么,

毕竟很多AOP框架或者其他框架都有利用代理的思想,

为什么都要这样玩呢?

很简单,代理就是帮你做相同事情,并且可以比你做的更多,还一点儿都不动到你原来的代码。

比如如下 真实的class 和代理class 看起来一模一样

但两者的真实的代码可能是这样子的

RealClass:

public class RealClass
{
public virtual int Add(int i, int j)
{
return i + j;
}
}
 ProxyClass: public class ProxyClass : RealClass
{
public override int Add(int i, int j)
{
int r = 0;
i += 7;
j -= 7;
r = base.Add(i, j);
r += 55;
return r;
}
}

所以我们调用的时候会是这样

2.2 做一个Proxy代码生成器

那么我们来做一个上面例子中能生成一模一样的ProxyClass 代码生成器

首先,我们都知道 csharp 再运行中可以反射获取元数据(反编译出代码也可以做,就是我们杀鸡用牛刀呢?)

我们知道了元数据,就可以拼字符串拼出我们想要的代码(对,你没看错,我们拼字符串就够了)

废话不说, show you code

public class ProxyGenerator
{
public string GenerateProxyCode(Type type, Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall)
{
var sb = new StringBuilder();
sb.Append($"{(type.IsPublic ? "public" : "")} class {type.Name}Proxy : {type.Name} {{ ");
foreach (var method in type.GetTypeInfo().DeclaredMethods)
{
GenerateProxyMethod(beforeCall, afterCall, sb, method);
}
sb.Append(" }");
return sb.ToString();
}
private static void GenerateProxyMethod(Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall, StringBuilder sb, MethodInfo method)
{
var ps = method.GetParameters().Select(p => $"{p.ParameterType.FullName} {p.Name}");
sb.Append($"{(method.IsPublic ? "public" : "")} override {method.ReturnType.FullName} {method.Name}({string.Join(",", ps)}) {{");
sb.Append($"{method.ReturnType.FullName} r = default;");
beforeCall(sb, method);
sb.Append($"r = base.{method.Name}({string.Join(",", method.GetParameters().Select(p => p.Name))});");
afterCall(sb, method);
sb.Append("return r; }");
}
}

测试一下

public static class TestProxyGenerator
{
public static void Test()
{
var generator = new ProxyGenerator();
var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => { }, (sb, method) => { sb.Append("r++;"); });
Console.WriteLine(code);
}
}

结果:

public class RealClassProxy : RealClass { public override System.Int32 Add(System.Int32 i,System.Int32 j) {System.Int32 r = default;r = base.Add(i,j);r++;return r; } }

2.3 结合在一起测试一下

public static class TestProxyJit
{
public static RealClass GenerateRealClassProxy()
{
var generator = new ProxyGenerator();
var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => { }, (sb, method) => { sb.Append("r++;"); });
var jit = new Jit();
var syntaxTree = jit.ParseToSyntaxTree(code);
var compilation = jit.BuildCompilation(syntaxTree);
var assembly = jit.ComplieToAssembly(compilation);
return Activator.CreateInstance(assembly.GetTypes().First()) as RealClass;
}
public static void Test()
{
RealClass proxy = GenerateRealClassProxy();
var i = 5;
var j = 10;
Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");
}
}

结果为:

5 + 10 = 15, but proxy is 16

是的,我们写了这么多代码就是为了让 15 变成 16 ,让别人不知道 多了个 r++; ,这就是AOP的意义

完整的demo 放在 https://github.com/fs7744/AopDemoList

2.4 再完善完善就可以了。。。(也就再写个几年)

你只需要完善如下:

  • 支持 void 方法
  • 支持 async await 方法
  • 支持抽象类
  • 支持接口
  • 支持构造方法
  • 支持属性
  • 支持索引器
  • 支持 in out ref
  • 支持泛型
  • 支持嵌套类
  • 支持剩下的各种各样情况

嗯,相信你自己,你可以的

3. 不推荐在生产环境不经过优化就这样玩,为什么?

3.1 两幅图

手写的proxy :

jit 编译proxy:

随着需要编译的Proxy class 增多, cpu 和 内存都会一样增多

所以要使用呢,最好用一些优化过的方案,情况会好的多,比如 https://github.com/dotnetcore/Natasha

3.2 你信不信得过调用你api的对方

嗯,这是一个信任度的问题,所以不要调用 Roslyn sdk 的输入暴露不做校验,黑客的骚操作是大家永远想不完的

只要对方可信,Roslyn sdk api 别人是不会调用的哦

但是我们都知道前人们都告诉我们有个准则:不要相信任何Input。

所以大家的猫主子会不会跟着别人跑掉就看大家信心和手段了。

用 Roslyn 做个 JIT 的 AOP的更多相关文章

  1. 基于 Source Generators 做个 AOP 静态编织小实验

    0. 前言 上接:用 Roslyn 做个 JIT 的 AOP 作为第二篇,我们基于Source Generators做个AOP静态编织小实验. 内容安排如下: source generators 是什 ...

  2. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  3. 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?

    今年我一直在思考web开发里的前后端分离的问题,到了现在也颇有点心得了,随着这个问题的深入,再加以现在公司很多web项目的控制层的技术框架由struts2迁移到springMVC,我突然有了一个新的疑 ...

  4. 玩转spring boot——AOP与表单验证

    AOP在大多数的情况下的应用场景是:日志和验证.至于AOP的理论知识我就不做赘述.而AOP的通知类型有好几种,今天的例子我只选一个有代表意义的“环绕通知”来演示. 一.AOP入门 修改“pom.xml ...

  5. AOP的实现原理

    1 AOP各种的实现 AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. 类别 ...

  6. Unity 处理IOC AOP

    用Unity 可以做IOC(控制反转) AOP(切面)可以做统一的异常和日志处理,非常方便,项目中是用微软企业库中的Microsoft.Practices.Unity实现 1 定义接口与实现 //定义 ...

  7. AOP的实现机制--转

    原文地址:http://www.iteye.com/topic/1116696 1 AOP各种的实现 AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前 ...

  8. Spring实现AOP的4种方式

    了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...

  9. 【Java】实战Java虚拟机之五“开启JIT编译”

    今天开始实战Java虚拟机之五“开启JIT编译” 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实战Ja ...

随机推荐

  1. vue在移动端使用alloyfinger手势库操作图片拖拽、缩放

    最近开发一个活动需要在手机上给上传的头像加上边框.装饰,需要拖拽.手势缩放边框下的头像图片,因为是vue项目,开始尝试了vue-drag-resize这个组件,对图片拖拽支持很完美,但是无法手势缩放, ...

  2. 去年去阿里面试,面试官居然问我Java类和对象,我是这样回答的!

    1.谈谈你对Java面向对象的理解? 面向对象就是把构成问题的事务分解成一个个对象,建立对象的目的不是一个步骤,而是为了描述一个事务在解决问题中的行为.类是面向对象的一个重要概念,类是很多个具有相同属 ...

  3. 使用Camtasia制作游戏直播高能短视频

    随着电竞行业的兴起,很多主播都开始做起游戏直播.对于喜欢打游戏的朋友来说,观看游戏直播既可以提高游戏的技术,也能作为下班后的休闲娱乐.对于喜欢钻研游戏技术的朋友,制作一个游戏高能合集能也是一件很燃的事 ...

  4. 接上一篇:(三) Spring环境搭建

    3.1.获取 Spring framework jar 包 (一) spring官网下载 (二)spring的核心包 (三) 配置 XML 1. 新建立一个 xml.名字任意,如 applicatio ...

  5. C语言讲义——文件操作

    fopen( ) 函数:创建一个新的文件或者打开一个已有的文件 FILE *fopen( const char * filename, const char * mode ); 关于参数mode的取值 ...

  6. 编程小白必备——主流语言C语言知识点

    对于编程语言来说,经常看到有因为各自支持的语言阵营而互怼的,其实根本没那个必要,都只是一种工具而已.当多数主流语言都会使用时也许你就不会有偏见了,本质不过都是用来描述计算机的一个任务,只是每门语言设计 ...

  7. eNSP VLAN划分基础配置及Access接口

    交换机内实现VLAN通信拓扑图: 一.修改主机名并保存 1.进入系统视图模式(配置模式) <Huawei>system-view 2.sysname命令修改主机名为yanyuda [Hua ...

  8. 卡耐基梅隆大学(CMU)元学习和元强化学习课程 | Elements of Meta-Learning

    Goals for the lecture: Introduction & overview of the key methods and developments. [Good starti ...

  9. 口述完SpringMVC执行流程,面试官就让同事回家等消息了

    Srping MVC 执行流程真的是老生常谈的话题了,最近同事小刚出去面试,前面面试官相继问了几个 Spring 相关的问题,但当面试官问他,你知道 Srping MVC 的执行流程吗?小刚娴熟的巴拉 ...

  10. js预解析练习

    分析下面两段代码 console.log(num)//undefined var num = 10 f()//可以执行 //f2()不可以执行 console.log(f)//function con ...