使用单个freemarker模板显示任意pojos表

Kev*_*uli 6 freemarker

注意提升Freemarker大师:

我想使用单个freemarker模板来输出任意pojos的表,其中要显示的列与数据分开定义.问题是我无法弄清楚如何在运行时获取pojo函数的句柄,然后让freemarker调用该函数(lambda样式).从浏览文档看,Freemarker似乎支持函数式编程,但我似乎无法论证正确的咒语.

我掀起了一个简单的具体例子.假设我有两个列表:具有firstName和lastName的人员列表,以及带有品牌和型号的汽车列表.想输出这两个表:

<table>
  <tr>
    <th>firstName</th>
    <th>lastName</th>
  </tr>
  <tr>
    <td>Joe</td>
    <td>Blow</d>
  </tr>
  <tr>
    <td>Mary</td>
    <td>Jane</d>
  </tr>
</table>
Run Code Online (Sandbox Code Playgroud)

<table>
  <tr>
    <th>make</th>
    <th>model</th>
  </tr>
  <tr>
    <td>Toyota</td>
    <td>Tundra</d>
  </tr>
  <tr>
    <td>Honda</td>
    <td>Odyssey</d>
  </tr>
</table>
Run Code Online (Sandbox Code Playgroud)

但我想使用相同的模板,因为这是一个必须处理几十种不同的pojo类型的框架的一部分.

给出以下代码:

public class FreemarkerTest {

  public static class Table {
    private final List<Column> columns = new ArrayList<Column>();

    public Table(Column[] columns) {
      this.columns.addAll(Arrays.asList(columns));
    }

    public List<Column> getColumns() {
      return columns;
    }

  }

  public static class Column {
    private final String name;

    public Column(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }
  }

  public static class Person {
    private final String firstName;
    private final String lastName;

    public Person(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
    }

    public String getFirstName() {
      return firstName;
    }

    public String getLastName() {
      return lastName;
    }
  }

  public static class Car {
    String make;
    String model;

    public Car(String make, String model) {
      this.make = make;
      this.model = model;
    }

    public String getMake() {
      return make;
    }

    public String getModel() {
      return model;
    }
  }

  public static void main(String[] args) throws Exception {
    final Table personTableDefinition = new Table(new Column[] { new Column("firstName"), new Column("lastName") });
    final List<Person> people = Arrays.asList(new Person[] { new Person("Joe", "Blow"), new Person("Mary", "Jane") });
    final Table carTable = new Table(new Column[] { new Column("make"), new Column("model") });
    final List<Car> cars = Arrays.asList(new Car[] { new Car("Toyota", "Tundra"), new Car("Honda", "Odyssey") });

    final Configuration cfg = new Configuration();
    cfg.setClassForTemplateLoading(FreemarkerTest.class, "");
    cfg.setObjectWrapper(new DefaultObjectWrapper());
    final Template template = cfg.getTemplate("test.ftl");

    process(template, personTableDefinition, people);
    process(template, carTable, cars);
  }

  private static void process(Template template, Table tableDefinition, List<? extends Object> data) throws Exception {
    final Map<String, Object> dataMap = new HashMap<String, Object>();
    dataMap.put("tableDefinition", tableDefinition);
    dataMap.put("data", data);
    final Writer out = new OutputStreamWriter(System.out);
    template.process(dataMap, out);
    out.flush();
  }

}
Run Code Online (Sandbox Code Playgroud)

所有上述内容都是针对此问题的.所以这是我一直在攻击的模板.请注意我遇到问题的评论.

<table>
  <tr>
<#list tableDefinition.columns as col>
    <th>${col.name}</th>
</#list>
  </tr>
<#list data as pojo>
  <tr>
<#list tableDefinition.columns as col>
    <td><#-- what goes here? --></td>    
</#list>  
  </tr>
</#list>
</table>
Run Code Online (Sandbox Code Playgroud)

因此col.name具有我想从pojo访问的属性的名称.我尝试过一些东西,比如

pojo.col.name
Run Code Online (Sandbox Code Playgroud)

<#assign property = col.name/>
${pojo.property}
Run Code Online (Sandbox Code Playgroud)

但当然这些都不起作用,我只是将它们包括在内以帮助传达我的意图.我正在寻找一种方法来获取函数的句柄并让freemarker调用它,或者某种"评估"功能,它可以将任意表达式作为字符串并在运行时对其进行评估.

Cha*_*tay 5

?eval (几乎?)总是一个坏主意,因为它经常带有性能缺陷(例如大量解析)和安全问题(例如"FTL注入").

更好的方法是使用方括号语法:

如果我们想要使用表达式指定子变量名称,则有另一种语法:book ["title"].在方括号中,只要求值为字符串,就可以给出任何表达式.

(来自FreeMarker关于从哈希中检索数据的文档)

在你的情况下,我会推荐类似的东西${pojo[col.name]}.


Kev*_*uli 2

找到了答案。

${("pojo." + col.name)?eval}
Run Code Online (Sandbox Code Playgroud)