经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.NET静态代码织入——肉夹馍(Rougamo)发布3.0
来源:cnblogs  作者:nigture  时间:2024/5/6 16:17:44  对本文有异议

肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应用启动的初始化时间让服务更快可用,同时还能对静态方法进行AOP操作。

正文

虽又是一个大版本,但本次大版本没有重大的功能上线,主要是修改了代码织入方式,这样的改动牵扯到一些现有功能。

代码织入方式变化

在3.0之前的版本中采用的是代码内嵌的方式进行织入,下面用简化代码进行演示:

  1. // 原始方法
  2. public void M()
  3. {
  4. Console.WriteLine(1);
  5. Console.WriteLine(2);
  6. Console.WriteLine(3);
  7. }
  8. // 3.0版本之前织入代码后
  9. public void M()
  10. {
  11. var context = new MethodContext(...);
  12. var mo = new AbcAttribute();
  13. mo.OnEntry(context);
  14. try
  15. {
  16. Console.WriteLine(1);
  17. Console.WriteLine(2);
  18. Console.WriteLine(3);
  19. mo.OnSuccess(context);
  20. }
  21. catch (Exception e)
  22. {
  23. context.Exception = e;
  24. mo.OnException(context);
  25. throw;
  26. }
  27. finally
  28. {
  29. mo.OnExit(context);
  30. }
  31. }

在3.0版本中采用的是代理调用的方式进行代码织入,将原方法拷贝为一个新方法$Rougamo_M,然后修改原方法进行代码织入后调用$Rougamo_M,简化代码如下:

  1. // 将原方法M拷贝为$Rougamo_M
  2. public void $Rougamo_M()
  3. {
  4. Console.WriteLine(1);
  5. Console.WriteLine(2);
  6. Console.WriteLine(3);
  7. }
  8. // 修改原方法进行代码织入并调用拷贝后的原方法
  9. public void M()
  10. {
  11. var context = new MethodContext(...);
  12. var mo = new AbcAttribute();
  13. mo.OnEntry(context);
  14. try
  15. {
  16. $Rougamo_M();
  17. mo.OnSuccess(context);
  18. }
  19. catch (Exception e)
  20. {
  21. context.Exception = e;
  22. mo.OnException(context);
  23. throw;
  24. }
  25. finally
  26. {
  27. mo.OnExit(context);
  28. }
  29. }

不同织入方法带来的影响

ExMoAttribute的弃用

早在1.2版本中新增了ExMoAttribute,可能很多朋友对此都并不了解,ExMoAttribute主要用来屏蔽使用和不使用async/await语法所带来的差异,因为使用async/await语法后,在编译时会生成对应的状态机类型,那么肉夹馍就会对应修改状态机代码进行织入,而不使用async/await语法的方法就只能对原方法进行代码织入,下面用代码简单演示其中的差异:

  1. public async Task Delay()
  2. {
  3. Console.WriteLine(1);
  4. await Task.Delay(2000);
  5. Console.WriteLine(2);
  6. }
  7. // 使用async/await语法调用Delay
  8. public async Task WithSyntax()
  9. {
  10. var context = new MethodContext(...);
  11. var mo = new AbcAttribute();
  12. mo.OnEntry(context);
  13. try
  14. {
  15. await Delay();
  16. mo.OnSuccess(context);
  17. }
  18. catch (Exception e)
  19. {
  20. context.Exception = e;
  21. mo.OnException(context);
  22. throw;
  23. }
  24. finally
  25. {
  26. mo.OnExit(context);
  27. }
  28. }
  29. // 不使用async/await语法调用Delay
  30. public Task WithoutSyntax()
  31. {
  32. var context = new MethodContext(...);
  33. var mo = new AbcAttribute();
  34. mo.OnEntry(context);
  35. try
  36. {
  37. var task = Delay();
  38. mo.OnSuccess(context);
  39. return task;
  40. }
  41. catch (Exception e)
  42. {
  43. context.Exception = e;
  44. mo.OnException(context);
  45. throw;
  46. }
  47. finally
  48. {
  49. mo.OnExit(context);
  50. }
  51. }

在上面的代码示例中,没有使用async/await的WithoutSyntax方法会在Delay还没有执行完毕之前就执行OnSuccessOnExit方法。

ExMoAttribute针对没有使用async/await语法的方法通过在OnSuccess方法中使用ContinueWith达到在异步方法实际执行完毕后执行OnExit系列方法。ExMoAttribute虽然能够解决语法差异带来的问题,但也增加了一定的复杂性,同时因为其可能鲜为人知,所以在使用时因语法差异带来的问题可能后知后觉。

在3.0版本中由于使用代理调用的方式,被代理方法是否使用async/await语法是被屏蔽的,代理方只需要知道你的返回值是Task即可,所以在3.0版本中MoAttribute即可应对是否使用async/await语法的两种情况,ExMoAttribute在3.0版本中标记为Obsolete并将在4.0版本中直接删除。下面用代码简单说明3.0对于是否使用async/await语法的统一处理方式:

  1. // 拷贝WithSyntax原方法为$Rougamo_WithSyntax
  2. public async Task $Rougamo_WithSyntax()
  3. {
  4. await Delay();
  5. }
  6. // 拷贝WithoutSyntax原方法为$Rougamo_WithoutSyntax
  7. public Task $Rougamo_WithoutSyntax()
  8. {
  9. return Delay();
  10. }
  11. public async Task WithSyntax()
  12. {
  13. // ...代码织入
  14. try
  15. {
  16. await $Rougamo_WithSyntax();
  17. }
  18. catch
  19. {
  20. // ...代码织入
  21. }
  22. }
  23. public async Task WithoutSyntax()
  24. {
  25. // ...代码织入
  26. try
  27. {
  28. await $Rougamo_WithoutSyntax();
  29. }
  30. catch
  31. {
  32. // ...代码织入
  33. }
  34. }

async void 弱支持

如果说前面介绍的是织入方式改变带来的优势,那么这里介绍的就是劣势了。async void方法是一种特殊的异步方法,同样会生成对应的状态机类型,但调用该方法无法进行await操作,也就无法等待该方法实际执行完毕。在3.0版本之前,由于采取的是内嵌代码织入,直接修改状态机代码完成织入,所以OnExit系列方法可以在正确的时间点执行,而在3.0版本后由于采用了代理调用的方式,所以在执行OnExit系列方法时无法确保方法实际已经执行完毕。

关于async void的织入方式目前还在思考中,考虑到winform和wpf中可能存在不少的async void写法,代理调用的织入方式可能就无法满足目前的使用要求了,所以我将在github中发布一个issue进行投票统计,请日常开发涉及到async void的朋友移步到github( https://github.com/inversionhourglass/Rougamo/issues/68 )中进行投票,投票将在4.0版本开发末期截止。

支持步入调试

在3.0版本之前,应用了肉夹馍完成织入的方法在开发时无法进行步入调试,这是因为之前的版本没有对调试信息做对应的修改,没有去做这一功能也是因为比较复杂懒得整。在3.0修改代码织入方式后,修改对应的调试信息相对要简单许多,因此3.0版本支持步入调试

仅ref/out支持刷新参数

在2.1版本中新增刷新参数功能,支持在OnSuccess / OnException / OnExit中通过MethodContext.Arguments获取最新的参数值,但在3.0版本之后,由于织入代码方式的改变,此功能仅支持refout参数。

  1. public void M(int x, out decimal y, ref string z)
  2. {
  3. // ...
  4. }
  5. // 3.0 版本之前的织入方式
  6. public void M(int x, out decimal y, ref string z)
  7. {
  8. try
  9. {
  10. // ...
  11. // 由于是内嵌织入,在这里可以直接获取到所有最新的参数值,所以参数x也可以更新
  12. context.Arguments[0] = x;
  13. context.Arguments[1] = y;
  14. context.Arguments[2] = z;
  15. mo.OnSuccess(context);
  16. }
  17. catch (Exception e)
  18. {
  19. // ...
  20. // 由于是内嵌织入,在这里可以直接获取到所有最新的参数值,所以参数x也可以更新
  21. context.Arguments[0] = x;
  22. context.Arguments[1] = y;
  23. context.Arguments[2] = z;
  24. mo.OnException(context);
  25. throw;
  26. }
  27. }
  28. // 3.0 版本的织入方式
  29. public void M(int x, out decimal y, ref string z)
  30. {
  31. try
  32. {
  33. $Rougamo_M(x, out y, ref z);
  34. // 由于是代理调用织入,参数x在$Rougamo_M中被重新赋值后无法在外部获取,所以仅更新参数y和z
  35. context.Arguments[1] = y;
  36. context.Arguments[2] = z;
  37. mo.OnSuccess(context);
  38. }
  39. catch (Exception e)
  40. {
  41. // ...
  42. }
  43. }

构造方法织入方式不变

由于构造方法较为特殊,readonly字段仅可在构造方法中初始化,所以无法使用代理调用的织入方式,这也表示使用肉夹馍代码织入的构造方法无法支持步入调试。

织入方式切换

新的编织方式涉及众多代码,代码织入部分的代码近乎重写,虽然做了大量的测试,但为了保证稳定性提供了降级配置,修改项目中FodyWeavers.xml文件中Rougamo节点配置,通过设置proxy-calling="false",将织入方式改回3.0版本之前的内嵌织入方式。需要注意的是,该配置仅为过渡配置,将在4.0版本中移出并最终仅保留代理织入的方式,如果代理织入的方式存在任何问题,请及时反馈。

  1. <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  2. <Rougamo proxy-calling="false" />
  3. </Weavers>

其他更新

以下列出3.0版本相关的所有issue,有兴趣的可以直接移步github查看issue中的回复

  • #36 应用Rougamo的方法支持步入调试
  • #54 解决snupkg报checksum错误的问题,需直接依赖Fody,详见issue回复
  • #60 支持自定义AsyncMethodBuilder
  • #63 支持泛型Attribute
  • #65 修复特定Type类型无法作为MoAttribute构造方法参数

原文链接:https://www.cnblogs.com/nigture/p/18173550

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号