JavaPoet源码初探
2016-06-06 21:42
483 查看
JavaPoet源码初探
JavaPoet是用于代码生成的开源编程框架,利用JavaPoet可以方便生成.java文件。代码生成技术相当于元编程,可用于编译期根据注解等元数据动态生成java类。广泛使用的dagger,butterknife框架就是利用JavaPoet对注入注解生成所需类。相关地址:github地址JavaPoet使用
JavaPoet使用比较简便,以简单的helloworld代码为例,如下:package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }
使用JavaPoet生成helloword代码如下所示:
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是对.java文件的抽象,TypeSpec是类的抽象,MethodSpec是方法的抽象。JavaPoet提供T,S等标识符替代字符串,T代表class,S代表string。
Java文件模型
JavaPoet中对.java文件及其组成进行抽象, 其java文件模型如下:可以看出,JavaPoet定义java文件分为注释、import代码、类型定义三部分,其中类型(TypeSepc)和方法(MethodSepc)为框架中核心类,这对应java文件的元素组成逻辑也不难理解。
在JavaPoet中,所有java文件的抽象元素都定义了emit方法,如TypeSepc,ParameterSepc等,emit方法传入CodeWriter对象输出字符串。上层元素调用下层元素的emit方法,如JavaFile的emit方法调用TypeSpec的emit方法,从而实现整个java文件字符串的生成。
所有的java文件抽象元素的emit方法最终都会调用CodeWriter的emit方法,CodeWriter是对字符串输出的抽象。
TypeSpec/MethodSpec
TypeSpec是类型元素的抽象,通过Kind枚举定义class、interface、enum、annotation四种类型。TypeSpec使用Builder根据传入不同的Kind常量创建对象。TypeSpec中包含FiledSpec和MethodSpec的列表对象,在emit中遍历列表调用其emit方法。MethodSpec代表方法的抽象。MethodSpec包含ParameterSepc列表对象和CodeBlock对象,CodeBlock是代码块的抽象类。CodeBlock中提供了beginControlFlow,nextControlFlow,endControlFlow等方法便于生成控制代码,同时,CodeBlock提供了L,S,T,N占位符替代字符串。
MethodSpec的emit方法代码如下:
void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers) throws IOException { codeWriter.emitJavadoc(javadoc); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, implicitModifiers); if (!typeVariables.isEmpty()) { codeWriter.emitTypeVariables(typeVariables); codeWriter.emit(" "); } if (isConstructor()) { codeWriter.emit("$L(", enclosingName); } else { codeWriter.emit("$T $L(", returnType, name); } boolean firstParameter = true; for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) { ParameterSpec parameter = i.next(); if (!firstParameter) codeWriter.emit(", "); parameter.emit(codeWriter, !i.hasNext() && varargs); firstParameter = false; } codeWriter.emit(")"); if (defaultValue != null && !defaultValue.isEmpty()) { codeWriter.emit(" default "); codeWriter.emit(defaultValue); } if (!exceptions.isEmpty()) { codeWriter.emit(" throws"); boolean firstException = true; for (TypeName exception : exceptions) { if (!firstException) codeWriter.emit(","); codeWriter.emit(" $T", exception); firstException = false; } } if (hasModifier(Modifier.ABSTRACT)) { codeWriter.emit(";\n"); } else if (hasModifier(Modifier.NATIVE)) { codeWriter.emit(code); codeWriter.emit(";\n"); } else { codeWriter.emit(" {\n"); codeWriter.indent(); codeWriter.emit(code); codeWriter.unindent(); codeWriter.emit("}\n"); } }
以上可以看出,MethodSepc通过调用codeWriter的emit方法依次输出javadoc,annotation,parameter,codeblock等方法组成元素。
自动插入import代码
JavaPoet框架另一个设计巧妙的地方在于能自动识别需要import的类型,如下所示:ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); ClassName list = ClassName.get("java.util", "List"); ClassName arrayList = ClassName.get("java.util", "ArrayList"); TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard); MethodSpec beyond = MethodSpec.methodBuilder("beyond") .returns(listOfHoverboards) .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList) .addStatement("result.add(new $T())", hoverboard) .addStatement("return result") .build(); // 生成代码 package com.example.helloworld; import com.mattel.Hoverboard; import java.util.ArrayList; import java.util.List; public final class HelloWorld { List<Hoverboard> beyond() { List<Hoverboard> result = new ArrayList<>(); result.add(new Hoverboard()); return result; } }
JavaPoet能自动识别需要import的类型并生成import代码,如上代码中的Hoverboard和ArrayList类。其中ClassName代表类名的抽象。
在实现上,JavaPoet两次生成java文件字符串,在第一次中生成字符串用于收集import的类型信息,第二次才输出字符串到文件中。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); }
上面代码可以看出,第一次输出字符串到NULL_APPENDABLE对象中,只作收集import类型。CodeWriter中包含importedTypes数据列表,记录需要import的类型。
总结
利用JavaPoet框架在编译期动态生成java文件,相当于提供了一种java的元编程方式,而在一些对性能要求较高的环境,动态生成java代码技术是替代运行时反射技术的一个很好的选项。相关文章推荐
- leetcode-java-168. Excel Sheet Column Title
- 设计模式——简单工厂模式
- java连接Sql Server2008 方法二
- RxJava Map操作详解
- Spring下载 ---- Spring_0
- Ubuntu下安装JDK和Eclipse遇到的问题记录
- java设计模式之装饰者模式学习
- java那些事情
- [置顶] Java中的逆变与协变
- java连接Sql server 2008步骤
- 内部类与匿名内部类的概念与使用————Java基础知识
- spark之DataFrame 通过反射创建
- java实现多线程
- eclipse将一段代码抽取为方法Extract Method
- Spring web mvc DispatcherServlet (1)---配置
- 命令行式下java package的编译与运行注意事项
- javac找不到或无法加载主类 com.sun.tools.javac.Main
- Java 虚拟机总结 - JVM 内存区域
- SpringMVC学习笔记:Ajax与Controller的参数交互
- eclipse 快捷键