目录
简介
JavaPoet是一款可以自动生成Java文件的第三方依赖。JavaPoet是JavaWriter的继承者。新项目应该首选JavaPoet,因为它有一个更强大的代码模型:它能理解类型并能自动管理导入。JavaPoet也更适合于组合:与其将一个.java文件的内容自上而下地一次性流传,不如将一个文件组装成一棵声明树。
引入
这里介绍两种项目中的引入方式
在maven项目中的引入方式:在pom.xml中添加
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
在Android项目中的引入方式:在需要使用Javapoet的gradle中添加
compile 'com.squareup:javapoet:1.13.0'
其它Java语言的项目中应该也是在相关配置文件中引入就行
简单范例
我们可以使用javapoet生成以下代码
package com.example.helloJavaPoet;
import java.lang.String;
import java.lang.System;
public final class HelloJavaPoet {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
使用以下代码可以生成上述代码
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class HelloWorld {
public static void main(String[] args) {
// 给类添加一个方法
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 代表一个类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloJavaPoet")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
// JavaFile 代表 Java 文件
JavaFile javaFile = JavaFile.builder("com.example.helloJavaPoet", helloWorld)
.build();
File outFile = new File("com/example/helloJavaPoet.java");
if(!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
if (!outFile.exists()) {
try {
outFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileWriter writer;
try {
writer = new FileWriter(outFile.getAbsolutePath());
writer.write("");//清空原文件内容
writer.write(javaFile.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
因为不是在安卓项目中实现的,所以生成文件上复杂了点,用了File和FileWriter
JavaPoet的常用类
JavaFile————用于生成的Java文件的类
TypeSpec————用于生成类、接口、枚举对象的类
MethodSpec————用于生成方法的类
ParameterSpec————用于生成参数的类
AnnotationSpec————用于生成注解的类
FieldSpec————用于生成变量的类
ClassName————通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName————通过MainClass和IncludeClass生成包含泛型的Class
占位符
$L for Literals
$L指的是没有转义的字面值,可以是字符串、基本数据类型、类型声明、注解甚至其他代码块的占位符,就像Formatter的%s。
上面的代码中,将$S换成$L就会不会转换成字符串(就是不加引号),直接以字面量代替
$S for Strings
$S指的是可以带着引号和转义的字符串的占位符。
(这里和上面的转义还没弄的很懂,官方文档中也只是给了引号的例子)
$T for Types
$T指的是数组类型的占位符,它不仅支持内置类型,还包括自动生成import语句。
例如:
用这个
MethodSpec today = MethodSpec.methodBuilder("today")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
生成这个
package com.example.helloworld;
import java.util.Date;
public final class HelloWorld {
Date today() {
return new Date();
}
}
$N for Names
指的是一个名称的占位符,例如调用的方法名称,变量名称(可以是自动生成的方法和变量)。
例如:
用这个代码
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
生成
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
参数
相对参数
CodeBlock.builder().add("I ate $L $L", 3, "tacos").build()
生成字符串
I ate 3 tacos
位置参数
CodeBlock.builder().add("I ate $2L $1L", "tacos", 3).build()
生成字符串同上
名字参数
使用语法$argumentName:X,其中X是上述占位符中的字母,用包含格式字符串中所有参数键的映射调用CodeBlock.addNamed()。参数名使用a-z、A-Z、0-9和_中的字符,并且必须以小写字符开始。
Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
生成字符串同上
使用详解
抽象方法
使用Modifiers.ABSTRACT可以用来获得一个没有任何主体的方法。当这只有在类是抽象类或接口时才合法。
MethodSpec flux = MethodSpec.methodBuilder("flux")
.addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(flux)
.build();
生成代码
public abstract class HelloWorld {
protected abstract void flux();
}
生成构造方法
普通方法用的是 MethodSpec.methodBuilder(方法名) 生成,构造方法是用MethodSpec.constructorBuilder()生成
MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "greeting")
.addStatement("this.$N = $N", "greeting", "greeting")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(flux)
.build();
生成代码:
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
方法参数
用 ParameterSpec.builder() 或 ==MethodSpec的addParameter() ==在方法和构造函数上声明参数。
ParameterSpec android = ParameterSpec.builder(String.class, "android")
.addModifiers(Modifier.FINAL)
.build();
MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(android)
.addParameter(String.class, "robot", Modifier.FINAL)
.build();
void welcomeOverlords(final String android, final String robot) {
}
类变量
变量的生成和方法参数也是一样的。用 FieldSpec.builder() 或 TypeSpec的addField()。这里不做解释。
接口
JavaPoet生成接口,接口方法必须始终是PUBLIC ABSTRACT,接口字段必须始终是PUBLIC STATIC FINAL。这些修饰语在定义接口时是必要的
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "change")
.build())
.addMethod(MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.build();
但这些修饰语在代码生成时被省略了。这些都是默认的,所以我们不需要为了javac而包含它们!
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
枚举
使用enumBuilder来创建枚举类型,并用 EnumConstant() 添加值。
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
生成:
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
此外,javapoet支持花式枚举,其中枚举值覆盖方法或调用超类构造函数。这里有一个全面的例子
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
.addMethod(MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "avalanche!")
.returns(String.class)
.build())
.build())
.addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
.build())
.addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
.build())
.addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "handsign")
.addStatement("this.$N = $N", "handsign", "handsign")
.build())
.build();
生成代码:
public enum Roshambo {
ROCK("fist") {
@Override
public String toString() {
return "avalanche!";
}
},
SCISSORS("peace"),
PAPER("flat");
private final String handsign;
Roshambo(String handsign) {
this.handsign = handsign;
}
}
匿名内部类
在枚举的代码中,我们使用了TypeSpec.anonymousInnerClass()。匿名内部类也可以在代码块中使用。它们是可以用$L引用的值
例子:
TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
.addMethod(MethodSpec.methodBuilder("compare")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "a")
.addParameter(String.class, "b")
.returns(int.class)
.addStatement("return $N.length() - $N.length()", "a", "b")
.build())
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addMethod(MethodSpec.methodBuilder("sortByLength")
.addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
.addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
.build())
.build();
生成的代码:
void sortByLength(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
}
定义匿名内类的一个特别棘手的部分是给超类构造函数的参数。在上面的代码中,我们传递的是空字符串,没有参数:TypeSpec.anonymousClassBuilder("")。要传递不同的参数,请使用JavaPoet的代码块语法,用逗号来分隔参数。
注解
用 addAnnotation() 生成注解
例如:
MethodSpec toString = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.returns(String.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S", "Hoverboard")
.build();
生成代码
@Override
public String toString() {
return "Hoverboard";
}
使用AnnotationSpec.builder()来设置注释的属性。
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(Headers.class)
.addMember("accept", "$S", "application/json; charset=utf-8")
.addMember("userAgent", "$S", "Square Cash")
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
生成代码
@Headers(
accept = "application/json; charset=utf-8",
userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
此外,注解的值可以是注解。使用$L进行嵌入式注解。
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(AnnotationSpec.builder(HeaderList.class)
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "Accept")
.addMember("value", "$S", "application/json; charset=utf-8")
.build())
.addMember("value", "$L", AnnotationSpec.builder(Header.class)
.addMember("name", "$S", "User-Agent")
.addMember("value", "$S", "Square Cash")
.build())
.build())
.addParameter(LogRecord.class, "logRecord")
.returns(LogReceipt.class)
.build();
@HeaderList({
@Header(name = "Accept", value = "application/json; charset=utf-8"),
@Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);
注意,你可以用同一个属性名称多次调用addMember()来为该属性填充一个值的列表。
Javadoc
字段、方法和类型可以用Javadoc进行记录
MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
.addJavadoc("Hides {@code message} from the caller's history. Other\n"
+ "participants in the conversation will continue to see the\n"
+ "message in their own history unless they also delete it.\n")
.addJavadoc("\n")
.addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
+ "conversation for all participants.\n", Conversation.class)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addParameter(Message.class, "message")
.build();
/**
* Hides {@code message} from the caller's history. Other
* participants in the conversation will continue to see the
* message in their own history unless they also delete it.
*
* <p>Use {@link #delete(Conversation)} to delete the entire
* conversation for all participants.
*/
void dismiss(Message message);