On this page
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
类包装String
,Number
,Date
,array
,Collection
(如List
),Map
,Boolean
和Iterator
对象以及具有freemarker.ext.dom.NodeModel
(freemarker.ext.dom.NodeModel
(__)的 W3C DOM 节点) ,因此上述规则不适用于这些规则。
这些情况下,您将要使用BeansWrapper
而不是DefaultObjectWrapper
:
在模板执行期间,应允许修改模型的
Collection
-s 和Map
-s。 (DefaultObjectWrapper
防止了这种情况,因为它会在包装时创建集合的副本,并且副本将是只读的.)如果将
array
,Collection
和Map
对象的标识传递到模板中的包装对象的方法时,必须保留它们的标识。也就是说,如果这些方法必须获得与之前包装的对象完全相同的对象。如果前面列出的类(
String
,Map
,List
... etc)的 Java API 对于模板是可见的。即使使用BeansWrapper
,它们在默认情况下也不可见,但是可以通过设置曝光水平来实现(请参阅下文)。请注意,这通常是一种不良做法;尝试使用the built-ins(例如foo?size
,foo?upper_case
,foo?replace('_', '-')
...等)代替 Java API。
以下是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)
,它将还将类的公共非静态字段公开为哈希键和值。即如果foo
是Bar
类的公共非静态字段,并且bar
是包装Bar
实例的模板变量,则bar.foo
表达式将求值为bar
对象的字段foo
的值。该类的所有超类中的公共字段也都公开。
关于安全性
默认情况下,您将无法访问几种认为不安全的方法。例如,您不能使用同步方法(wait
,notify
,notifyAll
),线程和线程组 Management 方法(stop
,suspend
,resume
,setDaemon
,setPriority
),反射(Field
setXxx
方法,Method.invoke
,Constructor.newInstance
,Class.newInstance
,Class.getClassLoader
等)和System
和Runtime
类(exec
,exit
,halt
,load
等)中的各种危险方法。 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
方法(indexOf
,substring
等)的访问权限。其中有本机 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
内置查询数组的长度或列表的大小。
同样,每个采用单个参数的方法(通过int
,long
,float
,double
,java.lang.Object
,java.lang.Number
和java.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 尝试以下规则:
如果该模型是此包装器的空模型,则返回 Java
null
。如果模型实现
AdapterTemplateModel
,则返回model.getAdaptedObject(T)
的结果(如果它是T
的实例或它是一个数字),并且可以使用数字强制转换为T
,如下面的三个项目符号所述。 BeansWrapper 创建的所有模型本身都是 AdapterTemplateModel 实现,因此解包由 BeansWrapper 为基础 Java 对象创建的模型始终会产生原始 Java 对象。如果模型实现了已弃用的
WrapperTemplateModel
,则返回model.getWrappedObject()
的结果(如果它是T
的实例或它是一个数字,并且可以使用数字强制转换为T
,如下面的两个项目符号所述)。如果
T
是java.lang.String
,那么如果模型实现TemplateScalarModel
,则返回其字符串值。请注意,如果模型未实现 TemplateScalarModel,则我们不会尝试使用 String.valueOf(model)自动将模型转换为字符串。您必须显式使用内置的?string 来将非标量作为字符串传递。如果
T
是原始数字类型,或者java.lang.Number
可从T
分配,并且模型实现TemplateNumberModel
,则如果它是T
的实例或其装箱类型(如果T
是原始类型),则返回其数字值。否则,如果T
是内置 Java 数字类型(原始类型或java.lang.Number
的标准子类,包括BigInteger
和BigDecimal
),则将使用数字模型的适当强制值创建T
类的新对象或其装箱类型。如果
T
是boolean
或java.lang.Boolean
,并且模型实现TemplateBooleanModel
,则返回其布尔值。如果
T
是java.util.Map
并且模型实现TemplateHashModel
,则返回哈希模型的特殊 Map 表示形式。如果
T
是java.util.List
并且模型实现TemplateSequenceModel
,则返回序列模型的特殊 List 表示形式。如果
T
是java.util.Set
且模型实现TemplateCollectionModel
,则返回集合模型的特殊 Set 表示形式。如果
T
是java.util.Collection
或java.lang.Iterable
并且模型实现TemplateCollectionModel
或TemplateSequenceModel
,则分别返回集合或序列模型的特殊 Set 或 List 表示形式。如果
T
是 Java 数组类型,并且模型实现了TemplateSequenceModel
,则将创建指定类型的新数组,并使用该数组的组件类型作为T
递归将其元素解包到该数组中。如果
T
是char
或java.lang.Character
,并且模型实现TemplateScalarModel
,并且其字符串表示形式恰好包含一个字符,则具有该值的java.lang.Character
将被撤消。如果可以从
T
分配java.util.Date
,并且模型实现TemplateDateModel
,并且其日期值为T
的实例,则返回其日期值。如果 model 是数字模型,并且其数值是
T
的实例,则返回该数值。您可以具有实现自定义接口的 java.lang.Number 的自定义子类,T 可能是该接口。 (*)如果 model 是日期模型,并且其日期值为
T
的实例,则返回日期值。与(*)类似的考虑如果 model 是标量模型,并且可以从
java.lang.String
分配T
,则返回字符串值。这涵盖了 T 为 java.lang.Object,java.lang.Comparable 和 java.io.Serializable(**)的情况如果 model 是 Boolean 模型,并且可以从
java.lang.Boolean
分配T
,则返回布尔值。和...一样 (**)如果模型是哈希模型,并且可以从
freemarker.ext.beans.HashAdapter
分配T
,则返回哈希适配器。和...一样 (**)如果 model 是序列模型,并且
T
可从freemarker.ext.beans.SequenceAdapter
分配,则返回序列适配器。和...一样 (**)如果 model 是集合模型,并且可以从
freemarker.ext.beans.SetAdapter
分配T
,则返回集合的设置适配器。和...一样 (**)如果模型是
T
的实例,则返回模型本身。这涵盖了方法显式声明了 FreeMarker 特定的模型接口的情况,以及当请求 java.lang.Object 时允许返回指令,方法和转换模型的情况。引发表示无法进行转换的异常。
访问静态方法
从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(例如模型缓存,安全级别或空模型表示形式)时。