Lombok, sources.jar und praktisches Debuggen

In unserem Team lieben wir Lombok wirklich. Sie können weniger Code schreiben und weniger umgestalten, was ideal für faule Entwickler ist. Wenn Sie jedoch zusätzlich zum Projektartefakt auch den Quellcode mit der Dokumentation veröffentlichen, tritt möglicherweise ein Problem auf - der Quellcode stimmt nicht mit dem Bytecode überein. Ich werde in diesem Beitrag darüber sprechen, wie wir dieses Problem gelöst haben und auf welche Schwierigkeiten wir dabei gestoßen sind.



Übrigens, wenn Sie in Java schreiben und aus irgendeinem Grund Lombok in Ihrem Projekt immer noch nicht verwenden, empfehle ich Ihnen, die Artikel über Habré ( eins und zwei ) zu lesen. Ich bin sicher, es wird dir gefallen!

Das Problem


Das Projekt, an dem wir arbeiten, besteht aus mehreren Modulen. Einige von ihnen (nennen wir sie bedingt Backend) werden im Archiv (Lieferung) gepackt, wenn die Version veröffentlicht, in das Repository hochgeladen und anschließend auf Anwendungsservern bereitgestellt wird. Der andere Teil ist der sogenannte Client-Modul - im Repository als eine Reihe von Artefakten veröffentlicht, einschließlich source.jar und javadoc.jar. Wir verwenden Lombok in allen Teilen und Maven wird alles tun.

Vor einiger Zeit hat einer der Verbraucher unseres Dienstes ein Problem angesprochen - er hat versucht, unser Modul zu debuggen, konnte es aber nicht, weil Quellen.jar fehlten Methoden (und sogar Klassen), in denen er Haltepunkte setzen möchte. Wir in unserem Team glauben, dass der Versuch, ein Problem unabhängig zu identifizieren und zu lösen, anstatt gedankenlos einen Fehler zu machen, ein Akt eines würdigen Ehemanns ist, der ermutigt werden muss! :-) Daher wurde beschlossen, sources.jar in Übereinstimmung mit dem Bytecode zu bringen.

Beispiel


Stellen wir uns vor, wir haben eine einfache Anwendung, die aus zwei Klassen besteht:

SomePojo.java
package com.github.monosoul.lombok.sourcesjar; import lombok.Builder; import lombok.Value; @Value @Builder(toBuilder = true) class SomePojo { /** * Some string field */ String someStringField; /** * Another string field */ String anotherStringField; } 


Main.java
 package com.github.monosoul.lombok.sourcesjar; import lombok.val; public final class Main { public static void main(String[] args) { if (args.length != 2) { throw new IllegalArgumentException("Wrong arguments!"); } val pojo = SomePojo.builder() .someStringField(args[0]) .anotherStringField(args[1]) .build(); System.out.println(pojo); } } 


Und unsere Anwendung wurde mit Maven erstellt:

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>lombok-sourcesjar</artifactId> <groupId>com.github.monosoul</groupId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.1</version> <configuration> <archive> <manifest> <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> 


Wenn Sie dieses Projekt kompilieren ( mvn compile ) und dann den resultierenden Bytecode dekompilieren, sieht die SomePojo- Klasse folgendermaßen aus:

SomePojo.class
 package com.github.monosoul.lombok.sourcesjar; final class SomePojo { private final String someStringField; private final String anotherStringField; SomePojo(String someStringField, String anotherStringField) { this.someStringField = someStringField; this.anotherStringField = anotherStringField; } public static SomePojo.SomePojoBuilder builder() { return new SomePojo.SomePojoBuilder(); } public SomePojo.SomePojoBuilder toBuilder() { return (new SomePojo.SomePojoBuilder()).someStringField(this.someStringField).anotherStringField(this.anotherStringField); } public String getSomeStringField() { return this.someStringField; } public String getAnotherStringField() { return this.anotherStringField; } public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof SomePojo)) { return false; } else { SomePojo other = (SomePojo)o; Object this$someStringField = this.getSomeStringField(); Object other$someStringField = other.getSomeStringField(); if (this$someStringField == null) { if (other$someStringField != null) { return false; } } else if (!this$someStringField.equals(other$someStringField)) { return false; } Object this$anotherStringField = this.getAnotherStringField(); Object other$anotherStringField = other.getAnotherStringField(); if (this$anotherStringField == null) { if (other$anotherStringField != null) { return false; } } else if (!this$anotherStringField.equals(other$anotherStringField)) { return false; } return true; } } public int hashCode() { int PRIME = true; int result = 1; Object $someStringField = this.getSomeStringField(); int result = result * 59 + ($someStringField == null ? 43 : $someStringField.hashCode()); Object $anotherStringField = this.getAnotherStringField(); result = result * 59 + ($anotherStringField == null ? 43 : $anotherStringField.hashCode()); return result; } public String toString() { return "SomePojo(someStringField=" + this.getSomeStringField() + ", anotherStringField=" + this.getAnotherStringField() + ")"; } public static class SomePojoBuilder { private String someStringField; private String anotherStringField; SomePojoBuilder() { } public SomePojo.SomePojoBuilder someStringField(String someStringField) { this.someStringField = someStringField; return this; } public SomePojo.SomePojoBuilder anotherStringField(String anotherStringField) { this.anotherStringField = anotherStringField; return this; } public SomePojo build() { return new SomePojo(this.someStringField, this.anotherStringField); } public String toString() { return "SomePojo.SomePojoBuilder(someStringField=" + this.someStringField + ", anotherStringField=" + this.anotherStringField + ")"; } } } 


Ganz anders als das, was in unsere Quellen gelangt. Jar, nicht wahr? ;) Wie Sie sehen, würden Sie, wenn Sie den Quellcode für das SomePojo- Debug verbunden und beispielsweise im Konstruktor einen Haltepunkt setzen wollten, auf ein Problem stoßen - es gibt keinen Ort, an dem der Haltepunkt gesetzt werden kann, und die SomePojoBuilder- Klasse ist überhaupt nicht vorhanden.

Was tun?


Wie so oft hat dieses Problem mehrere Lösungen. Schauen wir uns jeden an.

Verwenden Sie Lombok nicht


Als wir zum ersten Mal auf dieses Problem stießen, sprachen wir über ein Modul, in dem es nur wenige Klassen gab, die Lombok verwendeten. Natürlich wollte ich es nicht ablehnen, also dachte ich sofort darüber nach, Delombok zu machen. Nachdem ich diese Frage untersucht hatte, fand ich einige seltsame Lösungen mit Mavens Lombok-Plugin - Lombok -Maven-Plugin . In einem von ihnen wurde beispielsweise vorgeschlagen, die Quellen, in denen Lombok verwendet wird, in einem separaten Verzeichnis aufzubewahren, für das Delombok ausgeführt wird, und die generierten Quellen werden zu generierten Quellen geleitet, von wo aus sie kompiliert werden und zu sources.jar gehen. Diese Option funktioniert wahrscheinlich, aber in diesem Fall funktioniert die Syntaxhervorhebung in den Originalquellen in der IDE nicht Ein Verzeichnis mit ihnen wird nicht als Quellverzeichnis betrachtet. Diese Option passte nicht zu mir, und da die Kosten für das Verlassen von Lombok in diesem Modul gering waren, wurde beschlossen, keine Zeit damit zu verschwenden, Lombok zu deaktivieren und einfach die erforderlichen Methoden über die IDE zu generieren.

Im Allgemeinen scheint mir diese Option das Recht auf Leben zu haben, aber nur, wenn die Klassen, die Lombok verwenden, wirklich klein sind und sich selten ändern.


Delombok Plugin + Quellen.jar Build mit Ant


Nach einiger Zeit musste ich wieder auf dieses Problem zurückkommen, wenn es um das Modul ging, in dem Lombok viel intensiver eingesetzt wurde. Als ich noch einmal auf die Untersuchung dieses Problems zurückkam , stieß ich auf eine Frage zum Stackoverflow , in der vorgeschlagen wurde, Delombok für die Quellen auszuführen und dann Quellen in Ant zu verwenden, um sources.jar zu generieren.
Hier müssen Sie abschweifen, warum Sie sources.jar mit Ant generieren müssen, anstatt das Source-Plug-In ( maven-source-plugin ) zu verwenden. Tatsache ist, dass Sie das Quellverzeichnis für dieses Plugin nicht konfigurieren können. Es wird immer der Inhalt der sourceDirectory Eigenschaft des Projekts verwendet.

In unserem Beispiel sieht pom.xml also folgendermaßen aus:

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>lombok-sourcesjar</artifactId> <groupId>com.github.monosoul</groupId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <lombok.version>1.18.2</lombok.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.1</version> <configuration> <archive> <manifest> <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId> <version>${lombok.version}.0</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>delombok</goal> </goals> </execution> </executions> <configuration> <sourceDirectory>src/main/java</sourceDirectory> <outputDirectory>${project.build.directory}/delombok</outputDirectory> <addOutputDirectory>false</addOutputDirectory> <encoding>UTF-8</encoding> <formatPreferences> <generateDelombokComment>skip</generateDelombokComment> </formatPreferences> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>copy-to-lombok-build</id> <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> </resources> <outputDirectory>${project.build.directory}/delombok</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <id>generate-delomboked-sources-jar</id> <phase>package</phase> <goals> <goal>run</goal> </goals> <configuration> <target> <jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar" basedir="${project.build.directory}/delombok"/> </target> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> <executions> <execution> <id>install-source-jar</id> <goals> <goal>install-file</goal> </goals> <phase>install</phase> <configuration> <file>${project.build.directory}/${project.build.finalName}-sources.jar</file> <classifier>sources</classifier> <generatePom>true</generatePom> <pomFile>${project.basedir}/pom.xml</pomFile> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>3.0.0-M1</version> <executions> <execution> <id>deploy-source-jar</id> <goals> <goal>deploy-file</goal> </goals> <phase>deploy</phase> <configuration> <file>${project.build.directory}/${project.build.finalName}-sources.jar</file> <classifier>sources</classifier> <generatePom>true</generatePom> <pomFile>${project.basedir}/pom.xml</pomFile> <repositoryId>someRepoId</repositoryId> <url>some://repo.url</url> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> 


Wie Sie sehen können, ist die Konfiguration sehr gewachsen und weit entfernt von nur lombok-maven-plugin und maven-antrun-plugin . Warum ist das passiert? Tatsache ist, dass Maven nichts über dieses Artefakt weiß, da wir jetzt mit Ant Quellen.jar sammeln. Und wir müssen ihm klar sagen, wie dieses Artefakt installiert, wie es bereitgestellt und wie Ressourcen darin gepackt werden.

Außerdem habe ich festgestellt, dass Lombok beim Ausführen von Delombok standardmäßig einen Kommentar zum Header der generierten Dateien hinzufügt. In diesem Fall wird das Format der generierten Dateien nicht über die Optionen in der Datei lombok.config , sondern über die Plugin-Optionen gesteuert. Die Liste dieser Optionen war nicht leicht zu finden. Es war natürlich möglich, Lomboks Jar-Spitznamen mit den Tasten delombok und --help , aber ich bin zu faul für diesen Programmierer, also habe ich sie in den Quellcodes auf dem Github gefunden .

Aber weder das Volumen der Konfiguration noch ihre Merkmale können mit dem Hauptnachteil dieser Methode verglichen werden. Er löst das Problem nicht . Bytecode wird aus einer Quelle kompiliert, während andere in source.jar gelangen. Und trotz der Tatsache, dass Delombok von demselben Lombok ausgeführt wird, gibt es immer noch Unterschiede zwischen dem Bytecode und den generierten Quellen, d. H. Sie sind immer noch nicht zum Debuggen geeignet. Um es milde auszudrücken, ich war verärgert, als ich das merkte.


Delombok Plugin + Profil in Maven


Was tun? Ich hatte sources.jar mit den "richtigen" Quellen, aber sie unterschieden sich immer noch vom Bytecode. Im Prinzip könnte das Problem durch Kompilieren aus Quellcodes gelöst werden, die als Ergebnis von Delombok generiert wurden. Das Problem ist jedoch, dass das Maven-Compiler-Plugin den Pfad zur Quelle nicht angeben kann. Es werden immer die im sourceDirectory Projekts angegebenen Quellen verwendet, wie maven-source-plugin B. das maven-source-plugin . Wir könnten das Verzeichnis angeben, in dem delomboke Quellen generiert werden. In diesem Fall wird beim Importieren eines Projekts in die IDE das Verzeichnis mit den realen Quellen nicht als solches betrachtet und die Syntaxhervorhebung und andere Funktionen funktionieren für darin enthaltene Dateien nicht. Diese Option passte auch nicht zu mir.

Sie können Profile verwenden! Erstellen Sie ein Profil, das nur beim Erstellen des Projekts verwendet wird und in dem der Wert von sourceDirectory ersetzt wird! Aber es gibt eine Nuance. Das sourceDirectory Tag kann nur innerhalb des build Tags im Stammverzeichnis des Projekts deklariert werden.

Glücklicherweise gibt es eine Problemumgehung für dieses Problem. Sie können eine Eigenschaft deklarieren, die in das sourceDirectory Tag eingesetzt wird, und den Wert dieser Eigenschaft im Profil ändern!

In diesem Fall sieht die Projektkonfiguration folgendermaßen aus:

pom.xml
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>lombok-sourcesjar</artifactId> <groupId>com.github.monosoul</groupId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <lombok.version>1.18.2</lombok.version> <origSourceDir>${project.basedir}/src/main/java</origSourceDir> <sourceDir>${origSourceDir}</sourceDir> <delombokedSourceDir>${project.build.directory}/delombok</delombokedSourceDir> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies> <profiles> <profile> <id>build</id> <properties> <sourceDir>${delombokedSourceDir}</sourceDir> </properties> </profile> </profiles> <build> <sourceDirectory>${sourceDir}</sourceDirectory> <plugins> <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId> <version>${lombok.version}.0</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>delombok</goal> </goals> </execution> </executions> <configuration> <sourceDirectory>${origSourceDir}</sourceDirectory> <outputDirectory>${delombokedSourceDir}</outputDirectory> <addOutputDirectory>false</addOutputDirectory> <encoding>UTF-8</encoding> <formatPreferences> <generateDelombokComment>skip</generateDelombokComment> <javaLangAsFQN>skip</javaLangAsFQN> </formatPreferences> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.1</version> <configuration> <archive> <manifest> <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> 


Es funktioniert wie folgt. In der origSourceDir Eigenschaft origSourceDir wir den Pfad zum Verzeichnis durch die ursprünglichen Quellen, und in der sourceDir Eigenschaft sourceDir origSourceDir den Wert von origSourceDir . In der Eigenschaft delombokedSourceDir geben wir den Pfad zu den von delombok generierten Quellen an. Daher wird beim Importieren eines Projekts in die IDE das Verzeichnis von origSourceDir Wenn Sie beim build des Projekts das build angeben build wird das Verzeichnis delombokedSourceDir verwendet.

Als Ergebnis erhalten wir einen Bytecode, der aus denselben Quellen kompiliert wurde, die in sources.jar fallen, d. H. Debug wird endlich funktionieren. In diesem Fall müssen wir die Installation und Bereitstellung des Artefakts nicht mit den Quellen konfigurieren, da wir das maven-source-plugin , um das Artefakt zu generieren. Es stimmt, Magie mit Variablen kann eine Person verwirren, die mit den Nuancen von Maven nicht vertraut ist.

Sie können lombok.config die Option lombok.addJavaxGeneratedAnnotation = true in lombok.config . Dann steht die Annotation @javax.annotation.Generated("lombok") über dem generierten Code in den generierten Quellen, um Fragen wie "Warum sieht Ihr Code so seltsam aus?" Zu vermeiden. ! " :) :)


Verwenden Sie gradle


Ich denke, wenn Sie bereits mit Gradle vertraut sind, sollten Sie nicht alle Vorteile gegenüber Maven erläutern. Wenn Sie noch nicht damit vertraut sind, befindet sich ein ganzer Hub auf dem Hub Ein guter Grund, sich damit zu beschäftigen! :) :)
Als ich über die Verwendung von Gradle nachdachte, erwartete ich im Allgemeinen, dass es viel einfacher sein würde, das zu tun, was ich darin brauchte, da ich wusste, dass ich darin leicht darauf hinweisen konnte, woraus source.jar erstellt werden sollte und woraus zu Bytecode kompilieren. Das Problem wartete auf mich, wo ich am wenigsten erwartet hatte - es gibt kein funktionierendes Delombok-Plugin für Gradle.

Es gibt dieses Plugin , aber es scheint, dass Sie keine Optionen zum Formatieren von delomboked Quellen angeben können, die nicht zu mir passten.

Es gibt ein anderes Plugin , das aus den übergebenen Optionen eine Textdatei generiert und diese dann als Argument an lombok.jar übergibt. Ich konnte ihn nicht dazu bringen, die generierten Quellen in das richtige Verzeichnis zu stellen. Dies scheint darauf zurückzuführen zu sein, dass der Pfad in der Textdatei mit den Argumenten nicht in Anführungszeichen steht und nicht ordnungsgemäß maskiert wird. Vielleicht werde ich später eine Pull-Anfrage an den Autor des Plugins mit einem Vorschlag für eine Korrektur senden.

Am Ende habe ich mich für den anderen Weg entschieden und gerade eine Aufgabe beschrieben, bei der Ant aufruft, um Delombok auszuführen. In Lombok gibt es nur eine Ant-Aufgabe dafür , und sie sieht ziemlich gut aus:

build.gradle.kts
 group = "com.github.monosoul" version = "1.0.0" plugins { java } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } dependencies { val lombokDependency = "org.projectlombok:lombok:1.18.2" annotationProcessor(lombokDependency) compileOnly(lombokDependency) } repositories { mavenCentral() } tasks { "jar"(Jar::class) { manifest { attributes( Pair("Main-Class", "com.github.monosoul.lombok.sourcesjar.Main") ) } } val delombok by creating { group = "lombok" val delombokTarget by extra { File(buildDir, "delomboked") } doLast({ ant.withGroovyBuilder { "taskdef"( "name" to "delombok", "classname" to "lombok.delombok.ant.Tasks\$Delombok", "classpath" to sourceSets.main.get().compileClasspath.asPath) "mkdir"("dir" to delombokTarget) "delombok"( "verbose" to false, "encoding" to "UTF-8", "to" to delombokTarget, "from" to sourceSets.main.get().java.srcDirs.first().absolutePath ) { "format"("value" to "generateDelombokComment:skip") "format"("value" to "generated:generate") "format"("value" to "javaLangAsFQN:skip") } } }) } register<Jar>("sourcesJar") { dependsOn(delombok) val delombokTarget: File by delombok.extra from(delombokTarget) archiveClassifier.set("sources") } withType(JavaCompile::class) { dependsOn(delombok) val delombokTarget: File by delombok.extra source = fileTree(delombokTarget) } } 


Infolgedessen entspricht diese Option der vorherigen.


Schlussfolgerungen


Eine ziemlich triviale Aufgabe stellte sich in der Tat als eine Reihe von Versuchen heraus, Problemumgehungen für seltsame Entscheidungen von Maven-Autoren zu finden. Was mich betrifft - das Gradle-Skript sieht vor dem Hintergrund der resultierenden Maven-Konfigurationen viel offensichtlicher und logischer aus. Aber vielleicht konnte ich einfach keine bessere Lösung finden? Teilen Sie uns in den Kommentaren mit, ob und wie Sie ein ähnliches Problem gelöst haben.

Danke fürs Lesen!

Quellcode

Source: https://habr.com/ru/post/de438548/


All Articles