Einführung in JavaPoet
1. Überblick
In diesem Tutorial werden die grundlegenden Funktionen derJavaPoet-Bibliothek erläutert.
JavaPoet wird durchSquare entwickelt, wobeiprovides APIs to generate Java source code. Es kann primitive Typen, Referenztypen und deren Varianten (wie Klassen, Interfaces, Aufzählungstypen, anonyme innere Klassen), Felder, Methoden, Parameter, Anmerkungen und Javadocs generieren.
JavaPoet verwaltet den Import der abhängigen Klassen automatisch. Außerdem wird das Builder-Muster verwendet, um die Logik zum Generieren von Java-Code anzugeben.
2. Maven-Abhängigkeit
Um JavaPoet verwenden zu können, können wir die neuestenJAR file direkt herunterladen oder die folgende Abhängigkeit in unserenpom.xml: definieren
com.squareup
javapoet
1.10.0
3. Methodenspezifikation
Lassen Sie uns zunächst die Methodenspezifikation durchgehen. Um eine Methode zu generieren, rufen wir einfach diemethodBuilder()-Methode derMethodSpec-Klasse auf. Wir geben den generierten Methodennamen alsString Argument dermethodBuilder() Methode an.
Wir könnengenerate any single logical statement ending with the semi-colon mit der MethodeaddStatement(). In der Zwischenzeit können wir einen Kontrollfluss definieren, der mit geschweiften Klammern begrenzt ist, z. B.if-else Block oderfor Schleife, in einem Kontrollfluss.
Hier ein kurzes Beispiel: Generieren dersumOfTen()-Methode, mit der die Summe der Zahlen von 0 bis 10 berechnet wird:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addStatement("int sum = 0")
.beginControlFlow("for (int i = 0; i <= 10; i++)")
.addStatement("sum += i")
.endControlFlow()
.build();
Dies wird die folgende Ausgabe erzeugen:
void sumOfTen() {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
4. Codeblock
Wir können auchwrap 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();
Welches erzeugt:
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
Wir können die frühere Logik inMethodSpec vereinfachen, indem wiraddCode() aufrufen und das ObjektsumOfTenImpl bereitstellen:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addCode(sumOfTenImpl)
.build();
Ein Codeblock gilt auch für andere Spezifikationen, z. B. Typen und Javadocs.
5. Feldspezifikation
Weiter - Lassen Sie uns die Feldspezifikationslogik untersuchen.
Um ein Feld zu generieren, verwenden wir diebuilder()-Methode derFieldSpec-Klasse:
FieldSpec name = FieldSpec
.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.build();
Dadurch wird das folgende Feld generiert:
private String name;
Wir können den Standardwert eines Feldes auch initialisieren, indem wir die Methodeinitializer()aufrufen:
FieldSpec defaultName = FieldSpec
.builder(String.class, "DEFAULT_NAME")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("\"Alice\"")
.build();
Welches erzeugt:
private static final String DEFAULT_NAME = "Alice";
6. Parameterspezifikation
Lassen Sie uns nun die Parameterspezifikationslogik untersuchen.
Wenn wir der Methode einen Parameter hinzufügen möchten, können wiraddParameter() innerhalb der Kette der Funktionsaufrufe im Builder aufrufen.
Bei komplexeren Parametertypen können wir den Builder vonParameterSpecverwenden:
ParameterSpec strings = ParameterSpec
.builder(
ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)),
"strings")
.build();
Wir können auch den Modifikator der Methode hinzufügen, z. B.public und / oderstatic:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
So sieht der generierte Java-Code aus:
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
7. Typenspezifikation
Nachdem wir die Möglichkeiten zum Generieren von Methoden, Feldern und Parametern untersucht haben, werfen wir nun einen Blick auf die Typspezifikationen.
Um einen Typ zu deklarieren, können wirTypeSpec which can build classes, interfaces, and enumerated types verwenden.
7.1. Eine Klasse generieren
Um eine Klasse zu generieren, können wir dieclassBuilder()-Methode derTypeSpec-Klasse verwenden.
Wir können auch die Modifikatoren angeben, z. B. die Zugriffsmodifikatorenpublic andfinal. Zusätzlich zu Klassenmodifikatoren können wir auch Felder und Methoden mit den bereits erwähnten KlassenFieldSpec undMethodSpec angeben.
Beachten Sie, dass die MethodenaddField() undaddMethod() auch beim Generieren von Schnittstellen oder anonymen inneren Klassen verfügbar sind.
Schauen wir uns das folgende Beispiel für den Klassenersteller an:
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();
Und so sieht der generierte Code aus:
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. Schnittstelle generieren
Um eine Java-Schnittstelle zu generieren, verwenden wir dieinterfaceBuilder()-Methode derTypeSpec.
Wir können auch eine Standardmethode definieren, indem wir den Modifikatorwert vonDEFAULTinaddModifiers()angeben:
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();
Es wird der folgende Java-Code generiert:
public interface Person {
private static final String DEFAULT_NAME = "Alice";
void getName();
default void getDefaultName() {
return DEFAULT_NAME;
}
}
7.3. Eine Aufzählung generieren
Um einen Aufzählungstyp zu generieren, können wir dieenumBuilder()-Methode derTypeSpec verwenden. Um jeden aufgezählten Wert anzugeben, können wir die MethodeaddEnumConstant() aufrufen:
TypeSpec gender = TypeSpec
.enumBuilder("Gender")
.addModifiers(Modifier.PUBLIC)
.addEnumConstant("MALE")
.addEnumConstant("FEMALE")
.addEnumConstant("UNSPECIFIED")
.build();
Die Ausgabe der oben genanntenenumBuilder()-Logik ist:
public enum Gender {
MALE,
FEMALE,
UNSPECIFIED
}
7.4. Generieren einer anonymen inneren Klasse
Um eine anonyme innere Klasse zu generieren, können wir dieanonymousClassBuilder()-Methode derTypeSpec-Klasse verwenden. Beachten Sie, dasswe must specify the parent class in the addSuperinterface() method. Andernfalls wird die standardmäßige übergeordnete Klasse verwendet, nämlichObject:
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();
Dadurch wird der folgende Java-Code generiert:
new Comparator() {
public int compare(String a, String b) {
return a.length() - b.length();
}
});
8. Anmerkungsspezifikation
Um dem generierten Code eine Anmerkung hinzuzufügen, können Sie dieaddAnnotation()-Methode in einer Builder-Klasse vonMethodSpec oderFieldSpecaufrufen:
MethodSpec sumOfTen = MethodSpec
.methodBuilder("sumOfTen")
.addAnnotation(Override.class)
.addParameter(int.class, "number")
.addParameter(strings)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addCode(sumOfTenImpl)
.build();
Welches erzeugt:
@Override
public static void sumOfTen(int number, List strings) {
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
Falls wir den Elementwert angeben müssen, können wir die MethodeaddMember()der KlasseAnnotationSpecaufrufen:
AnnotationSpec toString = AnnotationSpec
.builder(ToString.class)
.addMember("exclude", "\"name\"")
.build();
Dadurch wird die folgende Anmerkung generiert:
@ToString(
exclude = "name"
)
9. Javadocs generieren
Javadoc kann mitCodeBlock, oder durch direkte Angabe des Werts generiert werden:
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();
Dadurch wird der folgende Java-Code generiert:
/**
* 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. Formatierung
Überprüfen wir noch einmal das Beispiel des Initialisierers vonFieldSpecinSection 5, der ein Escape-Zeichen enthält, mit dem der Wert von "Alice"Stringmaskiert wird:
initializer("\"Alice\"")
Es gibt auch ein ähnliches Beispiel inSection 8, wenn wir das ausgeschlossene Mitglied einer Annotation definieren:
addMember("exclude", "\"name\"")
Es wird unhandlich, wenn unser JavaPoet-Code wächst und viele ähnlicheStringEscape- oderStringVerkettungsanweisungen enthält.
Die Zeichenfolgenformatierungsfunktion in JavaPoet erleichtert die Formatierung vonStringin den MethodenbeginControlFlow(),addStatement() oderinitializer(). The syntax is similar to String.format() functionality in Java. It can help to format literals, strings, types, and names.
10.1. Literale Formatierung
JavaPoet replaces $L with a literal value in the output. Wir können jeden primitiven Typ undString Werte in den Argumenten angeben:
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();
}
Falls wirgenerateSumMethod() mit den folgenden angegebenen Werten aufrufen:
generateSumMethod("sumOfOneHundred", 0, 100, "+");
JavaPoet generiert die folgende Ausgabe:
int sumOfOneHundred() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
10.2. String Formatierung
Die Formatierung vonStringgeneriert einen Wert mit dem Anführungszeichen, der sich ausschließlich auf den TypStringin Java bezieht. 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();
}
Falls wir die MethodegenerateGetter()aufrufen und folgende Werte angeben:
generateStringSupplier("getDefaultName", "Bob");
Wir erhalten folgenden generierten Java-Code:
String getDefaultName() {
return "Bob";
}
10.3. Geben Sie Formatierung ein
JavaPoet replaces $T with a type in the generated Java code. JavaPoet behandelt den Typ in der Importanweisung automatisch. Wenn wir den Typ stattdessen als Literal angegeben hätten, würde JavaPoet den Import nicht verarbeiten.
MethodSpec getCurrentDateMethod = MethodSpec
.methodBuilder("getCurrentDate")
.returns(Date.class)
.addStatement("return new $T()", Date.class)
.build();
JavaPoet generiert die folgende Ausgabe:
Date getCurrentDate() {
return new Date();
}
10.4. Namensformatierung
Falls wirrefer to a name of a variable/parameter, field or method, we can use $N imString-Formatierer von JavaPoet benötigen.
Wir können die vorherigengetCurrentDateMethod() zur neuen Referenzierungsmethode hinzufügen:
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();
Welches erzeugt:
String getCurrentDateAsString() {
DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
return formatter.format(getCurrentDate());
}
11. Lambda-Ausdrücke generieren
Wir können die Funktionen, die wir bereits untersucht haben, verwenden, um einen Lambda-Ausdruck zu generieren. Zum Beispiel ein Codeblock, der das Feldname oder eine Variable mehrmals druckt:
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();
Diese Logik generiert die folgende Ausgabe:
List names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);
12. Erzeugen der Ausgabe mitJavaFile
The JavaFile class helps to configure and produce the output of the generated code. Um Java-Code zu generieren, erstellen wir einfach dieJavaFile,, die den Paketnamen und eine Instanz desTypeSpec-Objekts angeben.
12.1. Codeeinrückung
Standardmäßig verwendet JavaPoet zwei Leerzeichen zum Einrücken. Um die Konsistenz zu gewährleisten, wurden alle Beispiele in diesem Lernprogramm mit einem 4-Leerzeichen-Einzug dargestellt, der über die Methodeindent()konfiguriert wird:
JavaFile javaFile = JavaFile
.builder("com.example.javapoet.person", person)
.indent(" ")
.build();
12.2. Statische Importe
Falls wir einen statischen Import hinzufügen müssen, können wir den Typ und den spezifischen Methodennamen inJavaFile definieren, indem wir die MethodeaddStaticImport() aufrufen:
JavaFile javaFile = JavaFile
.builder("com.example.javapoet.person", person)
.indent(" ")
.addStaticImport(Date.class, "UTC")
.addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
.build();
Wodurch die folgenden statischen Importanweisungen generiert werden:
import static java.util.Date.UTC;
import static java.time.ZonedDateTime.*;
12.3. Ausgabe
Die MethodewriteTo() bietet Funktionen zum Schreiben des Codes in mehrere Ziele, z. B. Standardausgabestream (System.out) undFile.
Um Java-Code in einen Standardausgabestream zu schreiben, rufen wir einfach die MethodewriteTo() auf und gebenSystem.out als Argument an:
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);
Die MethodewriteTo() akzeptiert auchjava.nio.file.Path undjava.io.File. Wir können das entsprechendePath- oderFile-Objekt bereitstellen, um die Java-Quellcodedatei im Zielordner / -pfad zu generieren:
Path path = Paths.get(destinationPath);
javaFile.writeTo(path);
Ausführlichere Informationen zuJavaFile finden Sie unterJavadoc.
13. Fazit
Dieser Artikel enthält eine Einführung in JavaPoet-Funktionen wie das Generieren von Methoden, Feldern, Parametern, Typen, Anmerkungen und Javadocs.
JavaPoet ist nur für die Codegenerierung vorgesehen. Für den Fall, dass wir Metaprogrammierung mit Java durchführen möchten, unterstützt JavaPoet ab Version 1.10.0 das Kompilieren und Ausführen von Code nicht.
Wie immer sind die Beispiele und Codefragmenteover on GitHub verfügbar.