Thymeleaf includes caching that can be used to cache templates, fragments, messages, and expressions. The default implementation is an in-memory cache using standard Java collections. Wouldn’t it be nice to use the Spring Cache Abstraction instead? The advantages of such a setup include:
- Consistency – why have many separate caching implementations each configured in different ways when you can have one?
- Abstraction – Spring Cache Abstraction can be easily configured to use any number of implementations, such as ehcache, Guava, JDK collections, or any JSR-107 provider
- All the advantages of the implementation chosen – For example, if your cahce provider supports it, you get statistics, memory usage information, and more
To connect Thymeleaf (I’m using 2.1.x, but this should work for any version 2.1 or later) to Spring (I’m using 4.1.x, but this approach should work for 4.0 or later).
Here’s a working configuration:
package com.integralblue.candrews;
import java.util.List;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.Template;
import org.thymeleaf.cache.AbstractCacheManager;
import org.thymeleaf.cache.ICache;
import org.thymeleaf.cache.ICacheEntryValidityChecker;
import org.thymeleaf.cache.ICacheManager;
import org.thymeleaf.dom.Node;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
@Configuration
public class ThymeleafConfig {
private static final String VIEWS = "/WEB-INF/views/";
private static final String TEMPLATE_CACHE_NAME = "thymeleafTemplateCache";
private static final String FRAGMENT_CACHE_NAME = "thymeleafFragmentCache";
private static final String MESSAGE_CACHE_NAME = "thymeleafMessageCache";
private static final String EXPRESSION_CACHE_NAME = "thymeleafExpressionCache";
@Value("${thymeleaf.cache}")
private boolean thymeleafCache;
@Autowired
private CacheManager cacheManager;
@Autowired
private MessageSource messageSource;
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCache(thymeleafCache);
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setCacheManager(thymeleafCacheManager());
templateEngine.setTemplateResolver(templateResolver());
templateEngine.addDialect(new SpringSecurityDialect());
templateEngine.setMessageSource(messageSource);
return templateEngine;
}
@Bean
public ITemplateResolver templateResolver() {
TemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix(VIEWS);
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCacheable(thymeleafCache);
return templateResolver;
}
@Bean
ICacheManager thymeleafCacheManager() {
return new AbstractCacheManager() {
@Override
protected ICache<string, template=""> initializeTemplateCache() {
return templateCache();
}
@Override
protected ICache<string, list<node="">> initializeFragmentCache() {
return fragmentCache();
}
@Override
protected ICache<string, properties=""> initializeMessageCache() {
return messageCache();
}
@Override
protected ICache<string, object=""> initializeExpressionCache() {
return expressionCache();
}
};
}
@Bean
ICache<string, template=""> templateCache() {
return new CacheToICacheAdapter<template>(
cacheManager.getCache(TEMPLATE_CACHE_NAME));
}
@Bean
ICache<string, list<node="">> fragmentCache() {
return new CacheToICacheAdapter <list<node>>(
cacheManager.getCache(FRAGMENT_CACHE_NAME));
}
@Bean
ICache<string, properties=""> messageCache() {
return new CacheToICacheAdapter<properties>(
cacheManager.getCache(MESSAGE_CACHE_NAME));
}
@Bean
ICache<string, object=""> expressionCache() {
return new CacheToICacheAdapter<object>(
cacheManager.getCache(EXPRESSION_CACHE_NAME));
}
private static final class CacheToICacheAdapter<v> implements
ICache<string, v=""> {
private final Cache cache;
private static final class CacheEntry<v> {
private final V value;
private final long timestamp;
public CacheEntry(V value) {
this.value = value;
this.timestamp = System.currentTimeMillis();
}
public V getValue() {
return value;
}
public long getTimestamp() {
return timestamp;
}
}
public CacheToICacheAdapter(Cache cache) {
this.cache = cache;
}
@Override
public void put(String key, V value) {
cache.put(key, new CacheEntry<v>(value));
}
@Override
public V get(String key) {
@SuppressWarnings("unchecked")
CacheEntry<v> cacheEntry = cache.get(key, CacheEntry.class);
if (cacheEntry == null) {
return null;
} else {
return cacheEntry.getValue();
}
}
@Override
public V get(
String key,
ICacheEntryValidityChecker<!--? super String, ? super V--> validityChecker) {
@SuppressWarnings("unchecked")
CacheEntry<v> cacheEntry = cache.get(key, CacheEntry.class);
if (cacheEntry == null) {
return null;
} else {
V value = cacheEntry.getValue();
if (validityChecker == null
|| validityChecker.checkIsValueStillValid(key, value,
cacheEntry.getTimestamp())) {
return value;
} else {
return null;
}
}
}
@Override
public void clear() {
cache.clear();
}
@Override
public void clearKey(String key) {
cache.evict(key);
}
}
}
I've requested that Thymeleaf include this feature as part of the Spring integration in Thymeleaf 3.0: <a href="https://github.com/thymeleaf/thymeleaf-spring/issues/89">https://github.com/thymeleaf/thymeleaf-spring/issues/89</a> So hopefully, with future versions of Thymeleaf, none of this extra work will be necessary and things will Just Work™.
If using Ehcache as the backing cache for Thymeleaf, and ARC is being used, errors may occur - see <a href="http://candrews.integralblue.com/2015/07/issues-using-ehcache-with-arc-as-the-thymeleaf-cache/">"Issues using Ehcache with ARC as the Thymeleaf Cache"</a> for more information.
</v></v></v></v></string,></v></object></string,></properties></string,></list<node></string,></template></string,></string,></string,></string,></string,>