Thymeleaf
설치
dependencies {
implementation('net.benelog.spidersilk:spider-silk-thymeleaf:1.1.0') {
exclude group: 'gg.jte' // core's jte, unused here
}
}
모듈은 thymeleaf 코어에서 서블릿 API를 떼어 낸 계열인 Thymeleaf 3.1.x에 의존하므로, 여기서 두 번째 서블릿 수준을 클래스패스로 끌어오는 것은 없다.
jte 제외는 선택 사항이다. 쓰지 않는 엔진을 클래스패스에서 치울 뿐이다.
렌더링
new App()
.templates(new ThymeleafTemplates("thymeleaf"))
.get("/decks/{deckId}", req ->
WebResponse.template("deck", Map.of("deck", service.deck(req.pathParamLong("deckId")))));
이는 `classpath:/thymeleaf/deck.html`을 렌더링하며, UTF-8 HTML로 파싱하고 파싱된 템플릿은 캐시한다. 모델의 키가 템플릿이 읽는 변수이므로, `${deck.title}`은 `Map.of("deck", …)`에 닿는다.
`th:text`는 이스케이프하고 `th:utext`는 하지 않는데, 이는 Thymeleaf 자체의 기본값이며 여기서 그것을 위해 설정할 것이 없는 이유다.
모델인 것과 모델이 아닌 것
이는 Thymeleaf의 서블릿 없는 절반이다: 템플릿 이름, 모델, 렌더링 결과를 쓸 라이터다.
WebContext`가 없으므로, `#request, #session, `@bean`처럼 Spring MVC 페이지가 손을 뻗는 표현식은 쓸 수 없다.
핸들러가 `Map`에 넣은 것이 페이지가 읽을 수 있는 것이며, 그 밖에는 없다.
직접 정하는 루트나 접미사
app.templates(new ThymeleafTemplates("thymeleaf") // classpath:/thymeleaf
.suffix(".th.html")); // .../deck.th.html
접미사는 덧붙일 뿐 검사하지 않으므로, 확장자를 아직 달고 있는 이름은 접미사가 두 번 붙은 채로 조회된다.
리졸버 자체의 접미사는 비워 두므로, 작동하는 것은 suffix 하나뿐이다.
locale(Locale)`은 `#{…} 메시지와 `#numbers`가 어떤 로케일을 기준으로 형식을 만들지 설정한다.
기본값은 JVM의 것인데, `TemplateRenderer`는 요청이 아니라 모델을 건네받기 때문이다.
다이얼렉트와 리졸버
다른 생성자는 설정된 `TemplateEngine`을 받으며, 파일 시스템 리졸버, 메시지 소스, 다이얼렉트는 여기에 들어간다:
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("thymeleaf/");
resolver.setSuffix(""); // ThymeleafTemplates appends its own
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false); // reload while developing
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
app.templates(new ThymeleafTemplates(engine));
위처럼 리졸버의 접미사를 비워 두지 않으면, `ThymeleafTemplates`가 이미 덧붙인 접미사 위에 `.html`을 또 덧붙인다.
리플렉션
Thymeleaf는 `${deck.title}`을 OGNL로 평가하는데, 이는 리플렉션을 쓴다. 그것은 코어가 아니라 모듈의 리플렉션이다. 규칙이 적용되는 범위는 "리플렉션 없음"의 범위를 참고한다.