概要
Hello World をブラウザで表示できることを確認できたので、 HTML 文書をブラウザで表示できるようにします。 HTML を表示する際には静的な HTML としてではなく、 動的に生成した HTML を表示するようにするため、 テンプレートエンジンを使用します。 テンプレートエンジンには Thymeleaf (タイムリーフ) を使います。
確認環境
- IntelliJ IDEA COMMUNITY 2016.2
- Spring Boot 1.4.0
参考情報
Thymeleaf の使い方。日本語。
Thymeleaf と Spring の連携。英語。
手順
- build.gradle に
org.springframework.boot:spring-boot-starter-thymeleaf
を追加 - テンプレートを作成
- Controller を作成
- ブラウザで確認
Gradle の依存設定に Thymeleaf を追加する。下記は build.gradle ファイルの一部。
dependencies { ... compile('org.springframework.boot:spring-boot-starter-thymeleaf') ... }
テンプレートを作成する。形式は HTML であり拡張子は html として作成する。 今回は、user-request-list.html を src/main/resources/templates/ に以下の内容で作成した。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>機能一覧</title> </head> <body> <table> <thead> <tr> <th>ID</th> <th>機能概要</th> <th>人気</th> </tr> </thead> <tbody> <tr> <td>FIX_1</td> <td>A機能の不具合を修正して欲しい</td> <td>50</td> </tr> <tr> <td>NEW_1</td> <td>B機能を追加して欲しい</td> <td>50</td> </tr> </tbody> </table> </body> </html>
Controller を作成する。 @Controller アノテーションを付与したクラスと @RequestMapping を付与したメソッドを作成する。 今回は、UserRequestController.kt を src/main/kotlin/…/web に以下の内容で作成した。
package org.anyspirit.webapp.ask.request.web import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.RequestMapping @Controller class UserRequestController { @RequestMapping("/user-request-list") fun showList(): String = "user-request-list" }
@RequestMapping の /user-request-list
は URL の http://localhost:8080/user-request-list
と対応する。
showList メソッドの戻り値の user-request-list
はテンプレートの user-request-list.html
と対応する。
ブラウザで http://localhost:8080/user-request-list
を開くとエラーとなる。
ブラウザに表示されたエラーの内容は Exception parsing document: template=“user-request-list”, line 6 - column 3 となっており、
Terminal を確認すると下記のエラーが表示されている。
2016-08-29 17:30:13.343 ERROR 7100 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: Exception parsing document: template="user-request-list", line 6 - column 3] with root cause org.xml.sax.SAXParseException: The element type "meta" must be terminated by the matching end-tag "</meta>".
meta タグを閉じなければならないとのこと。/
を追加して閉じタグがないことを明示したところ、エラーは表示されずに HTML が表示された。
<meta charset="UTF-8" />
補足
meta タグを閉じなければならないことから、設定が XHTML になっていることを疑った。
適用されている auto-configuration を見るには debug スイッチを付与して起動するとある (Spring Boot Reference Guide)。
bootRun に --debug
を付与して下記のように起動すると Gradle の DEBUG メッセージは表示されるが、
アプリケーションの DEBUG メッセージは表示されない。
$ ./gradlew --debug bootRun
アプリケーションの DEBUG メッセージを表示するには、build.gradle に下記を追加する方法がある。
bootRun {
jvmArgs = ['-Ddebug']
}
欲しい情報はテンプレートの設定であり、これらの DEBUG メッセージには見当たらない。 テンプートの設定は Bean (Spring が生成を管理するオブジェクト) で行われると Tutorial: Thymeleaf + Spring に記載されている。 Bean の情報を取得してテンプレートのデフォルト設定を確認する。
Bean の情報を取得するために、main 関数に処理を加える。 SPRING INITIALIZER で作成したプロジェクトでは、ルートパッケージに XXXApplication.kt が作成され、main 関数が定義される。 下記のように記述することで Bean 名の一覧を取得できる。
fun main(args: Array<String>) { val ctx = SpringApplication.run(AskRequestApplication::class.java, *args) ctx.beanDefinitionNames.sorted().map { println(it) } }
bootRun で起動すると Terminal に Bean 名の一覧が表示される。 template に関する記述を探すと defaultTemplateResolver が見つかる。
defaultTemplateResolver の詳細を確認する。 main 関数に下記の処理を加えて Terminal の表示を確認すると、 SpringResourceTemplateResolver クラスのインスタンスが取得されている。
fun main(args: Array<String>) { val ctx = SpringApplication.run(AskRequestApplication::class.java, *args) ... val bean = ctx.getBean("defaultTemplateResolver") println(bean.toString()) }
クラスを特定できたので、インスタンスが保持している情報をメソッドで取り出す。 main 関数に下記の処理を加えて Terminal の表示を確認する。
fun main(args: Array<String>) { val ctx = SpringApplication.run(AskRequestApplication::class.java, *args) ... val bean = ctx.getBean("defaultTemplateResolver") as SpringResourceTemplateResolver bean.initialize() println(bean.prefix) println(bean.suffix) println(bean.templateMode) println(bean.isCacheable) }
defaultTemplateResolver の設定は下記の通り。
templateMode は HTML5 であり XHTML ではない。 Tutorial: Using Thymeleaf (ja) によると、 HTML5 モードは整形式 XML と書かれている。 もし、整形式 XML を使いたくなければ LEGACYHTML5 モードを使用する。
LEGACYHTML5 モードに設定を変更したい場合には、templateResolver の Bean 設定を行う。 Bean の設定は、@Configuration を付与したクラスで行う。 Tutorial: Thymeleaf + Spring では XML で記載されているが Spring Boot の慣習に則りアノテーションを使う。 SPRING INITIALIZER で作成したプロジェクトでは、ルートパッケージに XXXApplication.kt が作成され、@SpringBootApplication が付与されている。 @SpringBootApplication は @Configuration, @EnableAutoConfiguration, @ComponentScan をまとめて付与する。 つまり、XXXApplication.kt で Bean の設定を行えば良いということ。
下記は templateResolver の Bean 設定。
@SpringBootApplication open class AskRequestApplication { @Bean open fun templateResolver() = SpringResourceTemplateResolver().apply { prefix = "classpath:/templates/" suffix = ".html" templateMode = "LEGACYHTML5" isCacheable = false // TODO: make it true before release } }
ブラウザで確認をすると下記のようなエラーが表示される。
Cannot perform conversion to XML from legacy HTML: The nekoHTML library is not in classpath. nekoHTML 1.9.15 or newer is required for processing templates in “LEGACYHTML5” mode http://nekohtml.sourceforge.net. Maven spec: “net.sourceforge.nekohtml::nekohtml::1.9.15”. IMPORTANT: DO NOT use versions of nekoHTML older than 1.9.15.
LEGACYHTML5 を使うには nekoHTML が必要とのことなので build.gradle に下記を追加。
dependencies { ... compile('net.sourceforge.nekohtml:nekohtml:1.9.15') // for LEGACY HTML5 template ... }
以上の設定を加えることで閉じタグを省略できるようになる。
Kotlin 補足
下記の Kotlin コードと Java コードは同じ。
open fun templateResolver() = SpringResourceTemplateResolver().apply { prefix = "classpath:/templates/" suffix = ".html" templateMode = "LEGACYHTML5" isCacheable = false // TODO: make it true before release }
public SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); resolver.setPrefix("classpath:/templates/"); resolver.setSuffix(".html"); resolver.setTemplateMode("LEGACYHTML5"); resolver.setIsCacheable(false); // TODO: make it true before release return resolver; }