Введение в JavaPoet

Введение в JavaPoet

1. обзор

В этом руководстве мы исследуем основные функции библиотекиJavaPoet.

JavaPoet разработанSquare, которыйprovides APIs to generate Java source code. Он может генерировать примитивные типы, ссылочные типы и их варианты (такие как классы, интерфейсы, перечисляемые типы, анонимные внутренние классы), поля, методы, параметры, аннотации и Javadocs.

JavaPoet автоматически управляет импортом зависимых классов. Он также использует шаблон Builder, чтобы указать логику для генерации кода Java.

2. Maven Dependency

Чтобы использовать JavaPoet, мы можем напрямую загрузить последнюю версиюJAR file или определить следующую зависимость в нашемpom.xml:


    com.squareup
    javapoet
    1.10.0

3. Спецификация метода

Во-первых, давайте рассмотрим спецификацию метода. Чтобы сгенерировать метод, мы просто вызываем методmethodBuilder() классаMethodSpec. Мы указываем сгенерированное имя метода как аргументString методаmethodBuilder().

Мы можемgenerate any single logical statement ending with the semi-colon использовать методaddStatement(). Между тем, мы можем определить один поток управления, ограниченный фигурными скобками, например блокif-else или циклfor, в потоке управления.

Вот быстрый пример - создание методаsumOfTen(), который вычисляет сумму чисел от 0 до 10:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addStatement("int sum = 0")
  .beginControlFlow("for (int i = 0; i <= 10; i++)")
  .addStatement("sum += i")
  .endControlFlow()
  .build();

Это даст следующий результат:

void sumOfTen() {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

4. Блок кода

Мы также можемwrap one or more control flows and logical statements into one code block:

CodeBlock sumOfTenImpl = CodeBlock
  .builder()
  .addStatement("int sum = 0")
  .beginControlFlow("for (int i = 0; i <= 10; i++)")
  .addStatement("sum += i")
  .endControlFlow()
  .build();

Который генерирует:

int sum = 0;
for (int i = 0; i <= 10; i++) {
    sum += i;
}

Мы можем упростить предыдущую логику вMethodSpec, вызвавaddCode() и предоставив объектsumOfTenImpl:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addCode(sumOfTenImpl)
  .build();

Блок кода также применим к другим спецификациям, таким как типы и Javadocs.

5. Спецификация поля

Далее - давайте рассмотрим логику спецификации поля.

Для создания поля мы используем методbuilder() классаFieldSpec:

FieldSpec name = FieldSpec
  .builder(String.class, "name")
  .addModifiers(Modifier.PRIVATE)
  .build();

Это сгенерирует следующее поле:

private String name;

Мы также можем инициализировать значение поля по умолчанию, вызвав методinitializer():

FieldSpec defaultName = FieldSpec
  .builder(String.class, "DEFAULT_NAME")
  .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
  .initializer("\"Alice\"")
  .build();

Который генерирует:

private static final String DEFAULT_NAME = "Alice";

6. Спецификация параметров

Теперь рассмотрим логику спецификации параметров.

Если мы хотим добавить параметр к методу, мы можем вызватьaddParameter() в цепочке вызовов функций в построителе.

В случае более сложных типов параметров мы можем использовать построительParameterSpec:

ParameterSpec strings = ParameterSpec
  .builder(
    ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)),
    "strings")
  .build();

Мы также можем добавить модификатор метода, напримерpublic и / илиstatic:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

Вот как выглядит сгенерированный код Java:

public static void sumOfTen(int number, List strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

7. Спецификация типа

После изучения способов создания методов, полей и параметров, теперь давайте взглянем на спецификации типов.

Чтобы объявить тип, мы можем использоватьTypeSpec which can build classes, interfaces, and enumerated types.

7.1. Создание класса

Чтобы сгенерировать класс, мы можем использовать методclassBuilder() классаTypeSpec.

Мы также можем указать его модификаторы, например, модификаторы доступаpublic иfinal. В дополнение к модификаторам классов мы также можем указать поля и методы, используя уже упомянутые классыFieldSpec иMethodSpec.

Обратите внимание, что методыaddField() иaddMethod() также доступны при создании интерфейсов или анонимных внутренних классов.

Давайте посмотрим на следующий пример построителя классов:

TypeSpec person = TypeSpec
  .classBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(name)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC)
    .returns(String.class)
    .addStatement("return this.name")
    .build())
  .addMethod(MethodSpec
    .methodBuilder("setName")
    .addParameter(String.class, "name")
    .addModifiers(Modifier.PUBLIC)
    .returns(String.class)
    .addStatement("this.name = name")
    .build())
  .addMethod(sumOfTen)
  .build();

А вот как выглядит сгенерированный код:

public class Person {
    private String name;

    public String getName() {
        return this.name;
    }

    public String setName(String name) {
        this.name = name;
    }

    public static void sumOfTen(int number, List strings) {
        int sum = 0;
        for (int i = 0; i <= 10; i++) {
            sum += i;
        }
    }
}

7.2. Создание интерфейса

Чтобы сгенерировать интерфейс Java, мы используем методinterfaceBuilder() изTypeSpec.

Мы также можем определить метод по умолчанию, указав значение модификатораDEFAULT вaddModifiers():

TypeSpec person = TypeSpec
  .interfaceBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(defaultName)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .build())
  .addMethod(MethodSpec
    .methodBuilder("getDefaultName")
    .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
    .addCode(CodeBlock
      .builder()
      .addStatement("return DEFAULT_NAME")
      .build())
    .build())
  .build();

Он сгенерирует следующий код Java:

public interface Person {
    private static final String DEFAULT_NAME = "Alice";

    void getName();

    default void getDefaultName() {
        return DEFAULT_NAME;
    }
}

7.3. Генерация Enum

Чтобы сгенерировать перечислимый тип, мы можем использовать методenumBuilder() изTypeSpec. Чтобы указать каждое перечислимое значение, мы можем вызвать методaddEnumConstant():

TypeSpec gender = TypeSpec
  .enumBuilder("Gender")
  .addModifiers(Modifier.PUBLIC)
  .addEnumConstant("MALE")
  .addEnumConstant("FEMALE")
  .addEnumConstant("UNSPECIFIED")
  .build();

Результатом вышеупомянутой логикиenumBuilder() является:

public enum Gender {
    MALE,
    FEMALE,
    UNSPECIFIED
}

7.4. Создание анонимного внутреннего класса

Чтобы сгенерировать анонимный внутренний класс, мы можем использовать методanonymousClassBuilder() классаTypeSpec. Обратите внимание, чтоwe must specify the parent class in the addSuperinterface() method. В противном случае он будет использовать родительский класс по умолчанию, то естьObject:

TypeSpec comparator = TypeSpec
  .anonymousClassBuilder("")
  .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
  .addMethod(MethodSpec
    .methodBuilder("compare")
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "a")
    .addParameter(String.class, "b")
    .returns(int.class)
    .addStatement("return a.length() - b.length()")
    .build())
  .build();

Это сгенерирует следующий код Java:

new Comparator() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

8. Спецификация аннотации

Чтобы добавить аннотацию к сгенерированному коду, мы можем вызвать методaddAnnotation() в классе построителяMethodSpec илиFieldSpec:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

Который генерирует:

@Override
public static void sumOfTen(int number, List strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

Если нам нужно указать значение члена, мы можем вызвать методaddMember() классаAnnotationSpec:

AnnotationSpec toString = AnnotationSpec
  .builder(ToString.class)
  .addMember("exclude", "\"name\"")
  .build();

Это создаст следующую аннотацию:

@ToString(
    exclude = "name"
)

9. Создание документации Javadoc

Документацию Javadoc можно создать с помощьюCodeBlock, или путем непосредственного указания значения:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addJavadoc(CodeBlock
    .builder()
    .add("Sum of all integers from 0 to 10")
    .build())
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

Это сгенерирует следующий код Java:

/**
 * Sum of all integers from 0 to 10
 */
@Override
public static void sumOfTen(int number, List strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

10. форматирование

Давайте еще раз проверим пример инициализатораFieldSpec вSection 5, который содержит escape-символ, используемый для выхода из значения «Alice»String:

initializer("\"Alice\"")

ВSection 8 также есть аналогичный пример, когда мы определяем исключенный член аннотации:

addMember("exclude", "\"name\"")

Он становится громоздким, когда наш код JavaPoet растет и имеет много похожих операторов переходаString или конкатенацииString.

Функция форматирования строк в JavaPoet упрощает форматированиеString в методахbeginControlFlow(),addStatement() илиinitializer(). The syntax is similar to String.format() functionality in Java. It can help to format literals, strings, types, and names.

10.1. Литеральное форматирование

JavaPoet replaces $L with a literal value in the output. Мы можем указать любой тип примитива и значенияString в аргументе (ах):

private MethodSpec generateSumMethod(String name, int from, int to, String operator) {
    return MethodSpec
      .methodBuilder(name)
      .returns(int.class)
      .addStatement("int sum = 0")
      .beginControlFlow("for (int i = $L; i <= $L; i++)", from, to)
      .addStatement("sum = sum $L i", operator)
      .endControlFlow()
      .addStatement("return sum")
      .build();
}

В случае, если мы вызываемgenerateSumMethod() со следующими указанными значениями:

generateSumMethod("sumOfOneHundred", 0, 100, "+");

JavaPoet сгенерирует следующий вывод:

int sumOfOneHundred() {
    int sum = 0;
    for (int i = 0; i <= 100; i++) {
        sum = sum + i;
    }
    return sum;
}

10.2. String Форматирование

ФорматированиеString генерирует значение с кавычками, которое относится исключительно к типуString в Java. JavaPoet replaces $S with a String value in the output:

private static MethodSpec generateStringSupplier(String methodName, String fieldName) {
    return MethodSpec
      .methodBuilder(methodName)
      .returns(String.class)
      .addStatement("return $S", fieldName)
      .build();
}

В случае, если мы вызываем методgenerateGetter() и предоставляем эти значения:

generateStringSupplier("getDefaultName", "Bob");

Мы получим следующий сгенерированный код Java:

String getDefaultName() {
    return "Bob";
}

10.3. Форматирование текста

JavaPoet replaces $T with a type in the generated Java code. JavaPoet автоматически обрабатывает тип в операторе импорта. Если бы мы предоставили тип как литерал, JavaPoet не справился бы с импортом.

MethodSpec getCurrentDateMethod = MethodSpec
  .methodBuilder("getCurrentDate")
  .returns(Date.class)
  .addStatement("return new $T()", Date.class)
  .build();

JavaPoet сгенерирует следующий вывод:

Date getCurrentDate() {
    return new Date();
}

10.4. Форматирование имени

Если нам нужноrefer to a name of a variable/parameter, field or method, we can use $N в программе форматирования JavaPoetString.

Мы можем добавить предыдущийgetCurrentDateMethod() к новому методу ссылки:

MethodSpec dateToString = MethodSpec
  .methodBuilder("getCurrentDateAsString")
  .returns(String.class)
  .addStatement(
    "$T formatter = new $T($S)",
    DateFormat.class,
    SimpleDateFormat.class,
    "MM/dd/yyyy HH:mm:ss")
  .addStatement("return formatter.format($N())", getCurrentDateMethod)
  .build();

Который генерирует:

String getCurrentDateAsString() {
    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    return formatter.format(getCurrentDate());
}

11. Генерация лямбда-выражений

Мы можем использовать уже изученные нами возможности для генерации лямбда-выражения. Например, блок кода, который несколько раз печатает полеname или переменную:

CodeBlock printNameMultipleTimes = CodeBlock
  .builder()
  .addStatement("$T<$T> names = new $T<>()", List.class, String.class, ArrayList.class)
  .addStatement("$T.range($L, $L).forEach(i -> names.add(name))", IntStream.class, 0, 10)
  .addStatement("names.forEach(System.out::println)")
  .build();

Эта логика генерирует следующий вывод:

List names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);

12. Получение результата с использованиемJavaFile

The JavaFile class helps to configure and produce the output of the generated code. Чтобы сгенерировать код Java, мы просто создаемJavaFile,, предоставляя имя пакета и экземпляр объектаTypeSpec.

12.1. Отступ кода

По умолчанию JavaPoet использует два пробела для отступа. Чтобы сохранить последовательность, все примеры в этом руководстве были представлены с отступом в 4 пробела, который настраивается с помощью методаindent():

JavaFile javaFile = JavaFile
  .builder("com.example.javapoet.person", person)
  .indent("    ")
  .build();

12.2. Статический импорт

В случае, если нам нужно добавить статический импорт, мы можем определить тип и конкретное имя метода вJavaFile, вызвав методaddStaticImport():

JavaFile javaFile = JavaFile
  .builder("com.example.javapoet.person", person)
  .indent("    ")
  .addStaticImport(Date.class, "UTC")
  .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
  .build();

Который генерирует следующие статические операторы импорта:

import static java.util.Date.UTC;
import static java.time.ZonedDateTime.*;

12.3. Выход

МетодwriteTo() предоставляет функциональные возможности для записи кода в несколько целей, таких как стандартный выходной поток (System.out) иFile.

Чтобы записать код Java в стандартный поток вывода, мы просто вызываем методwriteTo() и передаемSystem.out в качестве аргумента:

JavaFile javaFile = JavaFile
  .builder("com.example.javapoet.person", person)
  .indent("    ")
  .addStaticImport(Date.class, "UTC")
  .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
  .build();

javaFile.writeTo(System.out);

МетодwriteTo() также принимаетjava.nio.file.Path иjava.io.File. Мы можем предоставить соответствующий объектPath илиFile, чтобы сгенерировать файл исходного кода Java в папку / путь назначения:

Path path = Paths.get(destinationPath);
javaFile.writeTo(path);

Для получения более подробной информации относительноJavaFile, пожалуйста, обратитесь кJavadoc.

13. Заключение

Эта статья была введением в функциональные возможности JavaPoet, такие как создание методов, полей, параметров, типов, аннотаций и Javadocs.

JavaPoet предназначен только для генерации кода. Если мы хотим выполнять метапрограммирование с помощью Java, JavaPoet версии 1.10.0 не поддерживает компиляцию и выполнение кода.

Как всегда, доступны примеры и фрагменты кодаover on GitHub.