You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FreeMarker跨路径导入.ftl文件失败问题求助

问题描述

尝试用FreeMarker模板化Java类时,无法跨路径导入公共函数模板functions.ftl。配置了多个模板加载路径,但使用绝对路径/common/functions.ftl导入时提示找不到模板,使用相对路径又触发"不允许从根目录退回"的错误。多模块项目中需要共享公共模板,目前只能在每个模板目录放置副本才能正常使用。

当前配置Bean:

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
  FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
      String[] paths = Stream.of(
              "classpath:/templates",
          "classpath:/templates/dao/",
          "classpath:/templates/pom/",
          "classpath:/common/")
      .map(path -> path.replace("/", File.separator).replace("\\", File.separator))
      .toArray(String[]::new);
  configurer.setTemplateLoaderPaths(paths);
  configurer.setDefaultEncoding("UTF-8");
  configurer.setPostTemplateLoaders();
  return configurer;
}

模板导入代码(Type.ftl):

<#import "/common/functions.ftl" as function>

报错信息:

2024-02-27T14:55:44.552-06:00 ERROR 60233 --- [nio-8090-exec-1] freemarker.runtime                       : Error executing FreeMarker template

freemarker.core._MiscTemplateException: Template importing failed (for parameter value "../../../common/functions.ftl"):
Template not found for name "dao/udt/../../../common/functions.ftl".
Reason given: Backing out from the root directory is not allowed.
The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/"), loader2 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/dao", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/dao/"), loader3 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/pom", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/pom/"), loader4 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/common", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/common/"), loader5 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath="" /* relatively to resourceLoaderClass pkg */)).
解决方案

方案1:修改导入路径为无斜杠的文件名

FreeMarker的MultiTemplateLoader会按配置顺序遍历所有模板加载路径查找模板。将导入语句改为:

<#import "functions.ftl" as function>

这样FreeMarker会依次在templates、templates/dao、templates/pom、common目录下查找functions.ftl,最终在common目录找到目标文件。

方案2:调整模板加载路径配置

去掉子目录的单独配置,只保留根级路径,让FreeMarker通过完整路径访问模板:

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
  FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
  // 只保留根路径,子目录通过模板内的路径引用
  String[] paths = Stream.of(
          "classpath:/",
          "classpath:/common/"
  )
  .map(path -> path.replace("/", File.separator).replace("\\", File.separator))
  .toArray(String[]::new);
  configurer.setTemplateLoaderPaths(paths);
  configurer.setDefaultEncoding("UTF-8");
  return configurer;
}

此时导入公共模板可以用绝对路径:

<#import "/common/functions.ftl" as function>

同时其他模板的引用也要调整为完整路径,比如/templates/dao/udt/Type.ftl。

方案3:将公共函数设为全局共享变量

通过代码提前加载公共模板中的函数,注入为FreeMarker的全局共享变量,无需在每个模板中导入:

@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() throws IOException, TemplateException {
  FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
  String[] paths = Stream.of(
          "classpath:/templates",
          "classpath:/common/"
  )
  .map(path -> path.replace("/", File.separator).replace("\\", File.separator))
  .toArray(String[]::new);
  configurer.setTemplateLoaderPaths(paths);
  configurer.setDefaultEncoding("UTF-8");

  // 加载公共函数模板并设置为全局变量
  configurer.setPostTemplateLoaders();
  Configuration configuration = configurer.createConfiguration();
  Template functionTemplate = configuration.getTemplate("functions.ftl");
  // 获取模板中的函数哈希表
  Map<String, Object> functionMap = new HashMap<>();
  functionTemplate.process(functionMap, new StringWriter());
  // 将函数设置为全局共享变量
  configuration.setSharedVariable("function", functionMap.get("function"));

  configurer.setConfiguration(configuration);
  return configurer;
}

这样所有模板无需导入,直接使用function.camelToSnakeCase(...)即可调用公共函数。

内容的提问来源于stack exchange,提问作者Rahul verma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:14:52