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子接口(TemplateScalarModelTemplateHashModeTemplateSequenceModel等)表示。要将 Java 对象 Map 到 FTL 的类型系统,将调用对象包装器的TemplateModel wrap(java.lang.Object obj)方法。

有时,FreeMarker 需要逆转此 Map,在这种情况下,将调用ObjectWrapperObject unwrap(TemplateModel)方法(或该方法的某些其他变体,但有关详细信息,请参阅 API 文档)。最后一个操作在ObjectWrapperAndUnwrapper(ObjectWrapper的子接口)中。大多数现实世界中的对象包装器都将实现ObjectWrapperAndUnwrapper

这是包装包含其他对象(例如MapList,数组或具有某些 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),方法是使用SimpleHashput方法填充它(这一点很重要,因此不必复制现有的Map已经填满)。这样可以加快对顶层数据模型变量的访问。

默认的对象包装器

object_wrapper Configuration设置的默认值为freemarker.template.DefaultObjectWrapper单例。除非您有非常特殊的要求,否则建议使用此对象包装器或您的DefaultObjectWrapper子类的实例。

它可以识别大多数基本的 Java 类型,例如StringNumberBooleanDateList(以及所有类型的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单例。

    • 否则,您必须独立于Configuration设置incompatibleImprovements。根据您创建/设置ObjectWrapper的方式,可以这样进行:
  • 如果使用构建器 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。如果您使用的是.propertiesFreemarkerServlet 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)