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
或FreemarkerServlet
init-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)