Help us understand the problem. What is going on with this article?

[SpringBoot+Thymeleaf] 負荷をかけた時だけコンパイルに失敗してハマった話

環境

  • Spring Boot 2.1.6.RELEASE
  • Thymeleaf 3.0.4.RELEASE

何が起きたのか?

ローカルで開発をしている時、または開発環境などの少人数で画面を操作している時には問題なく動作していた画面が突然コンパイルに失敗したというエラーが出力されるようになった。

画面のリロードを一定回数行った後、再現することが分かったが謎すぎる。
しかも、リロードする度にエラーが発生する行が変わる。
暫定的な解消方法としては再起動すれば直る。(マジで気味が悪い・・・)

これが俗に言う動いていたものが動かなくなったというものか・・・と半日もハマってた。

エラー内容

Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "xxxList[j].element" (template: "test/xxx.html" - line 111, col 51)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
    at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
    at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
    at org.thymeleaf.standard.expression.LinkExpression.resolveParameters(LinkExpression.java:337)
    at org.thymeleaf.standard.expression.LinkExpression.executeLinkExpression(LinkExpression.java:283)
    at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:85)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
    at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
    at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
    at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
    at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.Model.process(Model.java:290)
    at org.thymeleaf.engine.IteratedGatheringModelProcessable.processIterationModel(IteratedGatheringModelProcessable.java:367)
    at org.thymeleaf.engine.IteratedGatheringModelProcessable.process(IteratedGatheringModelProcessable.java:221)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleCloseElement(ProcessorTemplateHandler.java:1640)
    at org.thymeleaf.engine.CloseElementTag.beHandled(CloseElementTag.java:139)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1587)
    at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1587)
    at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.Model.process(Model.java:290)
    at org.thymeleaf.engine.GatheringModelProcessable.process(GatheringModelProcessable.java:78)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleCloseElement(ProcessorTemplateHandler.java:1640)
    at org.thymeleaf.engine.CloseElementTag.beHandled(CloseElementTag.java:139)
    at org.thymeleaf.engine.TemplateModel.process(TemplateModel.java:136)
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:592)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072)
    at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:362)
    at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:189)
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1371)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1117)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1056)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    ... 97 common frames omitted
Caused by: java.lang.IllegalStateException: Failed to instantiate CompiledExpression
    at org.springframework.expression.spel.standard.SpelCompiler.compile(SpelCompiler.java:111)
    at org.springframework.expression.spel.standard.SpelExpression.compileExpression(SpelExpression.java:511)
    at org.springframework.expression.spel.standard.SpelExpression.checkCompile(SpelExpression.java:487)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:329)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:263)
    ... 140 common frames omitted
Caused by: java.lang.VerifyError: (class: spel/Ex170, method: getValue signature: (Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;) Expecting to find integer on stack
    at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3138)
    at java.base/java.lang.Class.getConstructor0(Class.java:3343)
    at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2554)
    at org.springframework.util.ReflectionUtils.accessibleConstructor(ReflectionUtils.java:190)
    at org.springframework.expression.spel.standard.SpelCompiler.compile(SpelCompiler.java:108)
    ... 144 common frames omitted

結論

結論から言うとThymeleafではリストまたは配列の添字処理を行う時にプリプロセッシングと呼ばれる処理を行わないといけなかった。

NG
<div th:each="i : ${#numbers.sequence(0, xxxList.size()-1)}">
  <div th:text="${xxxList[i].element}"></div>
</div>
OK
<div th:each="i : ${#numbers.sequence(0, xxxList.size()-1)}">
  <div th:text="${xxxList[__${i}__].element}"></div>
</div>

${xxxList[${i}]}の式は本来xxxList[0], xxxList[1], xxxList[2]と評価されないといけないが、xxxList[i], xxxList[i], xxxList[i]のようにiが変数(数字)と評価される前に文字列として評価されてしまっていた。

これを解消するためには評価順番を指定する必要があり${xxxList[__${i}__]}のように変数の左右に"__"を使うことで先に評価する式を明示的に指定することができる。

今回の場合は負荷をかけることで評価する順番が変わってしまったため、リロードして負荷をあげた瞬間にコンパイルに失敗した、という訳だ。

[参考]
https://macchinetta.github.io/server-guideline-thymeleaf/current/ja/ImplementationAtEachLayer/ApplicationLayer.html#view-thymeleaf-preprocessing-label

最後に

Thmeleafでは添字する時にはプリプロセッシングを行わないといけない。という決まりごとがあるので他の人が同じような事象に陥らない。

故に無視したらどのような挙動になるのかの記事が全くなく、かなりの時間を費やしてしまったがいい勉強になった。

この事象に悩んでいる人の役に立っていれば幸いです!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした