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.javapackage com.github.monosoul.lombok.sourcesjar; import lombok.Builder; import lombok.Value; @Value @Builder(toBuilder = true) class SomePojo {  String someStringField;  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