[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ではリストまたは配列の添字処理を行う時にプリプロセッシングと呼ばれる処理を行わないといけなかった。


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

```html: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では添字する時にはプリプロセッシングを行わないといけない。という決まりごとがあるので他の人が同じような事象に陥らない。

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

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