--- title: Lombokの@Builderの代わりにIntelliJ IDEAのBuilder生成機能を使う tags: ["Java", "Lombok", "Jackson", "IntelliJ IDEA"] categories: ["Programming", "Java", "com", "fasterxml", "jackson"] date: 2019-05-25T02:01:19Z updated: 2019-05-25T02:01:19Z --- 昔はLombokをよく使っていたけれど、最近はもう使いません。 生成されるコードに見えないアノテーションが付いていて知らないと嵌ったり、バージョンが上がると生成されるコードが変わって嵌ったり、IDEでビルドするのにLombokプラグインが必要だったりで副作用による悪い点が自分の中では無視できなくなったためです。`lombok.config`とか嫌。 Getter/Setterは生成する前に、本当に必要かどうか一度考え、必要であればIDEの機能で生成するようにしています。IntelliJ IDEAなら`Command + N` => "Getter and Setter"で生成できます。 Lombokで便利だったのは`@Builder`アノテーションでBuilderを作れることでしたが、これも[IntelliJ IDEAの機能](https://www.jetbrains.com/help/idea/2019.1/replace-constructor-with-builder.html)で生成できます。 ### Replace Constructor with Builder 次のクラスを例に説明します。(Immutableだし、コンストラクタは`public`である必要はありません!) ``` java 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](https://user-images.githubusercontent.com/106908/58362846-034da780-7ed7-11e9-944c-ab456c253cc3.png) "Replace Constructor with Builder"を選択。 ![image](https://user-images.githubusercontent.com/106908/58362851-0cd70f80-7ed7-11e9-9a63-bf21734bd53e.png) デフォルトでは`new XyzBuilder().setFoo(foo).setBar(bar).createXyz()`というビルダーが生成されるのですが、個人的には最近はSetterよりWitherを使うことが多いので、setter prefixを`set`から`with`に変えます。 ![image](https://user-images.githubusercontent.com/106908/58362866-545d9b80-7ed7-11e9-87f8-b958b35bad7b.png) ![image](https://user-images.githubusercontent.com/106908/58362871-63dce480-7ed7-11e9-928b-1972df7e729e.png) Witherになっていることを確認したら"Refactor"ボタンを押してビルダーを生成します。 ![image](https://user-images.githubusercontent.com/106908/58362874-6b03f280-7ed7-11e9-978a-049ca5249e48.png) 次のようなコードが生成されます。 ```java 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; } } ``` 使い方は次の通り。 ```java 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です。 ```java 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`を変更しておきます。 ```java 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を使ってデシリアライズできます。 ```java @PostMapping(path = "/entries") public Entry post(@RequestBody Entry entry) { // ... } ``` --- 脱Lombok。