Chaining Thymeleaf template resolvers12 Aug 2022 - Tobias Erdle
To chain template resolvers in Thymeleaf, you need to set the order via
AbstractTemplateResolver#setOrder for each template resolver and check for existence with
AbstractTemplateResolver#setCheckExistence in the leading resolvers to allow a fallthrough to following ones, handling the not resolvable template. Then add them to the
Today I tried to fix an issue in the Krazo Thymeleaf Extension which drives my crazy everytime I use
this extension, namely that using fragments forces me to add the whole path of the template located in
/WEB-INF/views (or wherever the Jakarta MVC application is configured
to search for views). So I started to debug the issue using this easy templates where
index.html is the view and
fragment.html contains a simple HTML fragment to include.
<!-- index.html --> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Thymeleaf default view folder test</title> </head> <body> <div th:replace="fragment :: test"></div> </body> </html>
<!-- fragment.html --> <div th:fragment="test" id="from-fragment">I'm from the fragment</div>
The currently used template engine was produced with a single
org.thymeleaf.templateresolver.WebApplicationTemplateResolver using its default config. Now I started to debug
and recognized, that the view returned by the controller could be resolved because it contained the full path when fetched from the
jakarta.mvc.engine.ViewEngineContext. On the
other hand, the fragment was only resolved as
/fragment which surely can't be found in this location when it is really located in
/WEB-INF/views/. So I tried a few things until I came to a positive result.
Using prefixes / suffixes in default template resolver
My first approach was to use the
setSuffix method of
WebApplicationTemplateResolver to set the required values. My expectation was, that the resolver recognizes
/WEB-INF/views/index.html has the correct format and does nothing, whereas
fragments does not and needs them applied. Unfortunately, with this approach the template resolver added prefix and suffix even to the
/WEB-INF/views/index.html so the path got wrong and not resolvable. After this failure I tried another approach, testing if a second template resolver may help.
Using a second template resolver
So after I couldn't implement my desired behavior with a single template resolver, I added a second one which got configured like this:
final String mvcViewPath = //... final WebApplicationTemplateResolver secondResolver = new WebApplicationTemplateResolver(servletApplication); secondResolver.setPrefix(mvcViewPath); secondResolver.setSuffix(FILE_SUFFIX); secondResolver.setTemplateMode(TemplateMode.HTML); secondResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); secondResolver.setOrder(2); //...
Here I set the views directory as a prefix and
.html as suffix, so
fragment is, assumed that
mvcViewPath evaluates to
/WEB-INF/views, transformed to
/WEB-INF/views/fragment.html. Also I set the order of the template resolver to ensure, that the default one runs first, which got the order
1. Afterwards I added the initialized engine also to the
TemplateEngine#addTemplateResolver and tried again. Unfortunately, it failed again.
The solution: additionally use
I checked the available methods a few times until I came upon
setCheckExistence which contains this interesting sentence in its JavaDoc:
This allows resolvers to pass control to the next ITemplateResolver in the chain
So what I simply missed is to set this flag at the first template resolver, so in case the original
fragment template is not found, the name will be checked in the next
template resolver which adds the required prefix and suffix and will be able to find the template.
Attention: I just used two template resolvers here. In case you have more of them, all but the last one need this flag set to