On this page
Object wrappers
Page Contents
对象包装器是实现freemarker.template.ObjectWrapper接口的对象。目的是实现 Java 对象(例如String -s,Map -s,List -s,您的应用程序特定类的实例等)和 FTL 的类型系统之间的 Map。换句话说,它指定模板如何看待数据模型的 Java 对象(包括从模板调用的 Java 方法的返回值)。对象包装器以object_wrapper设置(或Configuration.setObjectWrapper)插入Configuration。
FTL 的类型系统在技术上由较早引入的TemplateModel子接口(TemplateScalarModel,TemplateHashMode,TemplateSequenceModel等)表示。要将 Java 对象 Map 到 FTL 的类型系统,将调用对象包装器的TemplateModel wrap(java.lang.Object obj)方法。
有时,FreeMarker 需要逆转此 Map,在这种情况下,将调用ObjectWrapper的Object unwrap(TemplateModel)方法(或该方法的某些其他变体,但有关详细信息,请参阅 API 文档)。最后一个操作在ObjectWrapperAndUnwrapper(ObjectWrapper的子接口)中。大多数现实世界中的对象包装器都将实现ObjectWrapperAndUnwrapper。
这是包装包含其他对象(例如Map,List,数组或具有某些 JavaBean 属性的对象)的 Java 对象通常的工作方式。假设对象包装器将Object[]数组包装到TemplateSquenceModel接口的某些实现中。当 FreeMarker 需要该 FTL 序列中的项目时,它将调用TemplateSquenceModel.get(int index)。此方法的返回类型为TemplateModel,即TemplateSquenceModel实现不仅必须从数组的给定索引中获取Object,而且还负责在返回值之前包装该值。为了解决这个问题,典型的TemplateSquenceModel实现将存储已确定其大小的ObjectWrapper,然后调用ObjectWrapper来包装所包含的值。相同的逻辑代表TemplateHashModel或代表其他TemplateModel -s 的任何其他TemplateModel。因此,通常,无论值层次有多深,所有值都将由同一个ObjectWrapper包裹。 (要创建遵循该惯用法的TemplateModel实现,可以将freemarker.template.WrappingTemplateModel用作 Base Class.)
数据模型本身(根变量)是TemplateHashModel。您指定给Template.process的根对象将被object_wrapper配置设置中指定的对象包装器包装,该包装必须产生TemplateHashModel。从那时起,包含值的包装遵循前面描述的逻辑(即容器负责包装其子包装)。
行为良好的对象包装器绕过了已按原样实现TemplateModel的对象。因此,如果将对象放入已经实现TemplateModel的数据模型中(或者从模板等调用的 Java 方法中以此类对象的形式返回),则可以避免实际的对象包装。通常在创建专门从模板访问的值时执行此操作。因此,您避免了很多对象包装性能开销,还可以精确控制模板将看到的内容(不取决于当前对象包装程序的 Map 策略)。此技巧的常见应用是使用freemarker.template.SimpleHash作为数据模型的根(而不是Map),方法是使用SimpleHash的put方法填充它(这一点很重要,因此不必复制现有的Map已经填满)。这样可以加快对顶层数据模型变量的访问。
默认的对象包装器
object_wrapper Configuration设置的默认值为freemarker.template.DefaultObjectWrapper单例。除非您有非常特殊的要求,否则建议使用此对象包装器或您的DefaultObjectWrapper子类的实例。
它可以识别大多数基本的 Java 类型,例如String,Number,Boolean,Date,List(以及所有类型的java.util.Collection -s),数组,Map等,并将它们包装到自然匹配的TemplateModel接口中。还将使用freemarker.ext.dom.NodeModel包裹 W3C DOM 节点,因此您可以方便地遍历 XML 为在自己的章节中描述)。对于 Jython 对象,它将委派给freemarker.ext.jython.JythonWrapper。对于所有其他对象,它将调用BeansWrapper.wrap(超类的方法),该方法会将对象的 JavaBean 属性公开为哈希项(如 FTL 中的myObj.foo将在幕后调用getFoo()),还将公开公共方法(JavaBean)动作)(例如 FTL 中的myObj.bar(1, 2)将调用方法)。 (有关 BeansWrapper 的详细信息,看到自己的部分。)
值得一提的有关DefaultObjectWrapper的其他详细信息:
您通常不应使用其构造函数,而应使用
DefaultObjectWrapperBuilder创建它。这使 FreeMarker 可以使用单例。DefaultObjectWrapper具有incompatibleImprovements属性,强烈建议将其设置为较高的值(有关效果,请参见API documentation)。设置方法:如果您已将
Configuration的incompatible_improvements设置设置为 2.3.22 或更高版本,而未设置object_wrapper设置(因此其设置保持其默认值),则您无需执行任何操作,因为它已经在使用一个具有incompatibleImprovements属性值的DefaultObjectWrapper单例。如果使用构建器 API:
... = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build()
- Or, if you are using the constructor:
... = new DefaultObjectWrapper(Configuration.VERSION_2_3_27)
- Or, if you are using the `object_wrapper` property \( `*.properties` file or `java.util.Properties` object\):
object_wrapper=DefaultObjectWrapper(2.3.27)
- Or, if you are configuring the `object_wrapper` through a `FreemarkerServlet` with an `init-param` in `web.xml` :
<init-param>
<param-name>object_wrapper</param-name>
<param-value>DefaultObjectWrapper(2.3.27)</param-value>
</init-param>
在新的或经过测试覆盖的项目中,还建议将
forceLegacyNonListCollections属性设置为false。如果您使用的是.properties或FreemarkerServletinit-params 或类似的参数,则看起来像DefaultObjectWrapper(2.3.22, forceLegacyNonListCollections=false),而使用 Java API 时,您必须先在DefaultObjectWrapperBuilder对象上调用setForceLegacyNonListCollections(false),然后再调用build()。自定义
DefaultObjectWrapper的最常见方法是覆盖其handleUnknownType方法。
自定义对象包装示例
假设您有一个类似于应用程序的类,如下所示:
package com.example.myapp;
public class Tupple<E1, E2> {
public Tupple(E1 e1, E2 e2) { ... }
public E1 getE1() { ... }
public E2 getE2() { ... }
}
您希望模板将其视为长度为 2 的序列,以便可以执行someTupple[1],<#list someTupple ...>或someTupple?size之类的操作。为此,您需要创建一个TemplateSequenceModel实现,以使Tupple适应TempateSequenceMoldel接口:
package com.example.myapp.freemarker;
...
public class TuppleAdapter extends WrappingTemplateModel implements TemplateSequenceModel,
AdapterTemplateModel {
private final Tupple<?, ?> tupple;
public TuppleAdapter(Tupple<?, ?> tupple, ObjectWrapper ow) {
super(ow); // coming from WrappingTemplateModel
this.tupple = tupple;
}
@Override // coming from TemplateSequenceModel
public int size() throws TemplateModelException {
return 2;
}
@Override // coming from TemplateSequenceModel
public TemplateModel get(int index) throws TemplateModelException {
switch (index) {
case 0: return wrap(tupple.getE1());
case 1: return wrap(tupple.getE2());
default: return null;
}
}
@Override // coming from AdapterTemplateModel
public Object getAdaptedObject(Class hint) {
return tupple;
}
}
关于类和接口:
TemplateSequenceModel:这就是模板将其视为序列的原因WrappingTemplateModel:只是一个便利类,用于TemplateModel-s 进行对象包装。通常只有包含其他对象的对象才需要这样做。请参见上面的wrap(...)调用。AdapterTemplateModel:指示此模板模型将已存在的对象改编为TemplateModel接口,因此展开应归还该原始对象。
最后,我们告诉 FreeMarker 将Tupple -s 与TuppleAdapter换行(或者,您可以在将它们传递给 FreeMarker 之前手动对其进行换行)。为此,首先我们创建一个自定义对象包装器:
package com.example.myapp.freemarker;
...
public class MyAppObjectWrapper extends DefaultObjectWrapper {
public MyAppObjectWrapper(Version incompatibleImprovements) {
super(incompatibleImprovements);
}
@Override
protected TemplateModel handleUnknownType(final Object obj) throws TemplateModelException {
if (obj instanceof Tupple) {
return new TuppleAdapter((Tupple<?, ?>) obj, this);
}
return super.handleUnknownType(obj);
}
}
然后在配置 FreeMarker(有关配置,请参见此处...)的地方,将对象包装器插入:
// Where you initialize the cfg *singleton* (happens just once in the application life-cycle):
cfg = new Configuration(Configuration.VERSION_2_3_27);
...
cfg.setObjectWrapper(new MyAppObjectWrapper(cfg.getIncompatibleImprovements()));
或如果您是用java.util.Properties配置 FreeMarker(并且它也是.properties文件):
object_wrapper=com.example.myapp.freemarker.MyAppObjectWrapper(2.3.27)