Bean wrapper

Page Contents

Note:

建议不要再直接使用BeansWrapper。请使用其子类DefaultObjectWrapper,尽管要确保其incompatibleImprovements属性至少为 2.3.22. DefaultObjectWrapper提供更清晰的数据模型(较少混淆的多类型 FTL 值),并且通常更快。 在此处查看有关 DefaultObjectWrapper 的更多信息...

freemarker.ext.beans.BeansWrapper是最初添加到 FreeMarker 的object wrapper,因此可以将任意 POJO-s(普通的旧 Java 对象)包装到TemplateModel接口中。从那时起,它已成为正常的处理方式,实际上DefaultObjectWrapper本身是BeansWrapper的扩展。因此,这里描述的所有内容也都适用于DefaultObjectWrapper,除了DefaultObjectWrapper将使用freemarker.template.SimpleXxx类包装StringNumberDatearrayCollection(如List),MapBooleanIterator对象以及具有freemarker.ext.dom.NodeModel(freemarker.ext.dom.NodeModel(__)的 W3C DOM 节点) ,因此上述规则不适用于这些规则。

这些情况下,您将要使用BeansWrapper而不是DefaultObjectWrapper

以下是BeansWrapper创建的TemplateModel -s 的摘要。为了进行下面的讨论,假定在包装之前将对象称为obj,在包装之后将对象称为model

TemplateHashModel functionality

每个对象都将包装在TemplateHashModel中,该TemplateHashModel将公开对象的 JavaBeans 属性和方法。这样,您可以在模板中使用model.foo来调用obj.getFoo()obj.isFoo()方法。 (请注意,公共字段不是直接可见的;您必须为它们编写一个 getter 方法.)公共方法也可以通过哈希模型作为模板方法模型进行检索,因此可以使用model.doBar()调用object.doBar()。关于方法模型功能的更多讨论。

如果无法将请求的键 Map 到 Bean 属性或方法,则框架将尝试找到所谓的“通用 get 方法”,即具有签名 public any-return-type get(String)或 public any-return-type get(Object)的方法,并使用请求的键调用该方法。请注意,这允许方便地访问java.util.Map和类似类中的 Map-只要 Map 的键是String,并且某些属性或方法名不会遮盖该 Map 即可。 (有一种避免 shade 的解决方案,请 continue 阅读.)另外请注意,java.util.ResourceBundle对象的模型使用getObject(String)作为通用的 get 方法。

如果您在BeansWrapper实例上调用setExposeFields(true),它将还将类的公共非静态字段公开为哈希键和值。即如果fooBar类的公共非静态字段,并且bar是包装Bar实例的模板变量,则bar.foo表达式将求值为bar对象的字段foo的值。该类的所有超类中的公共字段也都公开。

关于安全性

默认情况下,您将无法访问几种认为不安全的方法。例如,您不能使用同步方法(waitnotifynotifyAll),线程和线程组 Management 方法(stopsuspendresumesetDaemonsetPriority),反射(Field setXxx方法,Method.invokeConstructor.newInstanceClass.newInstanceClass.getClassLoader等)和SystemRuntime类(execexithaltload等)中的各种危险方法。 BeansWrapper具有多个安全级别(称为“方法公开级别”),默认名称EXPOSE_SAFE可能适用于大多数应用程序。有一个称为EXPOSE_ALL的无防护级别,它甚至可以调用上述不安全的方法,而有一个严格的EXPOSE_PROPERTIES_ONLY级别将仅公开 bean 属性的获取方法。最后,有一个名为EXPOSE_NOTHING的级别将不公开任何属性和方法。在这种情况下,您唯一可以通过哈希模型接口访问的数据是 Map 和资源束中的项目,以及从调用通用get(Object)get(String)方法返回的对象-只要受影响的对象具有此类方法。

TemplateScalarModel functionality

java.lang.String对象的模型将实现TemplateScalarModel,其getAsString()方法仅委托给toString()。请注意,将String对象包装到 Bean 包装器中不仅提供了标量,还提供了更多的功能:由于上述哈希接口,包装String的模型还提供了对所有String方法(indexOfsubstring等)的访问权限。其中有本机 FreeMarker 等效项,最好使用(s?index_of(n)s[start..<end]等)。

TemplateNumberModel functionality

属于java.lang.Number实现TemplateNumberModel实例的对象的模型包装器,其getAsNumber()方法返回包装的数字对象。请注意,将Number对象包装到 Bean 包装器中提供的功能远不止是数字模型:由于上述哈希接口,包装Number的模型还提供了对其所有方法的访问。

TemplateCollectionModel functionality

用于本机 Java 数组和所有实现java.util.Collection的类的模型包装器将实现TemplateCollectionModel,从而获得通过list指令可使用的附加功能。

TemplateSequenceModel functionality

用于本机 Java 数组和所有实现java.util.List的类的模型包装器将实现TemplateSequenceModel,因此可以使用model[i]语法通过索引访问其元素。您还可以使用model?size内置查询数组的长度或列表的大小。

同样,每个采用单个参数的方法(通过intlongfloatdoublejava.lang.Objectjava.lang.Numberjava.lang.Integer)都可以通过java.lang.Integer进行反射方法调用来分配,也都实现了此接口。这意味着您有一种方便的方法来访问所谓的索引 bean 属性:model.foo[i]将转换为obj.getFoo(i)

TemplateMethodModel functionality

一个对象的所有方法都表示为TemplateMethodModelEx个对象,这些对象可以使用在其对象模型上具有方法名称的哈希键进行访问。当您使用model.method(arg1, arg2, ...)调用方法时,参数将作为模板模型传递给该方法。该方法将首先尝试解包它们-有关解包的详细信息,请参见下文。然后将这些未包装的参数用于实际的方法调用。如果方法被重载,将使用与 Java 编译器用于从多个重载方法中选择一个方法的规则相同的规则来选择最具体的方法。如果没有方法签名与传递的参数匹配,或者没有歧义就无法选择任何方法,则抛出TemplateModelException

返回类型void的方法返回TemplateModel.NOTHING,因此可以使用${obj.method(args)}语法安全地调用它们。

java.util.Map实例的模型也将TemplateMethodModelEx作为调用其get()方法的手段。如前所述,您也可以使用哈希功能来访问“ get”方法,但是它有几个缺点:它较慢,因为首先检查了键的第一个属性和方法名;与属性和方法名称冲突的键将被它们遮盖;最后,您只能通过这种方法使用String键。相比之下,调用model(key)直接转换为model.get(key):更快,因为没有属性和方法名称查找。它没有 shade;最后,它适用于非字符串键,因为参数与普通方法调用一样被解包。实际上,Map上的model(key)等于model.get(key),只是写入时间较短。

java.util.ResourceBundle的模型还实现TemplateMethodModelEx作为资源访问和消息格式的便捷方式。对 bundle 的单参数调用将检索名称与 unwrapped 参数的toString()值相对应的资源。对 bundle 的多参数调用还将检索名称与 unwrapped 参数的toString()值相对应的资源,但是它将使用它作为格式模式,并使用第二个及后续参数的 unwrapped 值将其传递给java.text.MessageFormat作为格式参数。 MessageFormat对象将使用产生它们的包的语言环境进行初始化。

Unwrapping rules

从模板调用 Java 方法时,需要将其参数从模板模型转换回 Java 对象。假设目标类型(方法的形式参数的声明类型)表示为T,则按以下 Sequences 尝试以下规则:

访问静态方法

BeansWrapper.getStaticModels()返回的TemplateHashModel可用于创建哈希模型,以访问静态方法和任意类的字段。

BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
TemplateHashModel staticModels = wrapper.getStaticModels();
TemplateHashModel fileStatics =
    (TemplateHashModel) staticModels.get("java.io.File");

您将获得一个模板哈希模型,该模型将java.lang.System类的所有静态方法和静态字段(最终的和非最终的)公开为哈希键。假设您将先前的模型放在根模型中:

root.put("File", fileStatics);

从现在开始,您可以使用${File.SEPARATOR}将文件分隔符插入模板,或者甚至可以通过以下方式列出文件系统的所有根目录:

<#list File.listRoots() as fileSystemRoot>...</#list>

当然,您必须意识到此模型带来的潜在安全问题。

您甚至可以通过以下方式将模板的静态模型哈希放入模板根模型中,从而使模板作者可以完全自由地使用类的静态方法:

root.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

如果该对象用作以类名作为键的哈希值,则该对象几乎公开任何类的静态方法。然后,您可以在模板中使用诸如${statics["java.lang.System"].currentTimeMillis()}之类的表达式。但是请注意,这具有更大的安全隐患,因为如果方法的公开级别降低到EXPOSE_ALL,则有人甚至可以使用此模型调用System.exit()

请注意,在以上示例中,我们始终使用默认的BeansWrapper实例。这是一个方便的静态包装器实例,您可以在大多数情况下使用。您还可以自由创建自己的BeansWrapper实例并改用它们,尤其是当您想要修改其某些 Feature(例如模型缓存,安全级别或空模型表示形式)时。

Accessing enums

BeansWrapper.getEnumModels()返回的TemplateHashModel可用于创建用于访问枚举值的哈希模型。 (尝试在较早的 JRE 上调用此方法将导致UnsupportedOperationException.)

BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
TemplateHashModel enumModels = wrapper.getEnumModels();
TemplateHashModel roundingModeEnums =
    (TemplateHashModel) enumModels.get("java.math.RoundingMode");

然后,您将获得一个模板哈希模型,该模型将java.math.RoundingMode类的所有枚举值公开为哈希键。假设您将先前的模型放在根模型中:

root.put("RoundingMode", roundingModeEnums);

从现在开始,您可以使用RoundingMode.UP作为表达式来引用模板中的UP枚举值。

您甚至可以通过以下方式为模板作者提供完全的自由:使用以下方法将枚举模型哈希放入模板根模型中:

root.put("enums", BeansWrapper.getDefaultInstance().getEnumModels());

如果此对象用作以类名作为键的哈希值,则此对象公开任何枚举类。然后,您可以在模板中使用诸如${enums["java.math.RoundingMode"].UP}之类的表达式。

暴露的枚举值可以用作标量(它们将委托给它们的toString()方法),也可以用于相等性和不相等性比较中。

请注意,在以上示例中,我们始终使用默认的BeansWrapper实例。这是一个方便的静态包装器实例,您可以在大多数情况下使用。您还可以自由创建自己的BeansWrapper实例并改用它们,尤其是当您想要修改其某些 Feature(例如模型缓存,安全级别或空模型表示形式)时。

上一章 首页 下一章