Lombok修飾したNestedな内部クラスを使ってビルド失敗した時の解決法
最近ハマったlombokでビルド失敗するケースの解決法。
どうも内部クラスの扱いには若干問題があるらしいので、メモっとく。
問題の概要
Lombok使ったコード書いた→Mavenビルドした→なんかコンパイル失敗するんだけど…
出力されるのは下記のようなエラーメッセージ。
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.0:compile (default-compile) on project lombok-test: Compilation failure: Compilation failure: [ERROR] /C:/hogehoge/lombok-test/src/main/java/com/sample/lombok_test/NestedInner.java:[20,10] シンボルを見つけられません [ERROR] シンボル: クラス Data [ERROR] 場所: クラス com.sample.lombok_test.NestedInner [ERROR] /C:/hogehoge/workspace/lombok-test/src/main/java/com/sample/lombok_test/NestedInner.java:[21,10] シンボルを見つけられません [ERROR] シンボル: クラス Builder [ERROR] 場所: クラス com.sample.lombok_test.NestedInner
いやいやDataとBuilder見つからないってことは無いでしょ、lombokちゃんと引き込んでるしというのが第1印象。
ちなみにLombokインストールしたEclipseでコード書いてる間は怒られず、mavenビルドなんかを実行したタイミングで問題が発覚する。
※ Eclipseが怒ってくれないのはECJと純正コンパイラの処理の違いだろうか。
問題が発生するサンプルコード
下記のようにLombok修飾された内部クラスをネストして、同クラス内でLombok生成された処理にもアクセスしたコードを書いた場合に問題が発生した。
package com.sample.lombok_test; import com.sample.lombok_test.NestedInner.Nested1.Nested2; import lombok.Builder; import lombok.Data; public class NestedInner { public static Nested1 createNestedObj() { return Nested1.builder() .name("Nested1Name") .nested(Nested2.builder() .name("Nested2Name") .build()) .build(); } @Data @Builder public static class Nested1 { private String name; private Nested2 nested; @Data @Builder public static class Nested2 { private String name; } } }
StackOverflowあたり見ると、Nestedなクラスに対してはLombokは正常処理しないケースが潜在しているみたい。
java - Javac fails to compile annotations on static nested classes that have a public enum - Stack Overflow
importing nested static method breaks compilation · Issue #1249 · rzwitserloot/lombok · GitHub
import云々のコメントが多くimport com.sample.lombok_test.NestedInner.Nested1.Nested2;
するようなケースがアカンのかな。
公式でもバグ報告としてIssueがいくつか上がっていたり、「lombok cannot fix this.」と言われてたり状況がつかみにくい。
最初ググったときは「maven-compiler-pluginのバージョンあげろ」とか「lombokのバージョン下げなくちゃダメじゃね?」とか出てきたけど、結局問題点が別のところにあって解決まで2,3時間くらい彷徨う羽目になった…
解決方法
1. 内部クラスを外に切り出す
内部クラスを抱えてアクセスもしているのが問題で、Lombok修飾されたクラス定義を外部に切り出せばあっさり解決する。
こんな感じでデータを持つクラスを外出しするのが解決法の一つ。
package com.sample.lombok_test; import lombok.Builder; import lombok.Data; @Data @Builder public class Nested1 { private String name; private Nested2 nested; @Data @Builder public static class Nested2 { private String name; } }
2. 内部クラスをネストしない
内部クラスのネストを解消するとimport文に内部クラスが登場しなくなる。試してみた限りこれでも解決できた。
直接の内部クラスはimport不要だが、内部クラスの内部クラスはダメだってことか。
package com.sample.lombok_test; import lombok.Builder; import lombok.Data; public class NestedInner { public static Nested1 createNestedObj() { return Nested1.builder() .name("Nested1Name") .nested(Nested2.builder() .name("Nested2Name") .build()) .build(); } @Data @Builder public static class Nested1 { private String name; private Nested2 nested; } @Data @Builder public static class Nested2 { private String name; } }
規約化などの雑感
同じような問題引かないようにするために、コーディングルールをどう定めるべきか。
- 上記のような問題点があることを表明し「この問題を引かないようにコーディングすべし」というルールにする(やり方は各自考えられるよね系)。
- Lombok修飾した内部クラスをネストする場合は1段までという規約にする(内部クラスの内部クラスにしない)。
- Lombok修飾したクラスは専用のデータ定義クラスに切り出すことを必須とする(Lombok修飾したクラスを処理が書かれた場所で内部クラスにしない)。
- 内部クラスをネストする場合は1段までという規約にする。
- 内部クラスを作るなという規約にする。
プロジェクトの状況、担当者のレベル、人数の規模感などを考慮してどれかを規約化しておく形かな。
上から順に規約読んだ人が理解したり考えたりすることが多く、下に行くほど考えることは単純で盲目的に従う系の規約(おまじないとも言う)にしてみた。
個人的に一内部クラス使った方がわかりやすくなるケースも一部あると思ってるので、一律禁止はやりすぎだと思う。
データ定義クラスとして必ず切り出してね、あたりが現実的か。