
JavaPoet 代码生成代码
JavaPoet 是用来动态生成.java源文件的Java API
在执行诸如注释处理或与原数据文件(例如:数据库模式,协议格式)交互等操作时,源文件生成非常有用。通过生成代码,您无需编写样板,同时还为元数据保留了单一的真实来源。
概括来说就是用Java代码生成Java代码
在红猫三代项目中,后端代码中广泛应用JavaPoet插件来生成前端页面用户的相关设计,包括逻辑流,实体等等,这是项目中的核心功能。
由于项目中广泛应用Javapoet 贴不出实例代码
所以实例代码转自csdnJavaPoet使用详解
代码中可参照EntityClassBuilder
、ActionBuilder
例子
这是一个普通的HelloWorld类:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
这时使用JavaPoet生成它的代码:
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);
从上面代码可以简单看出:
MethodSpec
构建方法TypeSpec
构建类
为了声明main
方法,首先创建一个MethodSpec main
,并配置了修饰符、返回类型、参数和代码语句。我们将main
方法添加到HelloWorld
类中,然后将其添加到HelloWorld.java
文件中
在这种情况下,我们将文件写入System.out
,但我们也可以将其作为字符串获取(JavaFile.toString())或将其写入文件系统(JavaFile.wirteTo())
使用方法
1.添加依赖
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
2.使用方法
生成Java文件,需要的基本元素包括:
- 创建字段(属性)
- 创建方法
- 创建类(匿名类)、接口或枚举
- 输出文件
FieldSpec(字段)
可以使用构造器或使用方便的辅助方法来创建字段,FieldSpec就是来创建字段的类
FieldSpec.builder(String.class, "lkyujun").build()
以上代码等价于 String lkyujun;
如果添加修饰符可以使用addModifiers
FieldSpec.builder(String.class, "lkyujun")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.build()
以上代码等价于private final String lkyujun;
其中Modifier
包括我们用到的所有修饰符
public enum Modifier {
// See JLS sections 8.1.1, 8.3.1, 8.4.3, 8.8.3, and 9.1.1.
// java.lang.reflect.Modifier includes INTERFACE, but that's a VMism.
/** The modifier {@code public} */ PUBLIC,
/** The modifier {@code protected} */ PROTECTED,
/** The modifier {@code private} */ PRIVATE,
/** The modifier {@code abstract} */ ABSTRACT,
/**
* The modifier {@code default}
* @since 1.8
*/
DEFAULT,
/** The modifier {@code static} */ STATIC,
/** The modifier {@code final} */ FINAL,
/** The modifier {@code transient} */ TRANSIENT,
/** The modifier {@code volatile} */ VOLATILE,
/** The modifier {@code synchronized} */ SYNCHRONIZED,
/** The modifier {@code native} */ NATIVE,
/** The modifier {@code strictfp} */ STRICTFP;
/**
* Returns this modifier's name in lowercase.
*/
public String toString() {
return name().toLowerCase(java.util.Locale.US);
}
}
当字段具有Javadoc、注释或者字段初始值时,扩展形式是必需的。字段初始值设置项使用-like语法,如果需要初始化参数,使用initializer
FieldSpec.builder(String.class, "lkyujun")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S", "henry")
.build()
以上代码等价于private final String lkyujun = "henry";
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
.initializer("$S + $L", "Lollipop v.", 5.0d)
.build();
以上代码等价于private final String android = "Lollipop v." + 5.0;
MethodSpec(方法)
MethodSpec
用来创建方法,一个最简单的方法包括方法名、返回类型。
returns
用来定义方法的返回类型
MethodSpec.methodBuilder("test")
.returns(void.class)
.build()
以上代码等价于void test(){}
添加修饰符同上,如果要添加形参,需要使用addParameter
MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "str")
.returns(void.class)
.build()
以上代码等价于public void test(String str){}
给方法添加语句,需要使用addStatement
MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "str")
.addStatement("System.out.println(str)")
.returns(void.class)
.build()
/*public void test(String str) {
System.out.println(str);
}*/
以上方法等价于
public void test(String str){
System.out.println(str);
}
构建抽象、接口
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();
}
TypeSpec(类,接口,枚举)
TypeSpec用来创建类,接口,或者枚举
// class Test {}
TypeSpec.classBuilder("Test").build();
//interface Test {}
TypeSpec.interfaceBuilder("Test").build();
/*
enum Test {
ONE
}*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();
泛型
FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
以上代码等价private T mT;
TypeVariableName mTypeVariable = TypeVariableName.get("T");
ParameterizedTypeName mListTypeName = ParameterizedTypeName.get(ClassName.get(List.class), mTypeVariable);
FieldSpec fieldSpec = FieldSpec.builder(mListTypeName, "mList", Modifier.PRIVATE).build();
以上方法等价private List<T> mList;
代码块
TypeSpec.classBuilder("Test")
.addStaticBlock(CodeBlock.builder().build())
.addInitializerBlock(CodeBlock.builder().build())
.build();
以上代码等价
/*
class Test {
static {
}
{
}
}*/
addCode和ControlFlow(代码和控制流)
JavaPoet 使用字符串作为代码块
MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
以上代码等价于
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
可以看出来使用addCode
方法可以一股脑的添加所有的代码,但是需要我们自己换行和缩进
所以说这种方式非常LOW,JavaPoet
提供了API以使其更容易的向方法体中添加方法。
使用addStatement
可以帮我们添加分号和换行,使用beginControlFlow
和endControlFlow
组合可以帮我们轻松实现控制流代码
使用JavaPoet方式实现以上循环:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.build();
这种写法虽然已经比刚才第一种高大上一点了,但是能看出来所有的参数都是写死的,假设我们不只是将0加到10,而是希望操作和范围可配置。
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 1")
.beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
.addStatement("result = result " + op + " i")
.endControlFlow()
.addStatement("return result")
.build();
}
以上代码等价于
int multiply10to20() {
int result = 1;
for (int i = 10; i < 20; i++) {
result = result * i;
}
return result;
}
由于JavaPoet生成源代码而不是字节码,所以要检查语法、代码没有错误。
某些语句可能有无线控制可能性,所以推荐以下写法
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("long now = $T.currentTimeMillis()", System.class)
.beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
.nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time stood still!")
.nextControlFlow("else")
.addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
.endControlFlow()
.build();
以上代码等价于
void main() {
long now = System.currentTimeMillis();
if (System.currentTimeMillis() < now) {
System.out.println("Time travelling, woo hoo!");
} else if (System.currentTimeMillis() == now) {
System.out.println("Time stood still!");
} else {
System.out.println("Ok, time still moving forward");
}
}
使用JavaPoet捕获异常
MethodSpec main = MethodSpec.methodBuilder("main")
.beginControlFlow("try")
.addStatement("throw new Exception($S)", "Failed")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("throw new $T(e)", RuntimeException.class)
.endControlFlow()
.build();
以上代码等价于
void main() {
try {
throw new Exception("Failed");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
针对其他使用场景贴一些例子
// do... while
.beginControlFlow("do")
.endControlFlow("while (true)")
// if... else if... else...
.beginControlFlow("if (true)")
.nextControlFlow("else if (false)")
.nextControlFlow("else")
.endControlFlow()
// try... catch... finally
.beginControlFlow("try")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("e.printStackTrace()")
.nextControlFlow("finally")
.endControlFlow()
以上代码中的 S L等都是占位符,接下来介绍用法
占位符
上述某些例子的代码中,我们使用到的都是固定的字符串,很不灵活,JavaPoet为我们提供了很多中占位符来满足我们的要求。
$S(String)
当代码中包含字符串的时候,可以使用$S
表示
private static MethodSpec whatsMyName(String name) {
return MethodSpec.methodBuilder(name)
.returns(String.class)
.addStatement("return $S", name)
.build();
}
$L(Literal)
L 是字面量替换,它与`S`相似,但是它并不需要转义,也就是不包含字符串的引号。
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
}
$T(Type)
上面的例子为了简单,使用到的都是基本类型,为的是不需要导包。实际中,我们需要大量使用对象,需要将类所在的包import到当前代码中,没有导入则会报错,所以使用$T
来替换
同时在三代项目中,有时候需要引入其他服务中的类,
这时候就需要使用ClassName.get();
通过其他服务中的包名+类名获取对象的包路径并引用
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);
$T
会自动导包 以上代码等价于
package com.example.helloworld;
import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(createNimbus(2000));
result.add(createNimbus("2001"));
result.add(createNimbus(THUNDERBOLT));
sort(result);
return result.isEmpty() ? emptyList() : result;
}
}
$N(Name)
$N
是名称替换,它和$S
的区别在于$S
会加上双引号
addStatement("$N()",methodSpec)就更加方便了。
MethodSpec methodSpec = MethodSpec.methodBuilder("get" + name)
.returns(String.class)
.addStatement("return $S", name)
.build();
MethodSpec.methodBuilder("getValue")
.returns(String.class)
.addStatement("return $N()", methodSpec)
.build();
代码块格式字符串
代码块可以通过几种方式为其占位符指定值。代码块上的每个操作只能使用一种样式。
相对参数
将格式字符串的每个占位符的参数值传递给代码中的占位符
CodeBlock.builder().add("I ate $L $L", 3, "tacos")
等价于"i ate 3 tacos"
位置参数
在格式字符串中的占位符之前放置一个整数索引(从1开始)以指定要使用的参数
CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
等价于"i ate 3 tocos"
命名参数
使用语法 并调用包含格式字符串中所有的参数键的映射。参数名称使用参数中的字符,并且以小写开头
Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
构造方法
MethodSpec methodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("Test")
.addMethod(methodSpec)
.build();
以上代码等价于
/*
class Test {
public Test() {
}
}*/
生成有参构造
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 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) {
}
类的继承和接口实现
TypeSpec.classBuilder("Test")
.superclass(String.class)
.addSuperinterface(Serializable.class)
.build();
以上等价于,superclass
继承,addSuperinterface
实现
class Test extends String implements Serializable {}
接口
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();
以上代码等价
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
枚举
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
以上代码生成
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
生成复杂枚举
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;
}
}
匿名内部类
使用anonymousClassBuilder
构建匿名内部类
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("$T param",String.class) // <- 也可添加参数
.superclass(Runnable.class)
.addMethod(MethodSpec.methodBuilder("run")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(TypeName.VOID)
.build())
.build();
代码等价于
new java.lang.Runnable(String param) {
@java.lang.Override
public void run() {
}
}
Annotations(注解)
简单的注释很容易,直接使用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;
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);
同时还可以嵌入式注释
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);
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);
在Javadoc中引用类型以获取自动导入时使用