Lombok, sources.jar dan debug nyaman

Di tim kami, kami benar-benar mencintai Lombok . Ini memungkinkan Anda untuk menulis lebih sedikit kode dan lebih sedikit refactor, yang ideal untuk pengembang yang malas. Tetapi jika, selain artefak proyek, Anda mempublikasikan kode sumber dengan dokumentasi juga, maka Anda dapat mengalami masalah - kode sumber tidak akan bertepatan dengan bytecode. Saya akan berbicara tentang bagaimana kami menyelesaikan masalah ini dan kesulitan apa yang kami temui dalam proses dalam posting ini.



Omong-omong, jika Anda menulis di Jawa dan karena alasan tertentu masih tidak menggunakan Lombok dalam proyek Anda, maka saya sarankan Anda membaca artikel tentang Habrรฉ ( sekali dan dua kali ). Saya yakin Anda akan menyukainya!

Masalah


Proyek yang kami kerjakan terdiri dari beberapa modul. Beberapa dari mereka (sebut saja backend bersyarat) dikemas dalam arsip (pengiriman) ketika rilis dirilis, diunggah ke repositori dan kemudian digunakan untuk server aplikasi. Bagian lainnya adalah yang disebut modul klien - diterbitkan ke repositori sebagai satu set artefak, termasuk sources.jar dan javadoc.jar. Kami menggunakan Lombok di semua bagian, dan Maven akan melakukan semuanya.

Beberapa waktu yang lalu, salah satu konsumen layanan kami datang dengan masalah - ia mencoba men-debug modul kami, tetapi tidak dapat melakukannya, karena sources.jar tidak memiliki metode (dan bahkan kelas) di mana ia ingin mengatur breakpoint. Kami di tim kami percaya bahwa upaya mengidentifikasi dan memecahkan masalah secara mandiri, alih-alih membuat cacat adalah tindakan suami yang layak yang perlu didorong! :-) Oleh karena itu, diputuskan untuk membawa sources.jar sesuai dengan bytecode.

Contoh


Mari kita bayangkan bahwa kita memiliki aplikasi sederhana yang terdiri dari dua kelas:

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


Dan aplikasi kami dibangun menggunakan Maven:

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> 


Jika Anda mengkompilasi proyek ini ( mvn compile ), dan kemudian mendekompilasi bytecode yang dihasilkan, kelas SomePojo akan terlihat seperti ini:

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 + ")"; } } } 


Sangat berbeda dari apa yang masuk ke sumber kami. Jar, bukan? ;) Seperti yang Anda lihat, jika Anda menghubungkan kode sumber untuk debug SomePojo dan ingin menetapkan breakpoint, misalnya, di konstruktor, maka Anda akan mengalami masalah - tidak ada tempat untuk mengatur breakpoint, dan kelas SomePojoBuilder tidak ada sama sekali.

Apa yang harus dilakukan?


Seperti yang sering terjadi, masalah ini memiliki beberapa solusi. Mari kita lihat masing-masing.

Jangan gunakan Lombok


Ketika kami pertama kali menghadapi masalah ini, kami berbicara tentang modul di mana hanya ada beberapa kelas menggunakan Lombok. Tentu saja, saya tidak mau menolaknya, jadi saya langsung berpikir untuk melakukan delombok. Setelah menyelidiki pertanyaan ini, saya menemukan beberapa solusi aneh menggunakan plugin Maven Lombok - lombok-maven-plugin . Dalam salah satu dari mereka disarankan, misalnya, untuk menjaga sumber-sumber di mana Lombok digunakan dalam direktori terpisah yang delombok akan dieksekusi, dan sumber yang dihasilkan akan pergi ke sumber yang dihasilkan, dari mana ia akan dikompilasi dan pergi ke sources.jar. Opsi ini mungkin berfungsi, tetapi dalam hal ini penyorotan sintaks pada sumber asli tidak akan berfungsi di IDE, seperti direktori dengan mereka tidak akan dianggap sebagai direktori sumber. Opsi ini tidak cocok untuk saya, dan karena biaya meninggalkan Lombok dalam modul ini rendah, diputuskan untuk tidak membuang waktu untuk hal ini, menonaktifkan Lombok dan hanya menghasilkan metode yang diperlukan melalui IDE.

Secara umum, menurut saya opsi ini memiliki hak untuk hidup, tetapi hanya jika kelas yang menggunakan Lombok benar-benar kecil dan mereka jarang berubah.


Delombok plugin + sources.jar build menggunakan Ant


Setelah beberapa waktu, saya harus kembali ke masalah ini lagi, ketika itu sudah menjadi pertanyaan tentang modul di mana Lombok digunakan jauh lebih intensif. Kembali lagi ke studi tentang masalah ini, saya menemukan pertanyaan tentang stackoverflow , yang menyarankan mengeksekusi delombok untuk sumber, dan kemudian menggunakan sumber di Ant menghasilkan sources.jar.
Di sini Anda perlu ngelantur tentang mengapa Anda perlu menghasilkan sources.jar menggunakan Ant, daripada menggunakan plug-in Sumber ( maven-source-plugin ). Faktanya adalah Anda tidak dapat mengonfigurasi direktori sumber untuk plugin ini. Itu akan selalu menggunakan konten properti sourceDirectory proyek.

Jadi, dalam contoh kita, pom.xml akan terlihat seperti ini:

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> 


Seperti yang Anda lihat, konfigurasi telah berkembang sangat banyak, dan itu jauh dari hanya lombok-maven-plugin dan maven-antrun-plugin . Mengapa ini terjadi? Faktanya adalah karena kita sekarang mengumpulkan sumber-sumber. Bergabung dengan Ant, Maven tidak tahu apa-apa tentang artefak ini. Dan kita harus memberi tahu dia dengan jelas bagaimana cara menginstal artefak ini, bagaimana cara menyebarkannya dan bagaimana mengemas sumber daya ke dalamnya.

Selain itu, saya menemukan bahwa ketika menjalankan delombok secara default, Lombok menambahkan komentar ke header file yang dihasilkan. Dalam hal ini, format file yang dihasilkan dikendalikan tidak menggunakan opsi di file lombok.config , tetapi menggunakan opsi plugin. Daftar opsi ini tidak mudah ditemukan. Tentu saja mungkin untuk memanggil nama panggilan toples Lombok dengan delombok dan --help keys, tapi saya terlalu malas untuk programmer ini , jadi saya menemukannya di kode sumber di github .

Tetapi volume konfigurasi, maupun fiturnya tidak dapat dibandingkan dengan kelemahan utama dari metode ini. Dia tidak memecahkan masalah . Bytecode dikompilasi dari satu sumber, sementara yang lain masuk ke sources.jar. Dan terlepas dari kenyataan bahwa delombok dieksekusi oleh Lombok yang sama, masih akan ada perbedaan antara bytecode dan sumber yang dihasilkan, yaitu mereka masih tidak cocok untuk debug. Singkatnya, saya kesal ketika menyadari ini.


Plugin + profil Delombok dalam maven


Jadi apa yang harus dilakukan? Saya memiliki sources.jar dengan sumber yang "benar", tetapi mereka masih berbeda dari bytecode. Pada prinsipnya, masalah dapat diselesaikan dengan kompilasi dari kode sumber yang dihasilkan sebagai hasil dari delombok. Tetapi masalahnya adalah bahwa maven-compiler-plugin 'tidak dapat menentukan path ke sumbernya. Itu selalu menggunakan sumber yang ditentukan dalam sourceDirectory proyek, seperti maven-source-plugin . Mungkin saja untuk menunjukkan di sana direktori tempat sumber delomboked dihasilkan, tetapi dalam kasus ini, ketika mengimpor proyek ke IDE, direktori dengan sumber nyata tidak akan dianggap seperti itu dan penyorotan sintaks dan fitur lainnya tidak akan berfungsi untuk file di dalamnya. Pilihan ini juga tidak cocok untukku.

Anda dapat menggunakan profil! Buat profil yang hanya akan digunakan ketika membangun proyek dan di mana nilai sourceDirectory akan diganti! Namun ada nuansa. Tag sourceDirectory hanya dapat dideklarasikan di dalam tag build di root proyek.

Untungnya, ada solusi untuk masalah ini. Anda dapat mendeklarasikan properti yang akan diganti menjadi tag sourceDirectory , dan mengubah nilai properti ini di profil!

Dalam hal ini, konfigurasi proyek akan terlihat seperti ini:

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> 


Ini berfungsi sebagai berikut. Di properti origSourceDir kami mengganti jalur ke direktori dengan sumber asli, dan di properti sourceDir mengganti nilai dari origSourceDir . Di properti delombokedSourceDir kami menentukan jalur ke sumber yang dihasilkan oleh delombok. Jadi, ketika mengimpor proyek ke IDE, direktori dari origSourceDir , dan ketika membangun proyek, jika Anda menentukan profil build , direktori delombokedSourceDir akan digunakan.

Akibatnya, kita mendapatkan bytecode yang dikompilasi dari sumber yang sama yang akan jatuh ke sources.jar, yaitu akhirnya debug akan bekerja. Dalam hal ini, kita tidak perlu mengkonfigurasi instalasi dan penyebaran artefak dengan sumber, karena kita menggunakan maven-source-plugin untuk menghasilkan artefak. Benar, sihir dengan variabel dapat membingungkan seseorang yang tidak terbiasa dengan nuansa Maven.

Anda lombok.config dapat menambahkan opsi lombok.addJavaxGeneratedAnnotation = true di lombok.config , lalu anotasi @javax.annotation.Generated("lombok") akan berdiri di atas kode yang dihasilkan di sumber yang dihasilkan, yang akan membantu menghindari pertanyaan seperti "Mengapa kode Anda terlihat sangat aneh? ! " :)


Gunakan gradle


Saya pikir jika Anda sudah terbiasa dengan Gradle , maka Anda seharusnya tidak menjelaskan semua kelebihannya atas Maven. Jika Anda belum terbiasa dengan itu, maka ada hub seluruh pada hub Alasan yang bagus untuk melihatnya! :)
Secara umum, ketika saya berpikir tentang menggunakan Gradle, saya berharap akan lebih mudah untuk melakukan apa yang saya butuhkan di dalamnya, karena saya tahu bahwa di dalamnya saya dapat dengan mudah menunjukkan apa yang akan membangun sumber. kompilasi ke bytecode. Masalahnya menunggu saya di tempat yang saya harapkan paling tidak - tidak ada plugin delombok yang berfungsi untuk Gradle.

Ada plugin ini , tetapi tampaknya Anda tidak dapat menentukan opsi untuk memformat sumber yang dihapus di dalamnya, yang tidak cocok untuk saya.

Ada plugin lain , itu menghasilkan file teks dari opsi yang diteruskan ke sana, dan kemudian meneruskannya sebagai argumen ke lombok.jar. Saya tidak bisa membuatnya menempatkan sumber yang dihasilkan di direktori yang benar, tampaknya ini disebabkan oleh kenyataan bahwa path dalam file teks dengan argumen tidak dikutip dan tidak lolos dengan benar. Mungkin nanti saya akan melakukan permintaan tarik ke pembuat plugin dengan saran untuk memperbaikinya.

Pada akhirnya, saya memutuskan untuk pergi ke arah lain dan hanya menggambarkan tugas dengan panggilan Ant untuk mengeksekusi delombok, di Lombok hanya ada tugas Ant untuk ini , dan itu terlihat cukup bagus:

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


Akibatnya, opsi ini setara dengan yang sebelumnya.


Kesimpulan


Sebenarnya tugas yang sepele, ternyata merupakan serangkaian upaya untuk menemukan solusi di sekitar keputusan aneh oleh penulis Maven. Bagi saya - skrip Gradle, dengan latar belakang konfigurasi Maven yang dihasilkan, terlihat jauh lebih jelas dan logis. Namun, mungkin saya tidak bisa menemukan solusi yang lebih baik? Beri tahu kami di komentar jika Anda memecahkan masalah yang sama, dan jika demikian, caranya.

Terima kasih sudah membaca!

Kode sumber

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


All Articles