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

简介

官方描述: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

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

猜你喜欢

转载自blog.csdn.net/qxs965266509/article/details/53390860