freemarker使用自定义的模板加载器通过redis加载模板
其实更实用的是使用数据库中的数据,不过redis相对简单一些。结合了一些网上的资料:
CSDN上的通过数据库获取模板,这里了解了如何自定义TemplateLoader
:地址
通过FreeMarkerConfigurer
配置freemarker
,这里主要是了解如何在SpringBoot
中注册自定义的TemplateLoader
:地址
分四个部分:
- 用redis提供模板的存储功能:
RedisTemplateStorage
- 自定义的
RedisTempleLoader
用redis存储适配freemarker
的TemplateLoader
- 配置freemarker使用自定义
RedisTempleLoader
- 使用模板
存储部分
这里的方法被后面的RedisTempleLoader
使用来存储和查询模板信息
StringRedisTemplate
是利用的Jedis
。
@Component
public class RedisTemplateStorage {
@Autowired
StringRedisTemplate stringRedisTemplate;
private final String FTL_REDIS_PREFIX = "ftl-redis-ftl:";
private final String FTL_REDIS_KEY_PREFIX = FTL_REDIS_PREFIX + "key:";
private final String FTL_REDIS_TS_PREFIX = FTL_REDIS_PREFIX + "ts:";
/*
* 新增/修改模板
*/
public String saveTemplate(String name, String content) {
stringRedisTemplate.opsForValue().set(getRedisKey(name), content);
updateTimestamp(name);
return content;
}
/*
* 获取模板的原始内容
*/
public String getTemplate(String name) {
return stringRedisTemplate.opsForValue().get(getRedisKey(name));
}
/*
* 查询所有已经添加的模板名称
*/
public String getAllTemplateNames() {
Set<String> keys = stringRedisTemplate.keys(FTL_REDIS_KEY_PREFIX + "*");
Set<String> trimmedKeys = new HashSet<String>();
for (String k : keys) {
trimmedKeys.add(k.substring(FTL_REDIS_KEY_PREFIX.length()));
}
return JSON.toJSONString(trimmedKeys);
}
/*
* 删除一个模板
*/
public Boolean deleteTemplate(String name) {
stringRedisTemplate.delete(getRedisKey(name));
stringRedisTemplate.delete(getTimestampRedisKey(name));
return true;
}
/*
* 获取模板最后修改的时间
*/
public Long getTemplateTimestamp(String name) {
String ts = stringRedisTemplate.opsForValue().get(getTimestampRedisKey(name));
return Long.parseLong(ts);
}
/*
* 更新模板最后修改时间
*/
private void updateTimestamp(String name) {
stringRedisTemplate.opsForValue().set(getTimestampRedisKey(name), new Date().getTime() + "");
}
/*
* 删除模板最后更新时间
*/
private void deleteTimestamp(String name) {
stringRedisTemplate.delete(getTimestampRedisKey(name));
}
/*
* 根据模板名称生成一个redis的key
*/
private String getRedisKey(String name) {
return FTL_REDIS_KEY_PREFIX + name;
}
/*
* 根据模板名称生成一个模板时间戳的key
*/
private String getTimestampRedisKey(String name) {
return FTL_REDIS_TS_PREFIX + name;
}
}
适配TemplateLoader
有几个关键:
findTemplateSource
每次被调用来查询模板,返回的Object后续会被传入getLastModified
和getReader
判断时间戳和获取实际模板信息- freemarker每次获取模板时,都会先调用
findTemplateSource
,然后调用getLastModified
来判断是否要重新编译模板,所以最好实现getLastModified
方法 - 覆写父类的
toString
,可以让freemarker报错时信息更友好
@Component("redisTemplateLoader")
@Slf4j
public class RedisTemplateLoader implements TemplateLoader {
@Autowired
private RedisTemplateStorage storage;
@Override
public Object findTemplateSource(String name) {
try {
String tpl = storage.getTemplate(name);
log.info("Template name:[{}], content:[{}]", name, tpl);
if (StringUtils.isEmpty(tpl)) {
return null;
}
Long ts = storage.getTemplateTimestamp(name);
log.info("Template name:[{}], content:[{}], ts:[{}]", name, tpl, ts);
return new StringTemplateSource(name, tpl, ts);
} catch (Exception e) {
log.error("Failed to get template [{}]", name, e);
return null;
}
}
@Override
public long getLastModified(Object templateSource) {
return ((StringTemplateSource) templateSource).lastModified;
}
@Override
public Reader getReader(Object templateSource, String encoding) {
return new StringReader(((StringTemplateSource) templateSource).source);
}
@Override
public void closeTemplateSource(Object templateSource) {
// do nothing
}
private static class StringTemplateSource {
private final String name;
private final String source;
private final long lastModified;
StringTemplateSource(String name, String source, long lastModified) {
if (name == null) {
throw new IllegalArgumentException("name == null");
}
if (source == null) {
throw new IllegalArgumentException("source == null");
}
if (lastModified < -1L) {
throw new IllegalArgumentException("lastModified < -1L");
}
this.name = name;
this.source = source;
this.lastModified = lastModified;
}
public boolean equals(Object obj) {
if (obj instanceof StringTemplateSource) {
return name.equals(((StringTemplateSource) obj).name);
}
return false;
}
public int hashCode() {
return name.hashCode();
}
}
@Override
public String toString() {
return "RedisTemplateLoader";
}
}
配置freemarker
这里需要使用FreeMarkerConfigurer
类来配置,我这里调用的setPreTemplateLoaders
来设置我自定义的模板加载器实现。
@Configuration
@Slf4j
public class FreemarkerConfig {
@Autowired
RedisTemplateLoader loader;
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
log.info("Custom Freemarker configurer pre.");
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setPreTemplateLoaders(loader);
freeMarkerConfigurer.setDefaultEncoding("UTF-8"); // Default encoding of the template files
return freeMarkerConfigurer;
}
}
用来输出格式化后信息的工具类
@Component
@Slf4j
public class TemplateUtil {
@Autowired
freemarker.template.Configuration cfg;
public String format(String templateName, Map<String, Object> param)
throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException,
TemplateException {
Template tpl = cfg.getTemplate(templateName);
Writer out = new StringWriter();
tpl.process(param, out);
return out.toString();
}
}