freemarker使用自定义的模板加载器通过redis加载模板

其实更实用的是使用数据库中的数据,不过redis相对简单一些。结合了一些网上的资料:

CSDN上的通过数据库获取模板,这里了解了如何自定义TemplateLoader地址

通过FreeMarkerConfigurer 配置freemarker,这里主要是了解如何在SpringBoot中注册自定义的TemplateLoader地址

分四个部分:

  • 用redis提供模板的存储功能:RedisTemplateStorage
  • 自定义的RedisTempleLoader用redis存储适配freemarkerTemplateLoader
  • 配置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

有几个关键:

  1. findTemplateSource每次被调用来查询模板,返回的Object后续会被传入getLastModifiedgetReader判断时间戳和获取实际模板信息
  2. freemarker每次获取模板时,都会先调用findTemplateSource,然后调用getLastModified来判断是否要重新编译模板,所以最好实现getLastModified方法
  3. 覆写父类的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();
    }
}
posted @ 2020-03-31 22:37  mosakashaka  阅读(743)  评论(0编辑  收藏  举报