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的其他详细信息:

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

自定义对象包装示例

假设您有一个类似于应用程序的类,如下所示:

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

}

关于类和接口:

最后,我们告诉 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)
上一章 首页 下一章