JavaPoet 动态生成Java源码(1)---Android

来源:互联网 发布:服务器域名是后缀吗 编辑:程序博客网 时间:2024/06/05 20:11

简介

官方描述:JavaPoet is a Java API for generating .java source files.(JavaPoet是一个Java Api接口生成.java源码文件的工程)
JavaPoet源码:https://github.com/square/javapoet

我在此主要承担一个翻译解释的角色,分析给大家;为后一篇文章的发表,奠定一定的基础使用语法。

引入库

compile 'com.squareup:javapoet:1.7.0'

or Maven:

<dependency>  <groupId>com.squareup</groupId>  <artifactId>javapoet</artifactId>  <version>1.7.0</version></dependency>

or Eclipse:

https://search.maven.org/remote_content?g=com.squareup&a=javapoet&v=LATEST

使用

当做一些事情,如注解处理或元数据处理例如,数据库模式,协议格式)时,源文件生成是有用处的。通过生成的代码,你需要同时保持正确的唯一来源的元数据模板。

例如

看一个简单无趣的栗子:

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);

申请这个main方法,我们创建一个MethodSpec(修饰符)来配置main方法,配置包含:返回值类型,参数列表,代码语句。我们添加main放到到HelloWorld类中,然后就添加了一个HelloWorld.java文件。
在这个Case中,我们写这个文件到System.out进行输出,但我们可以把它作为字符串或写入到文件系统。

这个文档目录有所有的JavaPoet接口,我们继续向下探究学习。

代码与控制流

很多JavaPoet接口使用了会稳定的Java类。这样构建,方法链式和交互使Api更友好。JavaPoet经常使用的典型案例有类&接口(TypeSpec),属性(FieldSpec),方法&构造方法(MethodSpec),参数(ParameterSpec)和注解(AnnotationSpec).
但是方法和构造方法的主题并没有建模。没有表达式类,没有语句类或语法树节点。相反,javapoet使用字符串的代码块:

MethodSpec main = 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;  }}

我们人输入分号,换行符和缩进,这些繁琐的的事情使用JavaPoet提供的接口使这些变得更容易。
这个.addStatement()方法负责分号和换行,beginControlFlow() + endControlFlow()需要一起使用,提供换行符和缩进。

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 = 0")      .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")      .addStatement("result = result " + op + " i")      .endControlFlow()      .addStatement("return result")      .build();}

下方就是我们执行上方代码computeRange(“multiply10to20”, 10, 20, “*”)得到以下结果:

int multiply10to20() {  int result = 0;  for (int i = 10; i < 20; i++) {    result = result * i;  }  return result;}

方法生成方法!因为JavaPoet生成的是源码不是字节码,你可以阅读它却确保是正确的。

对于文字:$L

连接字符串调用beginControlFlow() 和 addStatement是分散的。太多的操作者。为了解决这个,JavaPoet提供了一个语法灵感,但是不符合语法String.format()。它接受$L在输出中发出一个文字值。这就像是Formatter’s %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();}

文字直接排放在输出语句中,参数可以是字符串,语句,和一些JavaPoet类型的数据。

对于字符串:$S

当输出的代码中包含字符串时,我们可以使用$S发送一个字符串,完成和包裹引用。下面是一个程序,它会发出3种方法,每一种方法都会返回自己的名字:

public static void main(String[] args) throws Exception {  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)      .addMethod(whatsMyName("slimShady"))      .addMethod(whatsMyName("eminem"))      .addMethod(whatsMyName("marshallMathers"))      .build();  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)      .build();  javaFile.writeTo(System.out);}private static MethodSpec whatsMyName(String name) {  return MethodSpec.methodBuilder(name)      .returns(String.class)      .addStatement("return $S", name)      .build();}

在这个Case中,$S给添加引号:

public final class HelloWorld {  String slimShady() {    return "slimShady";  }  String eminem() {    return "eminem";  }  String marshallMathers() {    return "marshallMathers";  }}

对于泛型的类型:$T

我们Java程序喜欢我们自己的类型:它们生成的代码很容易让我们理解。在JavaPoet上,它有丰富的内置支持类型,包含自动生成import声明,使用$T映射到参考类型:

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);

生成一下.java文件,完成必要的import:

package com.example.helloworld;import java.util.Date;public final class HelloWorld {  Date today() {    return new Date();  }}

我们通过Date.class引用类,正好可以代码生成。这不需要是这样。这里有一个类似的例子,但这一个引用了一个不存在的类:

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");MethodSpec today = MethodSpec.methodBuilder("tomorrow")    .returns(hoverboard)    .addStatement("return new $T()", hoverboard)    .build();

而不存在的类,导入也是完好的。

package com.example.helloworld;import com.mattel.Hoverboard;public final class HelloWorld {  Hoverboard tomorrow() {    return new Hoverboard();  }}

这个ClassName类型是非常重要的,当你频繁使用JavaPoet时,你将需要它。它可以识别任何声明类。声明的类型是java的丰富的类型系统的开始:我们也有数组,参数化类型,通配符类型和类型变量。JavaPoet有类构建这些:

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("result.add(new $T())", hoverboard)    .addStatement("result.add(new $T())", hoverboard)    .addStatement("return result")    .build();

JavaPoet将每一种类型分解,并在可能的情况下导入其组件。

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());    result.add(new Hoverboard());    result.add(new Hoverboard());    return result;  }}

Import static

JavaPoet支持导入静态类。它通过显式收集类型成员名称。让我们以一些静态来举栗子:

...ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");MethodSpec beyond = MethodSpec.methodBuilder("beyond")    .returns(listOfHoverboards)    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)    .addStatement("result.add($T.createNimbus(2000))", hoverboard)    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)    .addStatement("$T.sort(result)", Collections.class)    .addStatement("return result.isEmpty() $T.emptyList() : result", Collections.class)    .build();TypeSpec hello = TypeSpec.classBuilder("HelloWorld")    .addMethod(beyond)    .build();JavaFile.builder("com.example.helloworld", hello)    .addStaticImport(hoverboard, "createNimbus")    .addStaticImport(namedBoards, "*")    .addStaticImport(Collections.class, "*")    .build();

JavaPoet首先将导入静态块来配置文件,也需要导入其他类型。

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

生成的代码通常是自指的。使用$n引用另一个由它的名称生成的声明。这里的一个方法,调用另一个:

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');}

当发生上述代码,我们通过hexdigit()方法作为参数的bytetohex()方法使用$N:

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();

下一篇,接着解析或翻译剩余的部分;

文章出处:

[Coolspan CSDN博客:http://blog.csdn.net/qxs965266509][4]

欢迎关注我的公众号,实时给你推送文章,谢谢支持;

微信搜索公众号:coolspan

或保存以下二维码进行微信扫描:
此处输入图片的描述

1 0