Home > Uncategorized > Using Spring Cache as the Thymeleaf Cache

Using Spring Cache as the Thymeleaf Cache

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:

  1. package com.integralblue.candrews;
  2.  
  3. import java.util.List;
  4. import java.util.Properties;
  5.  
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.cache.Cache;
  9. import org.springframework.cache.CacheManager;
  10. import org.springframework.context.MessageSource;
  11. import org.springframework.context.annotation.Bean;
  12. import org.springframework.context.annotation.Configuration;
  13. import org.thymeleaf.Template;
  14. import org.thymeleaf.cache.AbstractCacheManager;
  15. import org.thymeleaf.cache.ICache;
  16. import org.thymeleaf.cache.ICacheEntryValidityChecker;
  17. import org.thymeleaf.cache.ICacheManager;
  18. import org.thymeleaf.dom.Node;
  19. import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
  20. import org.thymeleaf.spring4.SpringTemplateEngine;
  21. import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
  22. import org.thymeleaf.spring4.view.ThymeleafViewResolver;
  23. import org.thymeleaf.templateresolver.ITemplateResolver;
  24. import org.thymeleaf.templateresolver.TemplateResolver;
  25.  
  26. @Configuration
  27. public class ThymeleafConfig {
  28. 	private static final String VIEWS = "/WEB-INF/views/";
  29. 	private static final String TEMPLATE_CACHE_NAME = "thymeleafTemplateCache";
  30. 	private static final String FRAGMENT_CACHE_NAME = "thymeleafFragmentCache";
  31. 	private static final String MESSAGE_CACHE_NAME = "thymeleafMessageCache";
  32. 	private static final String EXPRESSION_CACHE_NAME = "thymeleafExpressionCache";
  33.  
  34. 	@Value("${thymeleaf.cache}")
  35. 	private boolean thymeleafCache;
  36.  
  37. 	@Autowired
  38. 	private CacheManager cacheManager;
  39.  
  40. 	@Autowired
  41. 	private MessageSource messageSource;
  42.  
  43. 	@Bean
  44. 	public ThymeleafViewResolver viewResolver() {
  45. 		ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
  46. 		viewResolver.setCache(thymeleafCache);
  47. 		viewResolver.setTemplateEngine(templateEngine());
  48. 		viewResolver.setCharacterEncoding("UTF-8");
  49. 		return viewResolver;
  50. 	}
  51.  
  52. 	@Bean
  53. 	public SpringTemplateEngine templateEngine() {
  54. 		SpringTemplateEngine templateEngine = new SpringTemplateEngine();
  55. 		templateEngine.setCacheManager(thymeleafCacheManager());
  56. 		templateEngine.setTemplateResolver(templateResolver());
  57. 		templateEngine.addDialect(new SpringSecurityDialect());
  58. 		templateEngine.setMessageSource(messageSource);
  59. 		return templateEngine;
  60. 	}
  61.  
  62. 	@Bean
  63. 	public ITemplateResolver templateResolver() {
  64. 		TemplateResolver templateResolver = new SpringResourceTemplateResolver();
  65. 		templateResolver.setPrefix(VIEWS);
  66. 		templateResolver.setSuffix(".html");
  67. 		templateResolver.setTemplateMode("HTML5");
  68. 		templateResolver.setCacheable(thymeleafCache);
  69. 		return templateResolver;
  70. 	}
  71.  
  72. 	@Bean
  73. 	ICacheManager thymeleafCacheManager() {
  74. 		return new AbstractCacheManager() {
  75.  
  76. 			@Override
  77. 			protected ICache<String, Template> initializeTemplateCache() {
  78. 				return templateCache();
  79. 			}
  80.  
  81. 			@Override
  82. 			protected ICache<String, List<Node>> initializeFragmentCache() {
  83. 				return fragmentCache();
  84. 			}
  85.  
  86. 			@Override
  87. 			protected ICache<String, Properties> initializeMessageCache() {
  88. 				return messageCache();
  89. 			}
  90.  
  91. 			@Override
  92. 			protected ICache<String, Object> initializeExpressionCache() {
  93. 				return expressionCache();
  94. 			}
  95. 		};
  96. 	}
  97.  
  98. 	@Bean
  99. 	ICache<String, Template> templateCache() {
  100. 		return new CacheToICacheAdapter<Template>(
  101. 				cacheManager.getCache(TEMPLATE_CACHE_NAME));
  102. 	}
  103.  
  104. 	@Bean
  105. 	ICache<String, List<Node>> fragmentCache() {
  106. 		return new CacheToICacheAdapter<List<Node>>(
  107. 				cacheManager.getCache(FRAGMENT_CACHE_NAME));
  108. 	}
  109.  
  110. 	@Bean
  111. 	ICache<String, Properties> messageCache() {
  112. 		return new CacheToICacheAdapter<Properties>(
  113. 				cacheManager.getCache(MESSAGE_CACHE_NAME));
  114. 	}
  115.  
  116. 	@Bean
  117. 	ICache<String, Object> expressionCache() {
  118. 		return new CacheToICacheAdapter<Object>(
  119. 				cacheManager.getCache(EXPRESSION_CACHE_NAME));
  120. 	}
  121.  
  122. 	private static final class CacheToICacheAdapter<V> implements
  123. 			ICache<String, V> {
  124. 		private final Cache cache;
  125.  
  126. 		private static final class CacheEntry<V> {
  127. 			private final V value;
  128. 			private final long timestamp;
  129.  
  130. 			public CacheEntry(V value) {
  131. 				this.value = value;
  132. 				this.timestamp = System.currentTimeMillis();
  133. 			}
  134.  
  135. 			public V getValue() {
  136. 				return value;
  137. 			}
  138.  
  139. 			public long getTimestamp() {
  140. 				return timestamp;
  141. 			}
  142. 		}
  143.  
  144. 		public CacheToICacheAdapter(Cache cache) {
  145. 			this.cache = cache;
  146. 		}
  147.  
  148. 		@Override
  149. 		public void put(String key, V value) {
  150. 			cache.put(key, new CacheEntry<V>(value));
  151. 		}
  152.  
  153. 		@Override
  154. 		public V get(String key) {
  155. 			@SuppressWarnings("unchecked")
  156. 			CacheEntry<V> cacheEntry = cache.get(key, CacheEntry.class);
  157. 			if (cacheEntry == null) {
  158. 				return null;
  159. 			} else {
  160. 				return cacheEntry.getValue();
  161. 			}
  162. 		}
  163.  
  164. 		@Override
  165. 		public V get(
  166. 				String key,
  167. 				ICacheEntryValidityChecker<? super String, ? super V> validityChecker) {
  168. 			@SuppressWarnings("unchecked")
  169. 			CacheEntry<V> cacheEntry = cache.get(key, CacheEntry.class);
  170. 			if (cacheEntry == null) {
  171. 				return null;
  172. 			} else {
  173. 				V value = cacheEntry.getValue();
  174. 				if (validityChecker == null
  175. 						|| validityChecker.checkIsValueStillValid(key, value,
  176. 								cacheEntry.getTimestamp())) {
  177. 					return value;
  178. 				} else {
  179. 					return null;
  180. 				}
  181. 			}
  182. 		}
  183.  
  184. 		@Override
  185. 		public void clear() {
  186. 			cache.clear();
  187. 		}
  188.  
  189. 		@Override
  190. 		public void clearKey(String key) {
  191. 			cache.evict(key);
  192. 		}
  193.  
  194. 	}
  195. }

I’ve requested that Thymeleaf include this feature as part of the Spring integration in Thymeleaf 3.0: https://github.com/thymeleaf/thymeleaf-spring/issues/89 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 “Issues using Ehcache with ARC as the Thymeleaf Cache” for more information.

CC BY-SA 4.0 Using Spring Cache as the Thymeleaf Cache by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Categories: Uncategorized Tags: