Lombokはとても便利ですよね。Javaで必要だけどあまり書きたくないGetterやSetter、toStringメソッドなどを自動的に生成してくれるので、私は積極的に利用しています。ただ、使っていく過程ではまることもあるのですが、最初の頃にはまった内容をまとめます。
LombokでStackOverflow
Lombokを使用していると、処理中に例外が発生した場合など下記のような循環参照でStackOverflowとなることがあります。
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at net.beaglesoft.girafa.domain.YayoiShohizeiKubun.toString(YayoiShohizeiKubun.java:23) ~[classes/:na] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at net.beaglesoft.girafa.domain.YayoiKaikeiShohizeiKubunConf.toString(YayoiKaikeiShohizeiKubunConf.java:21) ~[classes/:na] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60] at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:527) ~[hibernate-core-4.3.11.Final.jar:4.3.11.Final] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] ... at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[na:1.8.0_60] 2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed 2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.823 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 2015-11-13 20:10:44.824 DEBUG [http-nio-8080-exec-1] o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 2015-11-13 20:10:44.825 ERROR [http-nio-8080-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler processing failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null at java.lang.Integer.toString(Integer.java:400) ~[na:1.8.0_60] at java.sql.Timestamp.toString(Timestamp.java:312) ~[na:1.8.0_60] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at net.beaglesoft.girafa.domain.user.User.toString(User.java:64) ~[classes/:na] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] at java.lang.StringBuilder.append(StringBuilder.java:131) ~[na:1.8.0_60] at net.beaglesoft.girafa.domain.YayoiShohizeiKubun.toString(YayoiShohizeiKubun.java:23) ~[classes/:na] at java.lang.String.valueOf(String.java:2994) ~[na:1.8.0_60] ...
発生する原因
これは、オブジェクトの循環が原因で発生します。理屈としては以下のケースで発生します。
- 例外が発生時に例外メッセージでtoStringメソッドが呼ばれたときに、該当するオブジェクトに循環したオブジェクトが存在する。
- LombokでtoStringメソッドを自動生成している。
で、具体的にはというと以下のようなケースが該当します。
まず、UserクラスにはGroupという関連が定義されています。
@Data @NoArgsConstructor @Entity @Table(name = "users") @JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"}) public class User implements Serializable { private Group group; }
つぎに、GroupクラスにはUserクラスの参照を更新ユーザーとして定義しています。
@Data @NoArgsConstructor @Entity @Table(name = "users") @JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"}) public class Group implements Serializable { ... private User updateUser; }
このときUserクラスで例外が発生するとします。例外の中ではtoStringメソッドが呼ばれることもありますが、今回は呼ばれるとします。
UserクラスのtoStringメソッドを実行すると、Lombokが生成したtoStringメソッドではGroupクラスのtoStringメソッドが実行され流ことになります。
すると、GroupクラスのtoStringメソッド内で定義されているupdateUser変数がtoStringメソッドで呼ばれます。このときにはさらにUserクラスのtoStringメソッドを実行することになり次にUserクラスの中にあるGroupクラスのtoStringメソッドが実行される…というように循環するわけです。
解決方法
さて、この循環参照を避けるための方法です。循環の絶てば良いのでその方法を確認すると、以下の通りLombokに関するアノテーションを循環元のクラス(Userクラス)に設定すればOKです。
@Data @ToString(exclude = {"group"}) // ←ここでtoStringから除外するフィールドを指定する。 @NoArgsConstructor @Entity @Table(name = "users") @JsonIgnoreProperties(value = {"handler", "hibernateLazyInitializer"}) public class User implements Serializable { ... private Group group; }
これで例外が発生したときもStackOverflowにならないので安心です。もっとも、別な方法として自分でtoStringメソッドを実装するという方法もあります。こちらでは、循環させないようにすれば問題が起こることはありません。
SpringBootを始めるならこの書籍がおすすめです。というか、この書籍しかありません…。
はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槇俊明
- 出版社/メーカー: 工学社
- 発売日: 2014/11
- メディア: 単行本
- この商品を含むブログ (7件) を見る
JPAについて知りたいならこの書籍がおすすめです。
Pro JPA 2 (Expert's Voice in Java)
- 作者: Mike Keith,Merrick Schincariol
- 出版社/メーカー: Apress
- 発売日: 2013/09/26
- メディア: ペーパーバック
- この商品を含むブログを見る