如何获取当前Freemarker模板中使用的哈希与标量列表?
提取Freemarker模板中的哈希与标量变量
要提取Freemarker模板中引用的所有哈希(hashes)和标量(scalars),无需执行模板即可完成——你需要解析模板的抽象语法树(AST),因为Freemarker没有提供直接的API来获取这类静态元数据。你之前尝试的createProcessingEnvironment方法依赖模板执行,且需要传入有效数据模型,因此无法满足提前提取变量的需求。
实现方案:解析AST提取变量
通过Template.getRootTreeNode()获取模板的AST根节点,递归遍历所有节点,识别并收集变量引用。以下是完整的实现代码:
1. 变量提取工具类
import freemarker.core.*; import freemarker.template.TemplateNode; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class TemplateVariableExtractor { private final Set<String> scalarVariables = new HashSet<>(); private final Set<String> hashVariables = new HashSet<>(); public void extract(TemplateNode rootNode) { traverseNode(rootNode); } private void traverseNode(TemplateNode node) { // 处理文本插值中的变量(如${username}、${user.name}) if (node instanceof TextBlock) { TextBlock textBlock = (TextBlock) node; for (TemplateElement elem : textBlock.getElements()) { if (elem instanceof Interpolation) { processExpression(((Interpolation) elem).getExpression()); } } } // 处理指令参数中的变量(如#if(${user.isActive})、#foreach(item in items)) if (node instanceof DirectiveCallPlace) { DirectiveCallPlace directive = (DirectiveCallPlace) node; Map<String, Expression> params = directive.getParameters(); // 遍历指令参数表达式 for (Expression exp : params.values()) { processExpression(exp); } // 收集foreach循环变量 if ("foreach".equals(directive.getDirectiveName())) { Expression itemExp = params.get("item"); if (itemExp instanceof Identifier) { scalarVariables.add(((Identifier) itemExp).getName()); } } } // 递归处理子节点 List<TemplateNode> children = node.getChildNodes(); for (TemplateNode child : children) { traverseNode(child); } } private void processExpression(Expression exp) { if (exp instanceof Identifier) { // 标量变量(如${username}) scalarVariables.add(((Identifier) exp).getName()); } else if (exp instanceof Dot) { // 哈希的点访问(如${user.name}) Dot dotExp = (Dot) exp; Expression targetExp = dotExp.getTarget(); String propertyName = dotExp.getPropertyName(); // 递归处理目标表达式(如user.name中的user) processExpression(targetExp); // 记录父哈希 if (targetExp instanceof Identifier) { hashVariables.add(((Identifier) targetExp).getName()); } // 记录完整路径的标量(如user.name) scalarVariables.add(getExpressionPath(targetExp) + "." + propertyName); } else if (exp instanceof SquareBracket) { // 哈希的方括号访问(如${user["name"]}) SquareBracket bracketExp = (SquareBracket) exp; Expression targetExp = bracketExp.getTarget(); Expression indexExp = bracketExp.getIndex(); processExpression(targetExp); if (targetExp instanceof Identifier) { hashVariables.add(((Identifier) targetExp).getName()); } // 若索引为字符串常量,记录完整路径 if (indexExp instanceof StringLiteral) { String index = ((StringLiteral) indexExp).getAsString(); scalarVariables.add(getExpressionPath(targetExp) + "." + index); } } // 可扩展处理其他表达式类型:算术表达式、三元表达式等 } private String getExpressionPath(Expression exp) { if (exp instanceof Identifier) { return ((Identifier) exp).getName(); } else if (exp instanceof Dot) { Dot dotExp = (Dot) exp; return getExpressionPath(dotExp.getTarget()) + "." + dotExp.getPropertyName(); } else if (exp instanceof SquareBracket) { SquareBracket bracketExp = (SquareBracket) exp; Expression indexExp = bracketExp.getIndex(); if (indexExp instanceof StringLiteral) { return getExpressionPath(bracketExp.getTarget()) + "." + ((StringLiteral) indexExp).getAsString(); } } return exp.toString(); } public Set<String> getScalarVariables() { return scalarVariables; } public Set<String> getHashVariables() { return hashVariables; } }
2. 使用工具类提取变量
import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.Version; import java.io.IOException; import java.util.Set; public class TemplateProcessor { public static void main(String[] args) throws IOException { Configuration cfg = new Configuration(new Version("2.3.32")); cfg.setClassForTemplateLoading(TemplateProcessor.class, "/"); cfg.setDefaultEncoding("UTF-8"); // 加载目标模板 Template template = cfg.getTemplate("origin-keycloak/themes/base/account/totp.ftl"); // 提取变量 TemplateVariableExtractor extractor = new TemplateVariableExtractor(); extractor.extract(template.getRootTreeNode()); // 获取结果 Set<String> scalars = extractor.getScalarVariables(); Set<String> hashes = extractor.getHashVariables(); // 输出示例 System.out.println("提取的标量变量:"); scalars.forEach(System.out::println); System.out.println("\n提取的哈希变量:"); hashes.forEach(System.out::println); } }
关键说明
- 静态解析:无需执行模板或传入数据模型,直接基于模板AST分析,适合批量处理大量更新的模板。
- 覆盖场景:支持文本插值、指令参数、foreach循环变量,以及哈希的点访问和方括号访问。
- 可扩展性:可根据模板实际使用的表达式类型(如算术运算、三元表达式)扩展
processExpression方法,覆盖更多变量引用场景。
内容的提问来源于stack exchange,提问作者Holm
相关产品推荐
相关产品推荐

