可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I would like to store my FreeMarker templates in a database table that looks something like:
template_name | template_content --------------------------------- hello |Hello ${user} goodbye |So long ${user}
When a request is received for a template with a particular name, this should cause a query to be executed, which loads the relevant template content. This template content, together with the data model (the value of the 'user' variable in the examples above), should then be passed to FreeMarker.
However, the FreeMarker API seems to assume that each template name corresponds to a file of the same name within a particular directory of the filesystem. Is there any way I can easily have my templates loaded from the DB instead of the filesystem?
EDIT: I should have mentioned that I would like to be able to add templates to the database while the application is running, so I can't simply load all templates at startup into a new StringTemplateLoader (as suggested below).
Cheers, Don
回答1:
We use a StringTemplateLoader to load our tempates which we got from the db (as Dan Vinton suggested)
Here is an example:
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 = " World!"; // stringLoader.putTemplate("greetTemplate", secondTemplate); Configuration cfg = new Configuration(); cfg.setTemplateLoader(stringLoader); Template template = cfg.getTemplate(firstTemplate);
Edit You don't have to load all templates at startup. Whenever we will access the template, we'll fetch it from the DB and load it through the StringLoader and by calling template.process() we generate (in our case) the XML output.
回答2:
A couple of ways:
Create a new implementation of TemplateLoader to load templates direct from the database, and pass it to your Configuration instance using setTemplateLoader()
prior to loading any templates.
Use a StringTemplateLoader that you configure from your database when your application starts. Add it to the configuration as above.
Edit in light of the questioner's edit, your own implementation of TemplateLoader looks like the way to go. Check the Javadoc here, it's a simple little interface with only four methods, and its behaviour is well documented.
回答3:
Since 2.3.20 you can simply construct a Template
using a string:
public Template(String name, String sourceCode, Configuration cfg) throws IOException
which is a convenience constructor for Template(name, new StringReader(sourceCode), cfg)
.
回答4:
For those looking for some code, here it is. Take a look at the comments in the code for a better understanding.
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 implementation (EMF is an instance of an 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 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 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... } }
Setup the configuration class:
... TemplateLoaderImpl loader = new TemplateLoaderImpl(); templateConfig = new Configuration(Configuration.VERSION_2_3_25); templateConfig.setTemplateLoader(loader); ...
And finally, use it:
... long someId = 3L; Template template = templateConfig.getTemplate("" + someId); ...
This works great, and allows you to use all of Freemarker's features like imports, includes, etc. Look at the following examples:
...
Or in:
...
I use this loader on my own CMS (CinnamonFramework) and works like a charm.
Best,