2010-04-28 18 views
6

关注先进的Freemarker大师:使用单一的freemarker模板来显示任意的POJO表

我想用一个单一的freemarker模板能够任意的POJO产出表,同列显示效果比单独定义数据。问题是我无法弄清楚如何在运行时得到pojo上的函数句柄,然后让freemarker调用该函数(lambda样式)。从浏览文档看来,Freemarker似乎支持函数式编程,但我似乎无法对正确的咒语进行论证。

我刮起了简单的具体例子。假设我有两个列表:具有firstName和lastName的人员列表,以及具有make和model的汽车列表。想输出这两个表:

<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> 

<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> 

但我想使用相同的模板,因为这是必须处理的几十种不同的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(); 
    } 

} 

所有上述是针对此问题给出。所以这里是我一直在剽窃的模板。注意我遇到麻烦的评论。

<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> 

因此,col.name具有我想从pojo访问的属性的名称。我已经尝试了一些事情,比如

pojo.col.name 

<#assign property = col.name/> 
${pojo.property} 

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

回答

2

找到了答案。

${("pojo." + col.name)?eval} 
5

?eval是(几乎?)总是一个坏主意,因为它往往与性能的缺点(如很多分析的)和安全问题(例如,“FTL注入”)。

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

还有另外一种语法,如果我们要指定一个表达式的子变量名:书[“称号”。在方括号中,只要它计算为一个字符串,就可以给出任何表达式。

(从FreeMarker documentation about retrieving data from a hash

在你的情况我建议像${pojo[col.name]}