昔はLombokをよく使っていたけれど、最近はもう使いません。
生成されるコードに見えないアノテーションが付いていて知らないと嵌ったり、バージョンが上がると生成されるコードが変わって嵌ったり、IDEでビルドするのにLombokプラグインが必要だったりで副作用による悪い点が自分の中では無視できなくなったためです。lombok.config
とか嫌。
Getter/Setterは生成する前に、本当に必要かどうか一度考え、必要であればIDEの機能で生成するようにしています。IntelliJ IDEAならCommand + N
=> "Getter and Setter"で生成できます。
Lombokで便利だったのは@Builder
アノテーションでBuilderを作れることでしたが、これもIntelliJ IDEAの機能で生成できます。
Replace Constructor with Builder
次のクラスを例に説明します。(Immutableだし、コンストラクタはpublic
である必要はありません!)
package com.example.demo;
import java.time.Instant;
public class Entry {
private final Long entryId;
private final String content;
private final String author;
private final Instant createdAt;
private final Instant updatedAt;
Entry(Long entryId, String content, String author, Instant createdAt, Instant updatedAt) {
this.entryId = entryId;
this.content = content;
this.author = author;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getEntryId() {
return entryId;
}
public String getContent() {
return content;
}
public String getAuthor() {
return author;
}
public Instant getCreatedAt() {
return createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
}
コンストラクタで右クリックして、"Refeactor"を選択。
"Replace Constructor with Builder"を選択。
デフォルトではnew XyzBuilder().setFoo(foo).setBar(bar).createXyz()
というビルダーが生成されるのですが、個人的には最近はSetterよりWitherを使うことが多いので、setter prefixをset
からwith
に変えます。
Witherになっていることを確認したら"Refactor"ボタンを押してビルダーを生成します。
次のようなコードが生成されます。
package com.example.demo;
import java.time.Instant;
public class EntryBuilder {
private String author;
private String content;
private Instant createdAt;
private Long entryId;
private Instant updatedAt;
public Entry createEntry() {
return new Entry(entryId, content, author, createdAt, updatedAt);
}
public EntryBuilder withAuthor(String author) {
this.author = author;
return this;
}
public EntryBuilder withContent(String content) {
this.content = content;
return this;
}
public EntryBuilder withCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
return this;
}
public EntryBuilder withEntryId(Long entryId) {
this.entryId = entryId;
return this;
}
public EntryBuilder withUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
return this;
}
}
使い方は次の通り。
Entry entry = new EntryBuilder()
.withEntryId(1L)
.withAuthor("making")
.withContent("Hello")
.withCreatedAt(Instant.now())
.createEntry();
Jackson連携
特にImmutableなクラスを作るとき、Jacksonデシリアライズが問題になります。Lombokだと@ConstructorProperties
付けてくれるから設定不要だったような気がしますが、もう覚えていません。
Lombokを使わないならコンストラクタに@JsonCreator
+@JsonProperty
を使うのが一般的ですが、
上記の方法でBuilderを作った場合、もうちょっとスマートに書けます。
Builderによる生成対象クラスに@JsonDeserialize
アノテーションでBuilderクラスを指定し、Builder側には@JsonPOJOBuilder
を指定すればOKです。
package com.example.demo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.time.Instant;
@JsonDeserialize(builder = EntryBuilder.class)
public class Entry {
private final Long entryId;
private final String content;
private final String author;
private final Instant createdAt;
private final Instant updatedAt;
Entry(Long entryId, String content, String author, Instant createdAt, Instant updatedAt) {
this.entryId = entryId;
this.content = content;
this.author = author;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getEntryId() {
return entryId;
}
public String getContent() {
return content;
}
public String getAuthor() {
return author;
}
public Instant getCreatedAt() {
return createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
}
@JsonPOJOBuilder
はデフォルトでWitherを使って値を設定するので、Setterで生成した場合はwithPrefix = "set"
を指定する必要があります。buildMethodName
のデフォルト値はbuild
ですので、生成されたBuilderクラスを修正しても良いですが、ここではIntelliJ IDEAの慣習に合わせて、buildMethodName
を変更しておきます。
package com.example.demo;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.time.Instant;
@JsonPOJOBuilder(buildMethodName = "createEntry")
public class EntryBuilder {
private String author;
private String content;
private Instant createdAt;
private Long entryId;
private Instant updatedAt;
public Entry createEntry() {
return new Entry(entryId, content, author, createdAt, updatedAt);
}
public EntryBuilder withAuthor(String author) {
this.author = author;
return this;
}
public EntryBuilder withContent(String content) {
this.content = content;
return this;
}
public EntryBuilder withCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
return this;
}
public EntryBuilder withEntryId(Long entryId) {
this.entryId = entryId;
return this;
}
public EntryBuilder withUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
return this;
}
}
これで、JsonリクエストボディをBuilderを使ってデシリアライズできます。
@PostMapping(path = "/entries")
public Entry post(@RequestBody Entry entry) {
// ...
}
脱Lombok。