Project lombok автоматизирует написание одного и того же кода и позволяет сосредоточиться на задаче и при этом писать более простой и понятный код. С другой стороны, первый постулат Пардо напоминает: «Все, что есть хорошего в жизни, либо незаконно, либо аморально, либо ведет к ожирению». Какова цена удобства, которое привносит lombok?
В первую очередь я бы назвал недостатком lombok саму концепцию: использование аннотаций для генерации кода. Аннотации задумывались как средство задания свойства кода, которые могут изменять поведение кода, но не сам код. Project lombok использует их для генерации кода, что идеологически сомнительно.
Из этого недостатка вытекают другие — вся экосистема java не готова к project lombok. Расчёт покрытия unit тестами будет давать неверные оценки. Анализатор PMD будет ругаться на недоступные поля. FindBugs будет ругаться на некорректное исползьование методов (хотя с недавних пор это отключаемо). AspectJ вообще не способен собрать проект с lombok.
Что же делать? Есть ли возможность писать простой код с project lombok и при этом минимизировать ущерб от него? Авторы lombok называют эту возможность разломбочиванием «delomboking».
Проще всего, если эта операция не требуется часто, разломбочить вручную:
1 | java -jar lombok delombok src -d src-delomboked |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * Sample account entity. */ @Data(staticConstructor = "create") public class Account { /** * Id. */ @NonNull Long id; /** * Account owner. */ @NonNull User owner; /** * Account's value. */ BigDecimal amount; } |
Разломбоченный код настолько большой, что пришлось спрятать его.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | // Generated by delombok at Mon Nov 09 13:30:44 EET 2015 package ru.easyjava.java.accounts; import lombok.NonNull; import ru.easyjava.java.users.User; import java.math.BigDecimal; /** * Sample account entity. */ public class Account { /** * Id. */ @NonNull Long id; /** * Account owner. */ @NonNull User owner; /** * Account's value. */ BigDecimal amount; @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") private Account(@NonNull final Long id, @NonNull final User owner) { if (id == null) { throw new java.lang.NullPointerException("id"); } if (owner == null) { throw new java.lang.NullPointerException("owner"); } this.id = id; this.owner = owner; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public static Account create(@NonNull final Long id, @NonNull final User owner) { return new Account(id, owner); } /** * Id. */ @NonNull @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public Long getId() { return this.id; } /** * Account owner. */ @NonNull @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public User getOwner() { return this.owner; } /** * Account's value. */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public BigDecimal getAmount() { return this.amount; } /** * Id. */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public void setId(@NonNull final Long id) { if (id == null) { throw new java.lang.NullPointerException("id"); } this.id = id; } /** * Account owner. */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public void setOwner(@NonNull final User owner) { if (owner == null) { throw new java.lang.NullPointerException("owner"); } this.owner = owner; } /** * Account's value. */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public void setAmount(final BigDecimal amount) { this.amount = amount; } @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public boolean equals(final java.lang.Object o) { if (o == this) return true; if (!(o instanceof Account)) return false; final Account other = (Account)o; if (!other.canEqual((java.lang.Object)this)) return false; final java.lang.Object this$id = this.getId(); final java.lang.Object other$id = other.getId(); if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false; final java.lang.Object this$owner = this.getOwner(); final java.lang.Object other$owner = other.getOwner(); if (this$owner == null ? other$owner != null : !this$owner.equals(other$owner)) return false; final java.lang.Object this$amount = this.getAmount(); final java.lang.Object other$amount = other.getAmount(); if (this$amount == null ? other$amount != null : !this$amount.equals(other$amount)) return false; return true; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") protected boolean canEqual(final java.lang.Object other) { return other instanceof Account; } @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public int hashCode() { final int PRIME = 59; int result = 1; final java.lang.Object $id = this.getId(); result = result * PRIME + ($id == null ? 43 : $id.hashCode()); final java.lang.Object $owner = this.getOwner(); result = result * PRIME + ($owner == null ? 43 : $owner.hashCode()); final java.lang.Object $amount = this.getAmount(); result = result * PRIME + ($amount == null ? 43 : $amount.hashCode()); return result; } @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public java.lang.String toString() { return "Account(id=" + this.getId() + ", owner=" + this.getOwner() + ", amount=" + this.getAmount() + ")"; } } |
Maven
Для maven существует особый плагин, интегрирующий разломбочивание в сборочный ковейер.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId> <version>1.16.6.1</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>delombok</goal> </goals> </execution> </executions> </plugin> |
Плагин ожидает, что весь код, требующий разломбочивания, будет лежать в src/main/lombok, а разломбоченный код будет помещён в target/generated-sources/delombok, откуда его уже подхватит компилятор и скомпилирует. Код, лежащий в src/main/java, будет скомпилирован как обычно.
Gradle
В gradle поддержка project lombok так же осуществляется отдельным плагином:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | apply plugin: 'java' buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "gradle.plugin.net.franz-becker:gradle-lombok:1.5" } } apply plugin: "net.franz-becker.gradle-lombok" sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.3' } /** * Code below only required, if you need delomboking. */ import net.franz_becker.gradle.lombok.task.DelombokTask task delombok(type: DelombokTask) { args("src/main/lombok", "-d", "build/src/main/java") } task delombokHelp(type: DelombokTask) { args "--help" } gradle.projectsEvaluated { compileJava.dependsOn(delombok) } sourceSets { main { java { srcDir 'build/src/main/java' } } } |
Поскольку в Gradle из коробки нет scope provided, как в maven, наличие плагина обязательно для корректной компиляции кода с lombok. Разломбочивание реализуется отдельным gradle task и требует дополнительной настройки (просто скопируйте task из примера :)).
Код примера доступен на github.