我用过的代码生成方式综述

这段时间,做了很多开发效率相关的事情,涉及到了不少代码生成的方法和思路,总结如下。生成代码分两部分:代码分析生成工具和代码模板

工具

按编写难度排序

live template

这个是intellij的一个简单工具,看起来就是对freemarker或者正则替换做了一些封装。代表方案当然就是内置的那些了。
创建很简单:https://www.jetbrains.org/intellij/sdk/docs/tutorials/live_templates/template_support.html
值得注意的是,可以在 Edit variables的地方指定更复杂的匹配,生成更丰富的代码。

这种方案的优点:

  • 无需开发工具
  • 对当前环境有一定的判断能力(edit variables)

缺点:

  • 强依赖ide
  • 能力太弱

Config文件

代表方案是GreenDao
生成代码的流程是独立于App编译过程的。即有一个Java console的main方法,执行这个方法,能够根据config在指定位置生成代码。如果要将这个融入到编译过程中,需要用gradle task 在所有编译前调用jar包。
GreenDao比较巧妙的使用了Java代码作为Config文件,大大简化了代码生成工具的复杂程度。在时机生成过程中,可以使用json、xml等作为配置文件(Spring模式),与写代码区别并不大,只是免去了复杂工具的编译时间。

这种方案的优点:

  • 工具编制简单
  • 生成的目标代码有一定的灵活性

缺点:

  • 对于现有类的理解分析能力基本为0,很难做复杂自动化生成
  • 嵌入的打包流程比较生硬

Annotation Processor

这是目前最常见的方案,个人感觉ButterKnifeAndroid annotation可以作为代表。
生成代码的流程是Java编译内置的hook点,安全性和兼容能力非常强。Java本身提供了javax.lang.model的整套代码分析体系,能够非常好的对现有代码进行分析。然而其中的element和typemirror之间的转换、类名获取这种事情非常容易被搞晕。
整个工具编写比较简单:tutorial。但是一旦出现正向(怎么做xxx)问题,很难找到know-how的人。反向问题(bug)的调试反而非常简单,因为生成的代码都是可见且可断点的(不能修改)。
apt能做的东西,个人理解有几种:生成boilerplate代码,ButterKnife这种;以编译期生成代码减少反射,EventBus 3.0的Subscriber Index,后面我还会开源一个无反射按field名读取值的apt工具;类的合法性校验,这个是只有apt才能做的。
这种方案的优点:

  • 工具编写比较简单,不需要深奥的知识
  • 对于代码的理解是非常非常好的,基本可以做到最极致的类分析
  • debug极其容易
  • 速度比较快(如果反复apt轮数比较少的话),技术还算普及

缺点:

  • javax.lang.model的文档和相关资料都很少
  • 不能修改现存的类
  • 必须遵守依赖和可见关系

AspectJ

这是个通用技术,虽然顶着AOP的帽子,很多时候也会被用来生成代码。Hugo是个非常好的例子,有一个通用的plugin在Android工程下使用AspectJ,而切面又非常简单,好懂。
本身文档还比较完整,只是index比较乱。具体的hook,应该是基于gradle plugin的(没细致研究过)。底层是基于ASM的,成熟没什么兼容性问题。对于现有类的分析基本靠固定的匹配机制,更多的是依赖字符串和正则,不容易分析更深层次的类行为(人家本来就是用来做切面定义的),而且对于切面来说,能够拿到的上下文信息比较受限。
对于扩展信息,多是基于不适用Android的aspectj language,比较头疼。
这种方案的优点:

  • 修改现有类而非生成新类
  • 相较于其他修改类的方法来说,使用更加广泛(Spring),稳定性更有保障

缺点:

  • 类理解和生成代码的风格、位置、上下文都受限,主要还是适合做大规模、重复性的简单工作
  • 运行时需要依赖库
  • hook方法时,会生成很多类,增加了调用,运行时性能有潜在风险简单细节
  • debug行号都是错的,基本就是个跪。甚至反编译都很难看懂(生成的函数都蛮混淆的)

Javassist

Javassist是改代码的工具里最接近apt的。有个实例工程可以拿来当模板。
文档有,但比较有用的仅限于官方的tutorial,文档内容丰富程度堪比javax.lang.model,完全看不懂。想要嵌入打包过程,必须自己写plugin和transform,里面的坑也不少,资料也不多。而对于类的匹配和遍历都是需要手动写,远远高于apt的复杂程度。当然,真正处理也是复杂一些。Groovy语言本身,也是一大槽点,编的时候什么都能过,运行的时候什么都挂。主要的优势在于全能修改类和无视依赖关系两项。

这种方案的优点:

  • 生成的代码模板来源可以比较广,可读性好(基本就是正常Java)
  • 匹配逻辑可以做的非常复杂,与apt合用的话,可以写的比较简单
  • 可以完全脱离正常的依赖路径,合包的时候做transform可以任意依赖
  • 反编译修改后的类,代码可读性比较好
  • 是插入代码,不是给方法套proxy(较于AspectJ),性能更有保障

缺点:

  • 需要手写plugin、transform、匹配等等一系列额外操作
  • groovy
  • debug能力未知

ASM

ASM就是究极超神方案了,直接搞bytecode。仅仅看懂了某个开源项目。也需要手写plugin和transform,和javassist的区别主要在于代码形式和对代码的遍历方式(tree visitor vs structured object)。过多的做评述。

AST

专门做代码分析的,待补充。

模板

手写

仅在极简单或没有模板可用的时候用,否则就是自作孽。

freemarker

通用技术,没有对java的优化。
优点:

  • 足够灵活,输入Map输出String
  • 有一个带占位符的template文件,修改简单

缺点:

  • 没有对于Java语言的支持,格式化和import都比较差

JavaPoet

针对Java生成的专门库,对于import、替换有非常好的支持。
优点:

  • 结构、使用方便
  • import、缩进支持的非常好

缺点:

  • 不太容易有template模板文件,修改大多需要通读代码

猜你喜欢

转载自blog.csdn.net/pouloghost/article/details/79654227
今日推荐