zoukankan      html  css  js  c++  java
  • JavaPoet的基本使用

    原文:https://blog.csdn.net/crazy1235/article/details/51876192

    JavaPoet

    JavaPoet 是一个用来生成 .java源文件的Java API。

    当做如注解或者数据库模式、协议格式等事情时,生成源文件就比较有用处。

    Example

    以 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类来创建一个”main”方法,并配置了修饰符、返回值类型、参数以及代码语句。然后把这个main方法添加到 HelloWorld 类中,最后添加到 HelloWorld.java文件中。 
    这个例子中,我们将文件通过Sytem.out 进行输出,但是同样也可以使用(JavaFile.toString()) 得到string字符串,或者通过 (JavaPoet.writeTo()) 方法写入到文件系统中。

    Javadoc 中包括了完整的JavaPoet API, 我们接着往下看。

    Code & Control Flow

    大多数JavaPoet的API使用的是简单的不可变的Java对象。通过建造者模式,链式方法,可变参数是的API比较友好。JavaPoet提供了(TypeSpec)用于创建类或者接口,(FieldSpec)用来创建字段,(MethodSpec)用来创建方法和构造函数,(ParameterSpec)用来创建参数,(AnnotationSpec)用于创建注解。

    但是如果没有语句类,没有语法结点数,可以通过字符串来构建代码块:

    MethodSpec main = MethodSpec.methodBuilder("main")
        .addCode(""
            + "int total = 0;
    "
            + "for (int i = 0; i < 10; i++) {
    "
            + "  total += i;
    "
            + "}
    ")
        .build();

    生成的代码如下:

    void main() {
      int total = 0;
      for (int i = 0; i < 10; i++) {
        total += i;
      }
    }

    人为的输入分号、换行和缩进是比较乏味的。所以JavaPoet提供了相关API使它变的容易。 
    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();

    这个例子稍微有点差劲。生成的代码如下:

    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 for Literals

    字符串连接的方法beginControlFlow() 和 addStatement是分散开的,操作较多。 
    针对这个问题, JavaPoet 提供了一个语法但是有违String.format()语法. 通过 $L 来接受一个 literal 值。 这有点像 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();
    }

    Literals 直接写在输出代码中,没有转义。 它的类型可以是字符串、primitives和一些接下来要说的JavaPoet类型。

    $S for Strings

    当输出的代码包含字符串的时候, 可以使用 $S 表示一个 string。 下面的代码包含三个方法,每个方法返回自己的名字:

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

    输出结果如下:

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

    $T for Types

    使用Java内置的类型会使代码比较容易理解。JavaPoet极大的支持这些类型,通过 $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);

    自动完成import声明,生成代码如下:

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

    再举一个相似的例子,但是应用了一个不存在的类:

    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的时候会频繁的使用它。 
    它可以识别任何声明类。具体看下面的例子:

    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支持import static。它显示的收集类型成员的名称。例子如下:

    ...
    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将会首先添加 import static 代码块进行配置,当然也需要导入其他所需的类型引用。

    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 for Names

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

    生成的代码如下,在byteToHex()方法中通过$N来引用 hexDigit()方法作为一个参数:

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

    Methods

    上面的例子中的方法都有方法体。 使用 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();
    }

    当执行修饰符的时。JavaPoet用的是 
    javax.lang.model.element.Modifier类,这个类在android平台上不可用. 这只限制与生成代码阶段;输出的代码可运行在任何平台上: JVMs, Android, 
    and GWT。

    方法可能会有参数,异常,可变参数,注释,注解,类型变量和一个返回类型。这些都可以通过 MethodSpec.Builder 来进行配置。

    Constructors

    MethodSpec 也可以用来创建构造函数:

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

    多数情况下,构造方法同普通方法一样。当生成代码时,构造函数会先于其他方法生成。

    Parameters

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

    虽然上面的代码生成android 和 robot这两个参数是不同的方式,但是输出是一样的:

    void welcomeOverlords(final String android, final String robot) {
    }

    当参数有注解(比如 @Nullable)的时候,通过扩展的 Builder 方式创建参数是比较方便的。

    Fields

    字段通参数一样通过 build 方式创建:

    FieldSpec android = FieldSpec.builder(String.class, "android")
        .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
        .build();
    
    TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
        .addModifiers(Modifier.PUBLIC)
        .addField(android)
        .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
        .build();

    生成如下代码:

    public class HelloWorld {
      private final String android;
    
      private final String robot;
    }

    通关 Builder 方式很容易生成带注释、注解或者初始化的字段。 
    Field的初始化代码如下:

    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;

    Interfaces

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

    但是这些修饰符在生成的java文件中是找不到的。这些都是缺省值。

    public interface HelloWorld {
      String ONLY_THING_THAT_IS_CONSTANT = "change";
    
      void beep();
    }

    Enums

    通过 enumBuilder 可以创建枚举类型, 调用 addEnumConstant() 可添加枚举变量:

    TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
        .addModifiers(Modifier.PUBLIC)
        .addEnumConstant("ROCK")
        .addEnumConstant("SCISSORS")
        .addEnumConstant("PAPER")
        .build();

    生成如下代码:

    public enum Roshambo {
      ROCK,
    
      SCISSORS,
    
      PAPER
    }

    Fancy enums 也是支持的。

    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!")
                .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 void toString() {
          return "avalanche!";
        }
      },
    
      SCISSORS("peace"),
    
      PAPER("flat");
    
      private final String handsign;
    
      Roshambo(String handsign) {
        this.handsign = handsign;
      }
    }

    Anonymous Inner Classes

    在上面的枚举代码汇总,使用了 Types.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("")

    Annotations

    对方法添加注解非常简单:

    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

    变量方法和类都可以添加注释:

    MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
        .addJavadoc("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.
    ")
        .addJavadoc("
    ")
        .addJavadoc("<p>Use {@link #delete($T)} to delete the entire
    "
            + "conversation for all participants.
    ", 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);

    使用 $T 可以自动导入类型的引用。

    Download

    下载最新的 the latest .jar

    或者添加Maven依赖:

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

    或者Gradle依赖:

    compile 'com.squareup:javapoet:1.7.0'

    Snapshots of the development version are available in Sonatype’s snapshots repository.

  • 相关阅读:
    粉丝投稿!从2月份的面试被拒到如今的阿里P7,说一说自己学java以来的经验!
    深入浅出!阿里P7架构师带你分析ArrayList集合源码,建议是先收藏再看!
    简单梳理一下Redis实现分布式Session,建议做java开发的都看看!
    HashMap知识点总结,这一篇算是总结的不错的了,建议看看!
    面试官:小伙子,够了够了,一个工厂模式你都在这说半个小时了!
    iOS-----推送机制(下)
    iOS-----推送机制(上)
    iOS-----使用CoreLocation定位
    iOS-----使用AFNetworking实现网络通信
    iOS-----JSON解析
  • 原文地址:https://www.cnblogs.com/wytiger/p/10954087.html
Copyright © 2011-2022 走看看