我想将我的FreeMarker模板存储在类似于:
的数据库表中template_name | template_content
---------------------------------
hello |Hello ${user}
goodbye |So long ${user}
当收到具有特定名称的模板的请求时,这应该导致执行查询,该查询加载相关的模板内容。这个模板内容以及数据模型(上面例子中'user'变量的值)应该传递给FreeMarker。
但是,FreeMarker API似乎假设每个模板名称对应于文件系统的特定目录中的同名文件。有什么方法可以轻松地从数据库而不是文件系统加载我的模板吗?
编辑:我应该提到我希望能够在应用程序运行时向数据库添加模板,因此我不能简单地将启动时的所有模板加载到新的StringTemplateLoader中(如下所述)。
干杯, 唐
答案 0 :(得分:28)
我们使用StringTemplateLoader来加载我们从db获得的tempate(如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);
修改强> 您不必在启动时加载所有模板。每当我们访问模板时,我们将从DB中获取它并通过StringLoader加载它,并通过调用template.process()生成(在我们的例子中)XML输出。
答案 1 :(得分:18)
有两种方式:
创建TemplateLoader的新实现,直接从数据库加载模板,并在加载任何模板之前使用setTemplateLoader()
将其传递到Configuration实例。
使用您在应用程序启动时从数据库配置的StringTemplateLoader。将其添加到上面的配置中。
编辑,您自己的TemplateLoader实现看起来就像是要走的路。检查Javadoc here,它只是一个简单的小接口,只有四种方法,其行为已有详细记录。
答案 2 :(得分:3)
从2.3.20开始,你可以简单地construct a Template
using a string:
public Template(String name,
String sourceCode,
Configuration cfg)
throws IOException
答案 3 :(得分:3)
对于那些寻找代码的人来说,就是这样。请查看代码中的注释,以便更好地理解。
DBTemplate:
@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)上使用这个加载器,就像一个魅力。
最佳,
答案 4 :(得分:1)
一个古老的问题,但是对于遇到同样问题的任何人,我都实现了一个简单的解决方案,无需自定义模板加载器,也不必在启动时加载模板。
假设您的数据库中有动态模板:
数据库:
<p>Hello <b>${params.user}</b>!</p>
您可以使用interpret创建一个Freemarker文件(ftlh)来解释收到的字符串(content
)并从中生成模板:
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)。
答案 5 :(得分:0)
实施配置。
示例:
@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);