从数据库加载 FreeMarker 模板
Posted
技术标签:
【中文标题】从数据库加载 FreeMarker 模板【英文标题】:Load FreeMarker templates from database 【发布时间】:2010-09-26 07:43:15 【问题描述】:我想将我的 FreeMarker 模板存储在类似于以下内容的数据库表中:
template_name | template_content
---------------------------------
hello |Hello $user
goodbye |So long $user
当收到对具有特定名称的模板的请求时,这应该会导致执行查询,该查询会加载相关的模板内容。然后,应将此模板内容与数据模型(上述示例中的“用户”变量的值)一起传递给 FreeMarker。
但是,FreeMarker API 似乎假定每个模板名称对应于文件系统特定目录中的同名文件。有什么方法可以轻松地从数据库而不是文件系统加载我的模板?
编辑:我应该提到我希望能够在应用程序运行时将模板添加到数据库中,所以我不能简单地在启动时将所有模板加载到新的 StringTemplateLoader (如下所示)。
【问题讨论】:
【参考方案1】:我们使用 StringTemplateLoader 加载从数据库中获取的模板(如 Dan Vinton 建议的那样)
这是一个例子:
StringTemplateLoader stringLoader = new StringTemplateLoader();
String firstTemplate = "firstTemplate";
stringLoader.putTemplate(firstTemplate, freemarkerTemplate);
// It's possible to add more than one template (they might include each other)
// String secondTemplate = "<#include \"greetTemplate\"><@greet/> World!";
// stringLoader.putTemplate("greetTemplate", secondTemplate);
Configuration cfg = new Configuration();
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate(firstTemplate);
编辑 您不必在启动时加载所有模板。每当我们访问模板时,我们都会从数据库中获取它并通过 StringLoader 加载它,并通过调用 template.process() 我们生成(在我们的例子中)XML 输出。
【讨论】:
【参考方案2】:几种方法:
创建TemplateLoader 的新实现以直接从数据库加载模板,并在加载任何模板之前使用setTemplateLoader()
将其传递给您的Configuration 实例。
使用您在应用程序启动时从数据库中配置的StringTemplateLoader。将其添加到上面的配置中。
编辑 根据提问者的编辑,您自己的 TemplateLoader 实现看起来像是要走的路。查看 Javadoc here,它是一个简单的小接口,只有四个方法,并且它的行为有据可查。
【讨论】:
【参考方案3】:从 2.3.20 开始你可以简单地construct a Template
using a string:
public Template(String name,
String sourceCode,
Configuration cfg)
throws IOException
这是Template(name, new StringReader(sourceCode), cfg)
的便捷构造函数。
【讨论】:
【参考方案4】:对于那些寻找一些代码的人,这里就是。查看代码中的 cmets 以便更好地理解。
数据库模板:
@Entity
public class DBTemplate implements Serializable
private static final long serialVersionUID = 1L;
@Id
private long templateId;
private String content; // Here's where the we store the template
private LocalDateTime modifiedOn;
TemplateLoader 实现(EMF 是 EntityManagerFactory 的一个实例):
public class TemplateLoaderImpl implements TemplateLoader
public TemplateLoaderImpl()
/**
* Retrieves the associated template for a given id.
*
* When Freemarker calls this function it appends a locale
* trying to find a specific version of a file. For example,
* if we need to retrieve the layout with id = 1, then freemarker
* will first try to load layoutId = 1_en_US, followed by 1_en and
* finally layoutId = 1.
* That's the reason why we have to catch NumberFormatException
* even if it is comes from a numeric field in the database.
*
* @param layoutId
* @return a template instance or null if not found.
* @throws IOException if a severe error happens, like not being
* able to access the database.
*/
@Override
public Object findTemplateSource(String templateId) throws IOException
EntityManager em = null;
try
long id = Long.parseLong(templateId);
em = EMF.getInstance().getEntityManager();
DBTemplateService service = new DBTemplateService(em);
Optional<DBTemplate> result = service.find(id);
if (result.isPresent())
return result.get();
else
return null;
catch (NumberFormatException e)
return null;
catch (Exception e)
throw new IOException(e);
finally
if (em != null && em.isOpen())
em.close();
/**
* Returns the last modification date of a given template.
* If the item does not exist any more in the database, this
* method will return Long's MAX_VALUE to avoid freemarker's
* from recompiling the one in its cache.
*
* @param templateSource
* @return
*/
@Override
public long getLastModified(Object templateSource)
EntityManager em = null;
try
em = EMF.getInstance().getEntityManager();
DBTemplateService service = new DBTemplateService(em);
// Optimize to only retrieve the date
Optional<DBTemplate> result = service.find(((DBTemplate) templateSource).getTemplateId());
if (result.isPresent())
return result.get().getModifiedOn().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
else
return Long.MAX_VALUE;
finally
if (em != null && em.isOpen())
em.close();
/**
* Returns a Reader from a template living in Freemarker's cache.
*/
@Override
public Reader getReader(Object templateSource, String encoding) throws IOException
return new StringReader(((DBTemplate) templateSource).getContent());
@Override
public void closeTemplateSource(Object templateSource) throws IOException
// Nothing to do here...
设置配置类:
...
TemplateLoaderImpl loader = new TemplateLoaderImpl();
templateConfig = new Configuration(Configuration.VERSION_2_3_25);
templateConfig.setTemplateLoader(loader);
...
最后,使用它:
...
long someId = 3L;
Template template = templateConfig.getTemplate("" + someId);
...
这很好用,并且允许您使用 Freemarker 的所有功能,例如导入、包含等。请看以下示例:
<#import "1" as layout> <!-- Use a template id. -->
<@layout.mainLayout>
...
或在:
<#include "3"> <!-- Use a template id. -->
...
我在我自己的 CMS (CinnamonFramework) 上使用这个加载器,效果很好。
最好的,
【讨论】:
我认为您可以设置 Configuration.setLocalizedLookup(boolean) 以禁用本地化查找,这样您就不必捕获 NumberFormatException。【参考方案5】:老问题,但对于任何有同样问题的人,我实现了一个简单的解决方案,无需自定义模板加载器或在启动时加载模板。
假设您的数据库中有动态模板:
数据库:
<p>Hello <b>$params.user</b>!</p>
您可以创建一个 Freemarker 文件 (ftlh),它解释接收到的字符串 (content
) 并使用 interpret 从中生成模板:
dynamic.ftlh:
<#assign inlineTemplate = content?interpret>
<@inlineTemplate />
然后在你的java代码中你只需要从你的数据库中获取字符串(就像从数据库中检索任何其他数据一样),并使用具有interpret
的文件来生成模板:
java:
String content = getFromDatabase();
Configuration cfg = getConfiguration();
String filePath = "dynamic.ftlh";
Map<String, Object> params = new HashMap<String, Object>();
params.put("user", "World");
Map<String, Object> root = new HashMap<>();
root.put("content", content);
root.put("params", params);
Template template = cfg.getTemplate(filePath);
try (Writer out = new StringWriter())
template.process(root, out);
String result = out.toString();
System.out.println(result);
(将方法getFromDatabase()
和getConfiguration()
更改为您想从数据库中获取动态内容并分别获取Freemarker configuration object 的任何方法)
这应该打印出来:
<p>Hello <b>World</b>!</p>
然后您可以更改数据库中的动态内容或创建其他内容,添加新参数等,而无需创建其他 Freemarker 文件 (ftlh)。
【讨论】:
【参考方案6】:实施配置。
例子:
@Configuraton
public class FreemarkerConfig
@Autowired
TemplateRepository tempRepo;
@Autowired
TemplateUtils tempUtils;
@Primary
@Bean
public FreeMarkerConfigurationFactoryBean getFreeMarkerConfiguration()
// Create new configuration bean
FreeMarkerConfigurationFactoryBean bean = new FreeMarkerConfigurationFactoryBean();
// Create template loader
StringTemplateLoader sTempLoader = new StringTemplateLoader();
// Find all templates
Iterable<TemplateDb> ite = tempRepo.findAll();
ite.forEach((template) ->
// Put them in loader
sTempLoader.putTemplate(template.getFilename(), template.getContent());
);
// Set loader
bean.setPreTemplateLoaders(sTempLoader);
return bean;
然后你可以这样使用它:
@Autowired
private Configuration freemarkerConfig;
Template template = freemarkerConfig.getTemplate(templateFilePath);
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, mapTemplate);
【讨论】:
以上是关于从数据库加载 FreeMarker 模板的主要内容,如果未能解决你的问题,请参考以下文章