JavaPoet动态生成代码
来源:互联网 发布:传智播客oa项目数据库 编辑:程序博客网 时间:2024/05/16 14:05
- JavaPoet - 优雅地生成代码
- 一项目简介
- 二项目总览
- 1 大体结构图
- 2 关键类说明
- 三相关使用
- 1 API使用
- 2 一个简单示例
- 四源码浅析
- 五使用场景
- 1 根据编译时注解生成代码
- 11 前言
- 12 一个简单示例
- 2 根据协议文件生成对应代码
- 3 更多待扩展
- 1 根据编译时注解生成代码
- 六知识储备
- 1 注解处理器Annotation Processor
- 11 自定义注解处理器
- 12 注册注解处理器
- 13 comgoogleautoserviceauto-service
- 14 comneenbedanktandroid-apt
- 1 注解处理器Annotation Processor
- 七小结
- 八参考资料
一、项目简介
JavaPoet是square推出的开源Java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
项目主页及源码:https://github.com/square/javapoet
二、项目总览
该项目代码量相对较小,只有一个package(com.squareup.javapoet),所有类均位于该package下。
2.1 大体结构图
2.2 关键类说明
在JavaPoet中,JavaFile是对.java文件的抽象,TypeSpec是类/接口/枚举的抽象,MethodSpec是方法/构造函数的抽象,FieldSpec是成员变量/字段的抽象。这几个类各司其职,但都有共同的特点,提供内部Builder供外部更多更好地进行一些参数的设置以便有层次的扩展性的构造对应的内容。
另外,它提供$L(for Literals), $S(for Strings), $T(for Types), $N(for Names)等标识符,用于占位替换。
三、相关使用
3.1 API使用
关于JavaPoet 的API使用,官方Github主页已经有很详细的使用说明和示例了,具体可前往查看。此处不赘述,详见 项目主页、源码及使用说明
3.2 一个简单示例
下面就让我们以一个简单HelloWorld的例子来开启我们的JavaPoet之旅。
引入库:
build.gradle
- 1
- 1
例子如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上方的代码是通过下方代码调用JavaPoet的API生成的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
四、源码浅析
下面来看看调用了JavaFile的writeTo后实际做了些什么。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
通过源码可以知道,writeTo分为两部分:第一步收集import,记录下来后第二步才跟随内容一起写到CodeWriter。
另外我们可以看到源码中的emit方法,通过查看其它源码发现,在JavaPoet中,所有java文件的抽象元素都定义了emit方法,如TypeSepc,ParameterSepc等,emit方法传入CodeWriter对象输出字符串。上层元素调用下层元素的emit方法,如JavaFile的emit方法调用TypeSpec的emit方法,从而实现整个java文件字符串的生成。
下面我们以MethodSpec为例,查看其emit代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
可以看出,MethodSepc通过调用codeWriter的emit方法依次输出javadoc,annotation,parameter,codeblock等。
五、使用场景
5.1 根据编译时注解生成代码
5.1.1 前言
用过butterknife的同学会发现,使用butterknife我们可以省去平时重复书写的findViewById之类的代码,通过注解的方式即可实现。而早期的butterknife使用的注解是运行时注解,即运行时通过注解然后使用反射实现,存在一定的性能问题,后面作者做了改进,使用编译时注解,编译期间,在注解处理器中对注解进行处理生成相应代码。
通过查看butterknife源码,如下:
- build.gradle (butterknife-parent)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- build.gradle (butterknife-compiler)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- ButterKnifeProcessor.java (butterknife-compiler)
(注解处理器)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
可以看到butterknife在编译时在Processor中获取对应的注解,然后使用JavaPoet进行代码生成工作。(事实上开源框架Dagger也使用了JavaPoet)
5.1.2 一个简单示例
本节将简单演示利用编译时注解+JavaPoet来实现编译期间动态生成代码。
工程目录结构:
- Hello
- app
- hello-annotation (注解相关)
- hello-compiler (处理器生成代码相关)
①. 导入依赖:
build.gralde (project)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
build.gradle (Module:app)
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
build.gradle (Module:hello-compiler)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
注: 自Android Gradle 插件 2.2 版本开始,官方提供了名为 annotationProcessor 的功能来完全代替 android-apt。
若工程使用gradle版本>=2.2,则此处无需引用com.neenbedankt.android-apt相关,将 apt project(':hello-compiler')
改为 annotationProcessor project(':hello-compiler')
即可。
②. 定义注解: (Module:hello-annotation)
HelloAnnotation.java
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
③. 定义Processor: (Module:hello-compiler)
HelloProcessor.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
④. 使用注解并调用生成的类函数
MainActivity.java (Module:app)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
未编译前,HelloWorld.java是不存在的,这里会报错。那么,我们尝试编译一下,就会发现HelloWorld.java会自动生成,如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
5.2 根据协议文件生成对应代码
假设我们对类的声明以及接口的声明是以特定格式写在一个协议文件中,那么我们可以先读取该协议文件内容,使用JavaPoet根据协议对应生成Java代码。
如定义以下协议文件:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
那么利用JavaPoet我们可以生成对应的TestDemo.java, MyRequest.java, MyResponse.java, 以及TestDemo.java中对应的请求接口和实现。
注:此部分协议定义参考自google开源的protobuffer和grpc
5.3 更多待扩展
六、知识储备
6.1 注解处理器(Annotation Processor)
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册相应的注解处理器(自定义的注解处理器需继承自AbstractProcessor)。
6.1.1 自定义注解处理器
定义一个注解处理器,需要继承自AbstractProcessor。如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类如Elements, Types和Filer等。
- process(Set< ? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
- getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
- getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。
注: 注解处理器是运行在独立的虚拟机JVM中,javac启动一个完整Java虚拟机来运行注解处理器。
6.1.2 注册注解处理器
那么,如何将我们自定义的处理器MyProcessor注册到javac中呢?首先我们需要将我们的注解处理器打包到一个jar文件中,其次在这个jar中,需要打包一个特定的文件javax.annotation.processing.Processor到META-INF/services路径下。以下是这个jar的大致结构示意图:
- MyProcessor.jar
- com
- example
- MyProcessor.jar
- example
- META-INF
- services
- javax.annotation.processing.Processor
- services
- com
打包进MyProcessor.jar中的javax.annotation.processing.Processor的内容是,注解处理器的合法的全名列表,每一个元素换行分割:
- 1
- 2
- 3
- 1
- 2
- 3
把MyProcessor.jar放到你的builpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器。
6.1.3 com.google.auto.service:auto-service
Google提供了一个插件来帮助我们更方便的注册注解处理器,你只需要导入对应的依赖包,在自定义的Processor类上方添加@AutoService(Processor.class)即可。如下:
- 导入依赖包
- 1
- 1
- 添加声明
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
6.1.4 com.neenbedankt.android-apt
该插件用于处理注解处理器,用法如下:
- 添加plugin声明:
- 1
- 1
- 添加classpath声明:
- 1
- 1
- 添加处理声明:
- 1
- 1
注: 自Android Gradle 插件 2.2 版本开始,官方提供了名为 annotationProcessor 的功能来完全代替 android-apt。
若工程使用gradle版本>=2.2,则无需引用com.neenbedankt.android-apt相关,将原先的 apt project(':xxx-compiler')
改为 annotationProcessor project(':xxx-compiler')
即可。
七、小结
- JavaPoet为square出品,并且诸如butterknife、Dagger等著名开源框架也使用该库,可见其质量保障性和稳定性。
- JavaPoet提供的api清晰明了,使用起来简单方便,功能方面也很齐全,发布了很久目前也已迭代了很多个版本,趋于稳定阶段。
- 运用JavaPoet预生成代码的方式,在省去我们频繁书写重复代码的同时,也避免了使用运行时反射造成的效率问题。
八、参考资料
- JavaPoet on Github
- ANNOTATION PROCESSING 101
- Java注解处理器
- JavaPoet源码初探
- JavaPoet动态生成代码
- Android JavaPoet 动态生成Java源码(1)
- JavaPoet 动态生成Java源码(1)---Android
- JavaPoet生成.java源代码
- javapoet:源文件自动生成框架
- JavaPoet
- JavaPoet
- JavaPoet
- 动态生成JAVA代码
- 动态生成代码程序
- 动态生成JAVA代码
- 动态生成JAVA代码
- 动态生成JAVA代码
- 动态生成代码例子
- Lua动态生成代码
- 代码动态生成控件
- android 可以用来写代码的代码(JavaPoet)
- javapoet——会写代码的“诗人”
- Unity3d 的 EditorUtility类的简单说明
- ubuntu 卸载软件
- 找到自己
- 关于正则表达式
- Git安装及配置
- JavaPoet动态生成代码
- 网易2018校招内推编程题
- .net core 2.0学习笔记(三):度量.net framework 迁移到.net core的工作量
- leetcode 15 threeSum
- ORACLE in与exists语句的区别
- 解决设置border改变元素宽度问题
- 全栈开发:前后端分离配置篇(vue+webpack+mock+nginx+laravel)
- iOS项目使用cocoPods 管理百度地图SDK
- android小圆点