1、程序开发入门
1.1 创建配置实例
首先,你应该创建一个freemarker.template.Configuration的实例,然后调整它的设置。Configuration实例是存储FreeMarker应用级设置的核心部分。同时,它也处理创建和缓存预解析模板的工作。也许你只在应用(可能是servlet)生命周期的开始执行它一次:
Configuration cfg = new Configuration(); // 指定模板文件从何处加载的数据源,这里设置成一个文件目录。 cfg.setDirectoryForTemplateLoading( new File("/where/you/store/templates")); // 指定模板如何检索数据模型,这是一个高级的主题了… // 但先可以这么来用: cfg.setObjectWrapper(new DefaultObjectWrapper());
从现在开始,应该使用单实例配置。要注意不管一个系统有多少独立的组件来使用FreeMarker,它们都会使用他们自己私有的Configuration实例。
1.2 创建数据模型
在简单的示例中你可以使用java.lang和java.util包下的类,还有用户自定义的Java Bean来构建数据对象。 使用java.lang.String来构建字符串。 使用java.lang.Number来派生数字类型。 使用java.lang.Boolean来构建布尔值。 使用java.util.List或Java数组来构建序列。 使用java.util.Map来构建哈希表。 使用你自己定义的bean类来构建哈希表,bean中的项和bean的属性对应。例如product中的price属性可以用product.price来获取
构建数据模型的java代码
// 创建根哈希表 Map root = new HashMap(); // 在根中放入字符串"user" root.put("user", "Big Joe"); // 为"latestProduct"创建哈希表 Map latest = new HashMap(); // 将它添加到根哈希表中 root.put("latestProduct", latest); // 在latest中放置"url"和"name" latest.put("url", "products/greenmouse.html"); latest.put("name", "green mouse");
对于latestProduct你也可以使用有url和name属性的Java Bean(也就是说,对象要有公共的String getURL()和String getName()方法);它和模板的观点相同。
1.3 获得模板
模板代表了freemarker.template.Template的实例。典型的做法是从Configuration实例中获取一个Template实例。无论什么时候你需要一个模板实例,都可以使用它的getTemplate方法来获取。在之前设置的目录中,用test.ftl存储示例模板,那么就可以这样来做:
Template temp = cfg.getTemplate("test.ftl");
当调用这个方法的时候,将会创建一个test.ftl的Template实例,通过文件读取,然后解析(编译)它。Template实例以解析后的形式存储模板,而不是以源文件的文本形式。 Configuration缓存Template实例,当再次获得test.ftl时,它可能不会创建新的Template实例(因此不会读取和解析文件),而是返回第一次创建的实例。
1.4 合并模板和数据模型
我们都已经知道的,数据模型+模板=输出,我们已经有了一个数据模型(root)和一个模板(temp)了,所以为了得到输出就需要合并它们。这是由模板的process方法完成的。它用数据模型的根和Writer对象作为参数,然后向Writer对象写入产生的内容。为简单起见,这里我们只做标准的输出:
Writer out = new OutputStreamWriter(System.out); temp.process(root, out); out.flush();
一旦获得了Template实例,就能将它和不同的数据模型进行不限次数(Template实例是无状态的)的合并。此外,当Template实例创建之后test.ftl文件才能访问,而不是调用处理方法时。
2、 数据模型
现在,我们已经知道如何使用基本的Java类(Map,String等)构建一个数据模型了。在内部,模板中可用的变量都是实现了freemarker.template.TemplateModel接口的Java对象。但在你自己的数据模型中,可以使用基本的Java集合类作为变量,因为这些变量会在内部被替换为适当的TemplateModel类型。这种功能特性被称作是object wrapping对象包装。对象包装功能可以透明地把任何类型的对象转换为实现了TemplateModel接口类型的实例。这就使得下面的转换成为可能,如在模板中把java.sql.ResultSet转换为序列变量,把javax.servlet.ServletRequest对象转换成包含请求属性的哈希表变量,甚至可以遍历XML文档作为FTL变量。包装(转换)这些对象,需要使用合适的,也就是所谓的对象包装器实现(可能是自定义的实现);这将在后面讨论。现在的要点是想从模板访问任何对象,它们早晚都要转换为实现了TemplateModel接口的对象。那么首先你应该熟悉来写TemplateModel接口的实现类。
有一个freemarker.template.TemplateModel粗略的子接口对应每种基本变量类型(TemplateHashModel对应哈希表,TemplateSequenceModel对应序列,TemplateNumberModel对应数字等等)。例如,想为模板使用java.sql.ResultSet变量作为一个序列,那么就需要编写一个TemplateSequenceModel的实现类,这个类要能够读取java.sql.ResultSet中的内容。我们常这么说,你使用TemplateModel的实现类包装了java.sql.ResultSet,基本上只是封装java.sql.ResultSet,来提供使用普通的TemplateSequenceModel接口访问它。要注意一个类可以实现多个TemplateModel接口,这就是为什么FTL变量可以有多种类型
注意这些接口的一个细小的实现是和freemarker.template包一起提供的。例如,将一个String转换成FTL的字符串变量,可以使用SimpleScalar,将java.util.Map转换成FTL的哈希表变量,可以使用SimpleHash等等。 如果想尝试自己的TemplateModel实现,一个简单的方式是创建它的实例,然后将这个实例放入数据模型中(也就是把它放在哈希表的根上)。对象包装器将会给模板提供它的原状,因为它已经实现了TemplateModel接口,所以没有转换(包装)的需要。(这个技巧当你不想用对象包装器来包装(转换)某些对象时仍然有用。
(1)标量
有4种类型的标量: 布尔值 数字 字符串 日期 每一种标量类型都是TemplateTypeModel接口的实现,这里的Type就是类型的名称。这些接口只定义了一个方法type getAsType();它返回变量的Java类型(boolean,Number,String和Date各自代表的值)的值。
注意:由于历史遗留的原因,字符串标量的接口是TemplateScalarModel,而不是TemplateStringModel。 这些接口的一个细小的实现和SimpleType类名在freemarker.template包中是可用的。但是却没有SimpleBooleanModel类型;为了代表布尔值,可以使用TemplateBooleanModel.TRUE和TemplateBooleanModel.FALSE来单独使用。 注意:由于历史遗留的原因,字符串标量的实现类是SimpleScalar,而不是SimpleString。 在FTL中标量是一成不变的。当在模板中设置变量的值时,使用其他的实例来替换TemplateTypeModel实例时,是不用改变原来实例中存储的值的。
(2)数据类型的难点
数据类型还有一些复杂,因为Java API通常不区别java.util.Date,只存储日期部分(April 4, 2003),时间部分(10:19:18 PM),或两者都存(April 4, 2003 10:19:18 PM)。为了用文本正确显示一个日期变量,FreeMarker必须知道java.util.Date的哪个部分存储了有意义上的信息,哪部分没有被使用。不幸的是,Java API在这里明确的说,由数据库控制(SQL),因为数据库通常有分离的日期,时间和时间戳(又叫做日期-时间)类型,java.sql有3个对应的java.util.Date子类和它们相匹配。 TemplateDateModel接口有两个方法:分别是java.util.Date getAsDate()和int getDateType()。这个接口典型的实现是存储一个java.util.Date对象,加上一个整数来辨别“数据库存储的类型”。这个整数的值也必须是TemplateDateModel接口中的常量之一:DATE,TIME,DATETIME和UNKNOWN。 什么是UNKNOWN呢?我们之前说过,java.lang和java.util下的类通常被自动转换成TemplateModel的实现类,就是所谓的对象包装器。当对象转换器面对一个java.util.Date对象时,而不是java.sql日期类的实例,它就不能确定“数据库存储的类型”是什么,所以就使用UNKNOWN。往后执行,如果模板需要使用这个变量,操作也需要使用“数据存储的类型”,那就会停止执行并抛出错误。为了避免这种情况的发生,对于那些可能有问题的变量,模板开发人员需要帮助FreeMarker决定“数据库存储的类型”,使用内建函数date,time或datetime就可以解决了。注意一下,如果对要格式化参数使用内建函数string,比如foo?string("MM/dd/yyyy"),那么FreeMarker就不必知道“数据库存储的类型”了。
(3)容器
容器包括哈希表,序列和集合三种类型。
哈希表:
FreeMarker中的哈希表是实现了TemplateHashModel接口的Java对象。TemplateHashModel接口有两个方法:TemplateModel get(String key),这个方法根据给定的名称返回子变量,boolean isEmpty()这个方法表明哈希表是否含有子变量。get方法当在给定的名称没有找到子变量时返回null。 TemplateHashModelEx接口扩展了TemplateHashModel接口。它增加了更多的方法,使得可以使用内建函数values和keys来枚举哈希表中的子变量。 经常使用的实现类是SimpleHash,该类实现了TemplateHashModelEx接口。从内部来说,它使用一个java.util.Hash类型的对象存储子变量。SimpleHash类的方法可以添加和移除子变量。这些方法应该用来在变量被创建之后直接初始化。 在FTL中,容器是一成不变的。那就是说你不能添加,替换和移除容器中的子变量。
序列:
序列是实现了TemplateSequenceModel接口的Java对象。它包含两个方法:TemplateModel get(int index)和int size()。 经常使用的实现类是SimpleSequence,该类内部使用一个java.util.List类型的对象存储它的子变量。SimpleSequence有添加子元素的方法。在序列创建之后应该使用这些方法来填充序列。
集合:
集合是实现了TemplateCollectionModel接口的Java对象。这个接口只定义了一个方法:TemplateModelIterator iterator()。TemplateModelIterator接口和java.util.Iterator相似,但是它返回TemplateModel而不是Object,而且它能抛出TemplateModelException异常。 通常使用的实现类是SimpleCollection。
方法:
方法变量在存于实现了TemplateMethodModel接口的模板中。这个接口仅包含一个方法:TemplateModel exec(java.util.List arguments)。当使用方法调用表达式调用方法时,exec方法将会被调用。形参将会包含FTL方法调用形参的值。exec方法的返回值给出了FTL方法调用表达式的返回值。 TemplateMethodModelEx接口扩展了TemplateMethodModel接口。它没有任何新增的方法。事实上这个对象实现这个标记接口暗示给FTL引擎,形式参数应该直接以TemplateModel形式放进java.util.List。否则将会以String形式放入List。 一个很明显的原因是这些接口没有默认的实现。
例如这个方法,返回第一个字符串在第二个字符串第一次出现时的索引位置,如果第二个字符串中不包含第一个字符串,则返回“-1”: