freemarker程序开发

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”:

[java] view plain copy

package com.cdtax.freemarker;  

import java.util.List;  

import freemarker.template.SimpleNumber;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;  

public class IndexOfMethod implements TemplateMethodModel
{  

    @Override
    public TemplateModel exec(List arg0) throws TemplateModelException
    {
        if(arg0.size() != 2)
        {
            throw new TemplateModelException("Wrong argments !");
        }
        return new SimpleNumber(((String)arg0.get(1)).indexOf((String)arg0.get(0)));
    }  

}

  

test.ftl:

[html] view plain copy

<html>
<head>
    <title>welcome!</title>
</head>
<body>
    <h1>welcome ${user} !</h1>
    <p>our latest products:
    <a href="${latestproduct.url}">${latestproduct.name}</a>!  

<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count>
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>  

<#macro do_thrice>
<#nested 1>
<#nested 2>
<#nested 3>
</#macro>
<@do_thrice ; x> <#-- 用户自定义指令 使用";"代替"as" -->
${x} Anything.
</@do_thrice>  

<#import "lib/my_test.ftl" as my>
<@my.copyright date="2014-04-01" />
${my.mail}   

<#assign x = "something">
${indexOf("met",x)}
${indexOf("foo",x)}

  

test1.java:

[java] view plain copy

package com.cdtax.freemarker;  

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;  

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;  

public class Test1
{
    public static void main(String[] args) throws Exception
    {
        //Configuration实例是存储FreeMarker应用级设置的核心部分
        Configuration cfg = new Configuration();  

        // 指定模板文件从何处加载的数据源,这里设置成一个文件目录。
        File path1 = new File(System.getProperty("user.dir")+"/src/ceshi");
        cfg.setDirectoryForTemplateLoading(path1);  

        // 指定模板如何检索数据模型,这是一个高级的主题了…
        // 但先可以这么来用:
        cfg.setObjectWrapper(new DefaultObjectWrapper());  

        //获得模板
        Template temp = cfg.getTemplate("test.ftl");  

        // 创建根哈希表
        Map root = new HashMap();  

        // 在根中放入字符串"user"
        root.put("user", "xiaoming");  

        // 为"latestProduct"创建哈希表
        Map latest = new HashMap();  

        // 将它添加到根哈希表中
        root.put("latestproduct", latest);  

        // 在latest中放置"url"和"name"
        latest.put("url", "products/greenmouse.html");
        latest.put("name", "green mouse");  

        root.put("indexOf", new IndexOfMethod());
        Writer out = new OutputStreamWriter(System.out);
        // 将模板和数据模型合并 并输出
        temp.process(root, out);  

        out.flush();
    }
}

  

最终运行结果:

<html>
<head>
<title>welcome!</title>
</head>
<body>
<h1>welcome xiaoming !</h1>
<p>our latest products:
<a href="products/greenmouse.html">green mouse</a>!

1. 0.5
2. 1
3. 1.5
4. 2 Last!

1 Anything.
 
2 Anything.
 
3 Anything.

<p>Copyright (C) 2014-04-01 Julia Smith. All rights reserved.</p>
[email protected]

2
-1

如果需要访问FTL运行时环境(读/写变量,获取本地信息等),则可以使用Environment.getCurrentEnvironment()来获取。

指令:
Java程序员可以使用TemplateDirectiveModel接口在Java代码中实现自定义指令。详情可以参加API文档。
注意:
TemplateDirectiveModel在FreeMarker 2.3.11版本时才加入。用来代替快被废弃的TemplateTransformModel。

实现一个指令,这个指令可以将在它开始标签和结束标签之内的字符都转换为大写形式。就像这个模板:

[html] view plain copy

foo
<@upper>
bar
<#-- 这里允许使用所有的FTL -->

  

[html] view plain copy

<#list ["red", "green", "blue"] as color>
${color}
</#list>
baaz
</@upper>
wombat

  

输出:

foo
   BAR
      RED
      GREEN
      BLUE
   BAAZ
wombat

实现的java代码:

[java] view plain copy

package com.cdtax.freemarker;  

import java.io.IOException;
import java.io.Writer;
import java.util.Map;  

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;  

public class UpperDirective implements TemplateDirectiveModel
{  

    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars,
            TemplateDirectiveBody body) throws TemplateException, IOException
    {
        //检查参数是否传入
        if(!params.isEmpty())
        {
            throw new TemplateModelException("this directive doesn‘t allow parameters.");
        }  

        if(loopVars.length != 0)
        {
            throw new TemplateModelException("this directive doesn‘t allow loop variable.");
        }  

        //是否有非空的嵌套内容
        if(body != null)
        {
            //执行嵌入体部分,和FTL中的<#nested>一样,除了我们使用自己的writer来替代当前的output writer
            body.render(new UpperCaseFilterWriter(env.getOut()));
        }
        else
        {
            throw new RuntimeException("missing body");
        }
    }  

    /**
     * {@link Writer}改变字符流到大写形式,
     * 而且把它发送到另一个{@link Writer}中
     */
    private static class UpperCaseFilterWriter extends Writer
    {
        private final Writer out;
        public UpperCaseFilterWriter(Writer out)
        {
            this.out = out;
        }  

        public void write(char[] cbuf,int off,int len) throws IOException
        {
            char[] transformedCbuf = new char[len];
            for(int i = 0; i < len; i++)
            {
                transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]);
            }
            out.write(transformedCbuf);
        }  

        public void flush() throws IOException
        {
            out.flush();
        }  

        public void close() throws IOException
        {
            out.close();
        }
    }
}

  

现在我们需要创建这个类的实例,然后让这个指令在模板中可以通过名称“upper”来访问(或者是其它我们想用的名字)。一个可行的方案是把这个指令放到数据模型中:

root.put("upper", new com.example.UpperDirective());

但更好的做法是将常用的指令作为共享变量放到Configuration中。
当然也可以使用内建函数new将指令放到一个FTL库(宏的集,就像在模板中,使用include或import)中。
<#-- 也许在FTL中你已经有了实现了的指令 -->
<#macro something>
...
</#macro>
<#-- 现在你不能使用<#macro upper>,但是你可以使用: -->
<#assign upper = "com.example.UpperDirective"?new()>

第二个示例,我们来创建一个指令,这个指令可以一次又一次地执行其中的嵌套内容,这个次数由指定的数字来确定(就像list指令),可以使用<hr>将输出的重复内容分开。这个指令我们命名为“repeat”。示例模板如下:

[html] view plain copy

<#assign x = 1>
<@repeat count=4>
Test ${x}
<#assign x = x + 1>
</@repeat>
<@repeat count=3 hr=true>
Test
</@repeat>
<@repeat count=3; cnt>
${cnt}. Test
</@repeat>

  

输出为:

Test 1
Test 2
Test 3
Test 4
Test
<hr> Test
<hr> Test
1. Test
2. Test
3. Test

指令的实现类为:

[java] view plain copy

package com.cdtax.freemarker;  

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;  

import freemarker.core.Environment;
import freemarker.template.SimpleNumber;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;  

public class RepeatDirective implements TemplateDirectiveModel
{
    private static final String PARAM_NAME_COUNT = "count";
    private static final String PARAM_NAME_HR = "hr";  

    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars,
            TemplateDirectiveBody body) throws TemplateException, IOException
    {
        //处理参数:
        int countParam = 0;
        boolean countParamSet = false;
        boolean hrParam = false;  

        Iterator paramIter = params.entrySet().iterator();  

        while(paramIter.hasNext())
        {
            Map.Entry ent = (Map.Entry)paramIter.next();
            String paramName = (String)ent.getKey();
            TemplateModel paramValue = (TemplateModel)ent.getValue();  

            if(paramName.equals(PARAM_NAME_COUNT))
            {
                if(!(paramValue instanceof TemplateNumberModel))
                {
                    throw new TemplateModelException("The \"" + PARAM_NAME_HR
                            + "\" parameter " + "must be a number.");
                }
                countParam = ((TemplateNumberModel)paramValue).getAsNumber().intValue();
                countParamSet = true;  

                if (countParam < 0)
                {
                    throw new TemplateModelException("The \"" +
                    PARAM_NAME_HR + "\" parameter " + "can‘t be negative.");
                }
            }
            else if(paramName.equals(PARAM_NAME_HR))
            {
                if (!(paramValue instanceof TemplateBooleanModel))
                {
                    throw new TemplateModelException("The \"" +
                    PARAM_NAME_HR + "\" parameter " + "must be a boolean.");
                }  

                hrParam = ((TemplateBooleanModel)paramValue).getAsBoolean();
            }
            else
            {
                throw new TemplateModelException("Unsupported parameter: " + paramName);
            }
        }  

        if(!countParamSet)
        {
            throw new TemplateModelException("The required \""
                    + PARAM_NAME_COUNT + "\" paramter" + "is missing.");
        }  

        if(loopVars.length > 1)
        {
            throw new TemplateModelException("At most one loop variable is allowed.");
        }  

        //执行真正指令的执行部分:  

        Writer out = env.getOut();  

        if(body != null)
        {
            for(int i = 0; i < countParam; i++)
            {
                //如果“hr”参数为真,那么就在所有重复部分之间打印<hr>:
                if (hrParam && i != 0)
                {
                    out.write("<hr>");
                }  

                //如果有循环变量,那么就设置它:
                if(loopVars.length > 0)
                {
                    loopVars[0] = new SimpleNumber(i + 1);
                }  

                //执行嵌入体部分(和FTL中的<nested>一样)。
                //这种情况下,我们不提供一个特殊的writer作为参数:
                body.render(env.getOut());
            }
        }
    }  

}

  

在test1.java中增加:root.put("repeat", new RepeatDirective());

节点变量:

节点变量体现了树形结构中的节点。节点变量的引入是为了帮助用户在数据模型中处理XML文档,但是它们也可以用于构建树状模型,节点变量有下列属性,它们都由TemplateNodeModel接口的方法提供。

? 基本属性:
    ? TemplateSequenceModel getChildNodes():一个节点的子节点序列(除非这个节点是叶子节点,这时方法返回一个空序列或者是null)。子节点本身应该也是节点变量。
    ? TemplateNodeModel getParentNode():一个节点只有一个父节点(除非这个节点是节点树的根节点,这时方法返回null)。
? 可选属性。如果一个属性在具体的使用中没有意义,那对应的方法应该返回null:
    ? String getNodeName():节点名称也是宏的名称,当使用recurse和visit指令时,它用来控制节点。因此,如果想通过节点使用这些指令,那么节点的名称是必须的。
    ? String getNodeType():在XML技术中:"element","text","comment"等类型。如果这些信息可用,就是通过recurse和visit指令来查找节点的默认处理宏。而且,它对其他有具体用途的应用程序也是有用的。
    ? String getNamespaceURI():这个节点所属的命名空间(和用于库的FTL命名空间无关)。例如,在XML中,这就是元素和属性所属XML命名空间的URI。这个信息如果可用,就是通过recurse和visit指令来查找存储控制宏的FTL命名空间。
    在FTL这里,节点属性的直接使用可以通过内建函数node完成,还有visit和recurse宏。

对象包装:

当往容器中添加一些对象时,正如在FreeMarker API文档中看到的那样,它可以收到任意java对象类型的参数,而不一定是TemplateModel。这是因为模板实现时会默默地用合适的TemplateModel对象来替换原有对象。比如向容器中加入一个String,也许它将被替换为一个SimpleScalar实例来存储相同的文本。
至于替换什么时候发生,这就是容器业务处理的问题(类的业务实现了容器接口)所在,但是它在获取子变量时必须会发生,因为getter方法(依据接口而定)会返回TemplateModel,而不是Object。SimpleHash,SimpleSequence和SimpleCollection使用最懒的策略,当第一次获取子变量时,它们用一个适合的TemplateModel来替换一个非TemplateModel子变量。
至于什么类型的Java对象可以被替换,又使用什么样的TemplateModel来实现,它可以被实现的容器自身来控制,也可以委派给ObjectWrapper的一个实例。ObjectWrapper是一个接口,其中只定义了一个方法:TemplateModel wrap(java.lang.Object obj)。可以传递一个Object类型的参数,它会返回对应的TemplateModel对象,如果不行则抛出TemplateModelException异常。替换原则是在ObjectWrapper的实现类中编码实现的。

最重要的ObjectWrapper实现类是FreeMarker核心包提供的:

? ObjectWrapper.DEFAULT_WRAPPER:它使用SimpleScalar来替换String,SimpleNumber来替换Number,SimpleSequence来替换List和数组,SimpleHash来替换Map,TemplateBooleanModel.TRUE或TemplateBooleanModel.FALSE来替换Boolean,freemarker.ext.dom.NodeModel来替换W3C组织定义的DOM模型节点类型。对于Jython类型的对象,包装器会调用freemarker.ext.jython.JythonWrapper。而对于其他对象,则会调用BEAN_WRAPPER。
    ? ObjectWrapper.BEANS_WRAPPER:它可以通过Java 的反射机制来获取到Java Bean的属性和其他任意对象类型的成员变量。在最新的FreeMarker 2.3版本中,它是freemarker.ext.beans.BeansWrapper的实例。

做一个具体的例子,让我们来看看SimpleXxx类型都是怎么工作的。SimpleHash,SimpleSequence和SimpleCollection使用DEFAULT_WRAPPER来包装子变量(除非在构造方法中传递另外一个包装器)。这个例子在实战中来展示DEFAULT_WRAPPER。

Map map = new HashMap();
map.put("anotherString", "blah");
map.put("anotherNumber", new Double(3.14));
List list = new ArrayList();
list.add("red");
list.add("green");
list.add("blue");
SimpleHash root = new SimpleHash(); // 将会使用默认的包装器
root.put("theString", "wombat");
root.put("theNumber", new Integer(8));
root.put("theMap", map);
root.put("theList", list);

  

假设root是数据模型的root,那么得到的数据模型将是:

注意在theMap和theList中的Object也可以作为子变量来访问。这是因为,当要访问theMap.anotherString时,SimpleHash(这里作为根哈希表)会静默地使用SimpleHash实例来替换Map(theMap),这个实例使用了和根哈希表相同的包装器。所以当访问其中的子变量anotherString时,就会使用SimpleScalar来替换它。
如果在数据模型中放了任意的对象,那么DEFAULT_WRAPPER就会调用BEANS_WRAPPER来包装这个对象:

SimpleHash root = new SimpleHash();
// 可以拿到Java对象"simple":
root.put("theString", "wombat");
// 可以拿到Java对象":
root.put("theObject", new TestObject("green mouse", 1200));

假设TestObject是这样的:

[java] view plain copy

public class TestObject
{
    private String name;
    private int price;  

    public TestObject(String name, int price)
    {
        this.name = name;
        this.price = price;
    }  

    // JavaBean的属性
    // 注意公有字段不能直接可见;
    // 你必须为它们编写getter方法。
    public String getName()
    {
        return name;
    }  

    public int getPrice()
    {
        return price;
    }  

    // 一个方法
    public double sin(double x)
    {
        return Math.sin(x);
    }
}

  

数据模型就会是这样:

我们可以这样把它和模板合并:

${theObject.name}
${theObject.price}
${theObject.sin(123)}

输出将如下:

green mouse
1200
-0,45990349068959124

之前我们已经看到了,我们使用java.util.HashMap作为根哈希表,而不是SimpleHash或其他特定的FreeMarker类。因为Template.process(...)自动包装了给定的数据模型参数的对象,所以它才会起作用。它使用受Configuration级设置的对象包装器,object_wrapper(除非明确指定一个ObjectWrapper作为它的参数)。因此,编写简单的FreeMarker应用程序就不需要知道TemplateModel了。注意根的类型不需要一定是java.util.Map。它也可以是实现了TemplateHashModel接口的被包装的对象。
object_wrapper设置的默认值是ObjectWrapper.DEFAULT_WRAPPER。如果想改变它,比如换成ObjectWrapper.BEANS_WRAPPER,那么可以这样来配置FreeMarker引擎(在其它线程开始使用它之前):
cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);

要注意我们可以在这里设置任何对象实现接口ObjectWrapper,当然也可以用来设置你自己定义的实现类。

对于包装了基本Java容器类型(比如java.util.Map和java.util.List)的TemplateModel实现类,常规是它们使用像它们父容器那样的相同对象包装器来包装它们的子变量。从技术上讲,它们是被父容器(它对所创建的子类有全部的控制器)实例化的,因为父容器创建了它们,所以它们使用和父容器一样的对象包装器。如果BEANS_WRAPPER用来包装根哈希表,那么它也会被用来包装子变量(子变量的子变量也是如此,以此类推)。这个之前看到的theMap.anotherString是同样的现象。

时间: 2024-10-09 22:28:23

freemarker程序开发的相关文章

【FreeMarker】【程序开发】数据模型,对象包装

[FreeMarker][程序开发]数据模型,对象包装 分类: Java.FreeMarker2014-10-25 18:49 413人阅读 评论(0) 收藏 举报 FreeMarker 目录(?)[+] 在简单的示例中,可以使用 java.lang 和 java.util 包下的类,还有用户自定义的 Java Bean来构建数据对象. 使用 java.lang.String 来构建字符串 使用java.lang.Number 来派生数字类型 使用 java.lang.Boolean 来构建布尔

freemarker 模板开发入门

数据模型 scalars标量:从根 root 开始指定它的路径,每级之间用点来分隔. 如:whatnot.fruits sequences 序列:使用数组的方括号方式来访问一个序列的子变量. 如:animals[0].name,whatnot.fruits[1] 总结: 数据模型可以被看做是树状结构的. 标量存储单一的值,这种类型的值可以是字符串,数字,日期/时间或者是布尔值. 哈希表是存储变量和与其相关且有唯一标识名称变量的容器. 序列是存储有序变量的容器.存储的变量可以通过数字索引来检索,索

FreeMarker模板开发指南知识点梳理

freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么? FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具. 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件. 对于像我一样之前完全对freemarker没概念的来说,这种解释还是无法释疑解惑,需要说的再详尽点. freemarker有什么用? 模板编写

Java微信小程序开发_00_资源帖

1.微信小程序开发:http://blog.csdn.net/column/details/13721.html?&page=1 2.微信小程序栏目:http://blog.csdn.net/column/details/14653.html?&page=2 3.

微信小程序开发视频教程

课程:拜师学艺,微信小程序从入门到大神必经之路(第一季,共四季) 课程观看地址:http://www.xuetuwuyou.com/course/172/lesson/list 课程出自学途无忧网:http://www.xuetuwuyou.com 讲师:风舞烟 课时列表: 第一部_诞生 - 天降大任:名门贵族,互联网开发新宠--小程序横空出世 第1章 : 忽如一夜春风来--小程序的前世今生 课时1:课程开场 课时2:本章目标及任务 课时3:第一回合_小王子出世-微信小程序_小程序为什么这么火

Office 365 - SharePoint 2013 Online之应用程序开发工具

1.新建一个网站集,模板选择开发人员模板,如下图: 2.确定以后,需要稍等一会儿; 3.点击网站内容,添加app,如下图: 4.进入SharePoint Store,选择Napa,如下图: 5.选择ADD IT,如下图: 6.可能需要登录,如果没有微软账号,可以注册一个,如下图: 7.点击继续,如下图: 8.Return to site,如下图: 9.点击信任他,如下图: 10.稍等片刻,就添加成功了,如下图: 11.点击进入Napa,可以在这里创建app,如下图: 总 结 试用了一下Napa,

微信小程序开发心得

微信小程序也已出来有一段时间了,最近写了几款微信小程序项目,今天来说说感受. 首先开发一款微信小程序,最主要的就是针对于公司来运营的,因为,在申请appid(微信小程序ID号)时候,需要填写相关的公司认证信息如,营业执照等 再次就是用一个未曾开通过公众号的QQ号或微信号来注册一个微信小程序号. 最后,下载微信小程序开发工具. 由于这里,我们更多的关注如何去开发一些app,而不是科谱微信小程序,故在此不在过多的解释,详细的说明,可以去官网帮助文档. 首先,我们拿自己的项目在一步一步的说明并开发吧,

微信小程序开发 --02

微信小程序在开发中,难度系数不是很大,其中应用的技术也是web开发中常用的技术,虽然在微信开发者工具中的叫法与常见的web开发的叫法不太一样. 首先,在微信小程序开发中,代码文件格式大体有以下四中: .wxml .wxss .js .json 首先,wxml后缀的文件类似于html和xml的结合,例如在html中常用的div在微信小程序开发中被替换成了view,而在html中输出文本用的p标签被替换成了text标签,引入图像由html中的img 标签变成了image标签,等等.如果你之前开发的w

微信小程序开发之数据存储 参数传递 数据缓存

微信小程序开发内测一个月.数据传递的方式很少.经常遇到页面销毁后回传参数的问题,小程序中并没有类似Android的startActivityForResult的方法,也没有类似广播这样的通讯方式,更没有类似eventbus的轮子可用. 现在已知传递参数的方法只找到三种,先总结下.由于正处于内测阶段,文档也不是很稳定,经常修改,目前尚没有人造轮子. 先上GIF: 1.APP.js 我把常用且不会更改的参数放在APP.js的data里面了.在各个page中都可以拿到var app = getApp(