JavaPoet 是一个用于生成 .java 源代码文件的 Java API。截止博客发表为止,整个项目核心源码就17个类文件,github上却有 9.9k stars,可谓短小精悍。
javapoet 实现了自动导包和语句分号添加,代码流程控制,代码格式化等功能,很方便开发者生成一些模板代码
github 地址: https://github.com/square/javapoet
pom 依赖:
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
我们知道,一个 java 类源码文件由类、字段、构造方法、方法、参数、注解等元素组成,JavaPoet 为这些基本组成元素分别定义了相应的类,分别用来管理、生成相应元素相关的代码。
JavaPoet 所有类:
class | 说明 |
---|---|
AnnotationSpec | 用于生成注解 |
ArrayTypeName | 定义数组类型需要用这个类 |
ClassName | 对应一个类、接口或 enum 的名字,由 package 名字和类名字两部分组成 |
CodeBlock | 用于生成代码块 |
CodeWriter | 代码生成类,将 JavaFile 转换为人类可读和javac使用的字符串。 |
FieldSpec | 用于生成字段 |
JavaFile | 用于生成.java文件 |
LineWrapper | 行相关 |
MethodSpec | 用于生成方法或构造方法 |
NameAllocator | 指定Java标识符名称以避免冲突、关键字和无效字符 |
ParameterizedTypeName | 泛型中的参数化类型 |
ParameterSpec | 用于生成参数 |
TypeName | 类名字 |
TypeSpec | 类、接口或Enum |
TypeVariableName | 泛型中的类型变量 |
Util | 工具类 |
WildcardTypeName | 泛型中的通配符? |
JavaPoet支持语句中变量替换,几个常用替换符号的功能介绍如下:
替换符号 | 说明 |
---|---|
$T | 参数是Class对象,替换为Class的名字,如果需要,同时在文件头添加相应的 import 语句 |
$L | 替换为变量值的字面量值,功能相当于字符串的format()方法 |
$S | 也是替换为变量值的字面量值,但是字面量值为被字符串双引号包裹起来 |
$N | 参数是ParameterSpec、TypeSpec、MethodSpec等,替换为这些变量的name值 |
变量、参数、返回值的类型即可以用 java 的 class 来表达,javapoet 也定义了相应的类型表示系统,对应关系如下:
类别 | 生成的类型举例 | javapoet 表达方式 |
---|---|---|
基本类型 | int | TypeName.INT |
基本类型包装类型 | Integer | Integer.class |
数组 | int[] | ArrayTypeName.of(int.class) |
对象类型 | String | String.class |
参数化类型 | List<String> | ParameterizedTypeName.get(List.class, String.class) |
类型变量 | T | TypeVariableName.get(“T”) |
通配符类型 | ? extends String | WildcardTypeName.subtypeOf(String.class) |
使用 FieldSpec 可以设置字段类型和名字,还可以设置初始值,和修饰符 public/private/protect
注:javapoet 中 Modifier修饰符都是 javax.lang.model.element.Modifier,而不是 java.lang.reflect.Modifier
// String 类型
FieldSpec name = FieldSpec.builder(String.class, "name")
.addModifiers(Modifier.PUBLIC).build();
// int 类型
FieldSpec age = FieldSpec.builder(TypeName.INT, "age")
.addModifiers(Modifier.PRIVATE)
.initializer("12").build();
// String 数组类型
FieldSpec skills = FieldSpec.builder(ArrayTypeName.of(String.class), "skills")
.addModifiers(Modifier.PUBLIC).build();
能够生成下面代码
public String name;
private int age = 12;
public String[] skills;
方法使用 MethodSpec 来构建方法,常用的可以添加修饰符public,返回值,参数,语句等等
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();
生成 java 代码 如下
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
除了以上,还支持添加注解, 代码块,异常,javadoc,注释等等
CodeBlock codeBlock = CodeBlock.builder()
.addStatement("int i = 0")
.addStatement("$T.out.println(i)", System.class)
.build();
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addAnnotation(Benchmark.class)
.addException(Exception.class)
.addJavadoc("this is javapoet demo")
.addCode(codeBlock)
.addComment("这是注释")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.addComment("这是另一段注释")
.build();
生成方法
/**
* this is javapoet demo
*/
@Benchmark
public static void main(String[] args) throws Exception {
int i = 0;
System.out.println(i);
// 这是注释
System.out.println("Hello, JavaPoet!");
// 这是另一段注释
}
此外,还支持构造方法等等
MethodSpec m = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.build();
代码块使用 CodeBlock 来生成,可以使用 beginControlFlow endControlFlow 来控制流程,特别是减少了循环和 if 的大括号手动添加
CodeBlock codeBlock = CodeBlock.builder()
.addStatement("int i = 0")
.beginControlFlow("for (;i < 10; i++)")
.addStatement("$T.out.println(i)", System.class)
.endControlFlow()
.beginControlFlow("if (i > 9)")
.beginControlFlow("if (i < 15)")
.addStatement("i = 200")
.nextControlFlow("else ")
.addStatement("i = 300")
.endControlFlow()
.addStatement("i++")
.endControlFlow()
.build();
生成代码
int i = 0;
for (;i < 10; i++) {
System.out.println(i);
}
if (i > 9) {
if (i < 15) {
i = 200;
} else {
i = 300;
}
i++;
}
异常代码块也可以用 nextControlFlow 控制
CodeBlock cb = CodeBlock.builder()
.beginControlFlow("try")
.addStatement("throw new Exception($S)", "Failed")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("throw new $T(e)", RuntimeException.class)
.endControlFlow()
.build();
生成代码
try {
throw new Exception("Failed");
} catch (Exception e) {
throw new RuntimeException(e);
}
类使用 TypeSpec 来生成,可以添加字段,方法,构造方法,静态代码块,接口,父类等等方法
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addField(name)
.addField(age)
.addField(skills)
.addAnnotation(Benchmark.class)
.addSuperinterface(Serializable.class)
.superclass(AbstractList.class)
.addMethod(main)
.addMethod(m)
.build();
不仅可以生成普通类,还可以生成枚举,接口,匿名类等
TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("ROCK")
.addEnumConstant("SCISSORS")
.addEnumConstant("PAPER")
.build();
生成代码如下
public enum Roshambo {
ROCK,
SCISSORS,
PAPER
}
最终 java 文件可以通过 JavaFile 生成
JavaFile javaFile = JavaFile.builder("com.demo.helloworld", helloWorld)
.skipJavaLangImports(true)
.build();
// 在控制台打印代码
javaFile.writeTo(System.out);
// 将代码存储为文件
javaFile.writeTo(new File("D:\\java"));
将一个数据库的所有表转化为实体类。使用 spring-boot-starter-jdbc 连接数据库,guava库实现表和字段的驼峰转化,实体类带有lombok的@Data注解
@SpringBootTest
public class EnityGenerate {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void main() {
String databaseName = "test";
String packageName = "com.aabond.demo.enity";
List<Map<String, Object>> list = jdbcTemplate.queryForList("select\n" +
"\t\ttable_name,\n" +
" column_name,\n" +
" data_type\n" +
"from information_schema.columns\n" +
"where table_schema = '" + databaseName + "'");
Map<Object, List<Map<String, Object>>> name = list.stream().collect(Collectors.groupingBy(m -> m.get("table_name")));
name.forEach((tableName, fields) -> {
Map<String, Type> tmp = new HashMap<>();
for (Map<String, Object> field : fields) {
String columnName = (String) field.get("column_name");
String dataType = (String) field.get("data_type");
try {
tmp.put(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, columnName), mysqlTypeToJavaType(dataType));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
String className = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, (String) tableName);
TypeSpec generate = generate(className, tmp);
JavaFile javaFile = JavaFile.builder(packageName, generate)
.indent(" ")
.skipJavaLangImports(true)
.build();
try {
javaFile.writeTo(Paths.get("src/main/java"));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
private Type mysqlTypeToJavaType(String mysqlType) throws Exception {
switch (mysqlType) {
case "varchar":
case "blob":
case "text":
case "tinyblob":
return String.class;
case "int":
return Integer.class;
case "bigint":
return Long.class;
case "timestamp":
return LocalDateTime.class;
case "decimal":
return BigDecimal.class;
default:
throw new Exception("unknow mysql type: " + mysqlType);
}
}
private TypeSpec generate(String table, Map<String, Type> field) {
List<FieldSpec> collect = field.entrySet().stream().map((a) -> FieldSpec.builder(a.getValue(), a.getKey(), Modifier.PRIVATE).build()).collect(Collectors.toList());
TypeSpec typeSpec = TypeSpec.classBuilder(table)
.addFields(collect)
.addAnnotation(Data.class).build();
return typeSpec;
}
}
将service层的所有接口的所有方法生成测试类
@Test
public void serviceTestGenerate() {
String packagePath = "src/main/java/com/aabond/demo/service";
String packageName = "com.aabond.demo.service";
String testPath = "src/test/java";
File path = Paths.get(packagePath).toFile();
File[] files = path.listFiles(file -> file.getName().endsWith("Service.java"));
System.out.println(Arrays.toString(files));
Arrays.stream(Objects.requireNonNull(files)).map(f -> {
try {
return Class.forName(packageName + "." + f.getName().replace(".java", ""));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}).forEach(c -> {
String name = c.getSimpleName();
Method[] methods = c.getMethods();
List<String> strings = Arrays.stream(methods).map(Method::getName).collect(Collectors.toList());
TypeSpec generate = generate(name, strings);
JavaFile javaFile = JavaFile.builder(packageName, generate)
.indent(" ")
.skipJavaLangImports(true)
.build();
try {
// javaFile.writeTo(System.out);
javaFile.writeTo(Paths.get(testPath));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
private TypeSpec generate(String className, List<String> methodNames) {
List<MethodSpec> collect = methodNames.stream().map((a) -> MethodSpec.methodBuilder(a)
.addModifiers(Modifier.PUBLIC).addAnnotation(Test.class).build())
.collect(Collectors.toList());
TypeSpec typeSpec = TypeSpec.classBuilder(className)
.addMethods(collect)
.build();
return typeSpec;
}