【问题标题】:Load FreeMarker templates from database从数据库加载 FreeMarker 模板
【发布时间】:2010-09-26 07:43:15
【问题描述】:

我想将我的 FreeMarker 模板存储在类似于以下内容的数据库表中:

template_name | template_content
---------------------------------
hello         |Hello ${user}
goodbye       |So long ${user}

当收到对具有特定名称的模板的请求时,这应该会导致执行查询,该查询会加载相关的模板内容。然后,应将此模板内容与数据模型(上述示例中的“用户”变量的值)一起传递给 FreeMarker。

但是,FreeMarker API 似乎假定每个模板名称对应于文件系统特定目录中的同名文件。有什么方法可以轻松地从数据库而不是文件系统加载我的模板?

编辑:我应该提到我希望能够在应用程序运行时将模板添加到数据库中,所以我不能简单地在启动时将所有模板加载到新的 StringTemplateLoader (如下所示)。

【问题讨论】:

    标签: java templates freemarker


    【解决方案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);
            

            【讨论】:

              猜你喜欢
              • 2013-03-18
              • 2011-05-11
              • 2012-07-17
              • 1970-01-01
              • 2019-10-28
              • 1970-01-01
              • 1970-01-01
              • 2011-01-28
              • 2014-05-10
              相关资源
              最近更新 更多