IK.AM

@making's tech note


Lombokの@Builderの代わりにIntelliJ IDEAのBuilder生成機能を使う

🗃 {Programming/Java/com/fasterxml/jackson}
🏷 Java 🏷 Lombok 🏷 Jackson 🏷 IntelliJ IDEA 
🗓 Updated at 2019-05-25T02:01:19Z  🗓 Created at 2019-05-25T02:01:19Z   🌎 English Page

昔は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"を選択。

image

"Replace Constructor with Builder"を選択。

image

デフォルトではnew XyzBuilder().setFoo(foo).setBar(bar).createXyz()というビルダーが生成されるのですが、個人的には最近はSetterよりWitherを使うことが多いので、setter prefixをsetからwithに変えます。

image

image

Witherになっていることを確認したら"Refactor"ボタンを押してビルダーを生成します。

image

次のようなコードが生成されます。

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。


✒️️ Edit  ⏰ History  🗑 Delete