JavaPoet - 优雅地生成代码
一、项目简介
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。
项目主页及源码:https://github.com/square/javapoet
二、项目总览
该项目代码量相对较小,只有一个package(com.squareup.javapoet),所有类均位于该package下。
2.1 大体结构图
2.2 关键类说明
class | 说明 | |
---|---|---|
JavaFile | A Java file containing a single top level class | 用于构造输出包含一个顶级类的Java文件 |
TypeSpec | A generated class, interface, or enum declaration | 生成类,接口,或者枚举 |
MethodSpec | A generated constructor or method declaration | 生成构造函数或方法 |
FieldSpec | A generated field declaration | 生成成员变量或字段 |
ParameterSpec | A generated parameter declaration | 用来创建参数 |
AnnotationSpec | A generated annotation on a declaration | 用来创建注解 |
在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
compile 'com.squareup:javapoet:1.9.0'
例子如下:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
上方的代码是通过下方代码调用JavaPoet的API生成的:
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
四、源码浅析
下面来看看调用了JavaFile的writeTo后实际做了些什么。
public void writeTo(Appendable out) throws IOException {
// First pass: emit the entire class, just to collect the types we'll need to import.
CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports);
emit(importsCollector);
Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
// Second pass: write the code, taking advantage of the imports.
CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports);
emit(codeWriter);
}
通过源码可以知道,writeTo分为两部分:第一步收集import,记录下来后第二步才跟随内容一起写到CodeWriter。
另外我们可以看到源码中的emit方法,通过查看其它源码发现,在JavaPoet中,所有java文件的抽象元素都定义了emit方法,如TypeSepc,ParameterSepc等,emit方法传入CodeWriter对象输出字符串。上层元素调用下层元素的emit方法,如JavaFile的emit方法调用TypeSpec的emit方法,从而实现整个java文件字符串的生成。
下面我们以MethodSpec为例,查看其emit代码:
void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
throws IOException {
codeWriter.emitJavadoc(javadoc);
codeWriter.emitAnno