FreeMarker是什么?


摘自百度百科

FreeMarker允许Javaservlet保持图形设计同应用程序逻辑的分离,这是通过在模板中密封HTML完成的。模板用servlet提供的数据动态地生成 HTML。模板语言是强大的直观的,编译器速度快,输出接近静态HTML页面的速度。

FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写

FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序

  虽然FreeMarker具有一些编程的能力,但通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据

FreeMarker不是一个Web应用框架,而适合作为Web应用框架一个组件

FreeMarker与容器无关,因为它并不知道HTTPServletFreeMarker同样可以应用于非Web应用程序环境

FreeMarker更适合作为Model2框架(如Struts)的视图组件,你也可以在模板中使用JSP标记库

FreeMarker是免费的


类似于java的国际化

ResourceBundle

user=hello{0},welcome to {1}


1.预留填充

2.编程能力


1、模板文件

实际上你用程序语言编写的程序就是模板,模板也被称为FTL(代表FreeMarkerTemplate Language)。这是为编写模板设计的非常简单的编程语言。

结构

主要有四部分:文本、注释<#-- -->、插值${…} #{…}FTL指令组成。

Text文本:文本会照着原样来输出。

Interpolation插值:这部分的输出会被计算的值来替换。插值由${}所分隔(或者#{},这种风格已经不建议再使用了)。

FTL tags标签:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示,而且不会打印在输出内容中。

Comments注释:FTL的注释和HTML的注释也很相似,但它们是由<#---->来分隔的。注释会被FreeMarker所忽略,更不会在输出内容中显示。

我们来看一个具体的模板,其中的内容已经用颜色来标记了:文本插值FTL标签注释,为了看到可见的换行符,这里使用了[BR]

<html>[BR]

<head>[BR]

<title>Welcome!</title>[BR]

</head>[BR]

<body>[BR]

<#-- Greet the user with his/her name -->[BR]

<h1>Welcome ${user}!</h1>[BR]

<p>We have these animals:[BR]

<ul>[BR]

<#list animals as being>[BR]

<li>${being.name} for ${being.price}Euros[BR]

</#list>[BR]

</ul>[BR]

</body>[BR]

</html>

FTL是区分大小写的。插值仅仅可以在文本中间使用(也可以在字符串表达式中)FTL标签不可以在其他FTL标签和插值中使用。注释可以在FTL标签和插值中间。

注意:

如果目前您已经自己尝试了上面所有的示例的话,那么你也许会注意一些空格、制表符和换行符从模板输出中都不见了,尽管我们之前已经说了文本是按照原样输出的。现在不用为此而计较,这是由于FreeMarker空格剥离特性在起作用,它当然会自动去除一些多余的空格,制表符和换行符了。


FTL指令规则

标签分为两种:开始标签:<#directivenameparametes>结束标签:</#dirctivename>

除了标签以#开头外,其他和HTMLXML的语法很像。如果标签没有嵌套内容(在开始标签和结束标签之内的内容),那么可以只使用开始标签。

指令有两种类型:预定义指令和用户自定义指令。对于用户自定义指令使用@来代替#

FreeMarker仅仅关心FTL标签的嵌套而不关心HTML标签的嵌套,他只会吧HTML看做是文本,不会解释HTML

如果你尝试使用一个不存在的指令,FreeMarker就会拒绝执行模板,同时会抛出错误信息。FreeMarker会忽略FTL标签中的多余空白。当然,也不能在<<\和指令名中间插入空白标记。

插值规则

插值的使用语法是:${ exception}exception可以是所有种类的表达式(比如${100+x})。

插值是用来插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:文本区(如<h1>Hello ${name}!</h1>)和字符串表达式(如<#include“/footer/${company}.html”>)中。

警告:要在不能使用插值的地方使用它。例如:<#if ${isBig}>Wow</#if>,这就是语法上的错误。只要写成<#ifisBig>Wow</#if>就对了,而且<#if “${isBig}”>Wow</#if>也是错误的,因为这样的参数就是字符串类型了,但是if指令的参数要求是布尔值,所以运行时就会发生错误。

插值表达式的结果必须是字符串,数字或日期类型的,因为只有数字和日期类型可以自动转换为字符串类型,其他类型的值(如布尔类型、序列)只能手动转换为字符串类型,否则就会发生错误导致模板执行中止。

数字插入指南

如果表达式是数字类型,那么根据数字的默认格式,数值将会转换为字符串。这也许会包含最大、最小数,数字分组和近似处理的问题。通常程序员应该设置默认的数字格式,而模板设计者不需要处理它(但是可以使用number_format设置来进行,或使用内建函数string为一个插值来重写默认数值格式)。

日期/时间插入指南

如果表达式的值是时间日期类型,那么日期中的数字将会按照默认格式来转换成文本。通常程序员应该设置默认格式。

布尔值插入指南

若要使用插值方式来打印布尔值会引起错误,中止模板执行。例如:${a==2}就会引起错误,它不会打印“true”或者其他内容。

然而,我们可以使用内建函数string来将布尔值转换为字符串形式。比如打印变量“married”(假设它是布尔值),那么可以这么来写:${married?(“yes”,”no”)}

精确的转换规则

表达式的值转换为字符串精确的规则:

A.如果这个值是数字,那么它会按照指定的number_format设置规则来转换为字符串。

B.如果这个值是日期,时间或时间日期类型中的一种,那么他们就会按照time_formatdate_formatdatetime_format设置规则来转换为字符串,这要看日期信息中是只包含日期、时间还是全包括了。如果它不被探测出来是那种日期类型时,就会报错。

C.如果值本来就是字符串类型的,不需要转换。

D.如果freemarker引擎在传统兼容模式下:

1、如果值是布尔类型,那么就转换成“true”,false值将会转换为空串。

2、如果表达式未被定义(null或者变量未定义),那么就转换为空串

3、否则就会发生错误终止模版执行

E.否则就会发生错误中止模板执行


2、表达式

直接指定值

1.字符串

直接输出插值中的值,而不是变量的值。可以是字符串、数值、布尔、集合、map对象等。在文本中确定字符串的方法是看双引号和单引号,这两种形式是相等的。如果文本本身包含用于字符串引用的引号或反斜杠时,应该在他们的前面加一个反斜杠,这就是转义。转移允许你直接在文本中输入任何字符,也包括反斜杠。

例如:${“测试 C:\\”}${‘测试’}

2.数值

输入不带引号的数字就可以直接指定一个数字,必须使用点作为小数的分隔符而不能是其他的分组分隔符。可以使用-+来表明符号(+是多余的)。科学记数法暂不支持使用(1E3就是错误的),而且也不能在小数点之前不写0.5也是错误的)。

3. 布尔

直接写truefalse就表征一个布尔值了,不需使用引号。

4. 集合

指定一个文字的序列,使用逗号来分隔其中的每个子变量,然后把整个列表放到方括号中。


<#list[“winter”,“spring”,“summer”,“autumn”] as x>
    ${x}
</#list>

5. map

类似json的样子

用来检索的名字就必须是字符串类型的。


输出变量值

可以是顶层变量,也可以是listmap中的,可以使用.来访问java对象的属性

Map root = new HashMap();
root.put(“name”,”云飞日月”);

${name}


集合

${list[2]}


字符串操作

1、插值(或连接)

在字符串中插入表达式的值:${“Hello ${user} !”}${“${user}${ user }${ user }${ user }”}

使用“+”连接多个字符串和多次使用${...}效果是一样的:

${“Hello” + user + “!”}${user +user + user + user}


2、获取一个字符

使用索引值来直接引用一个字符串中的字符,FTL并没有独立的字符类型。所谓的字符就是长度为1的字符串。如果索引值超出字符串的长度,模版的执行就会发生错误并终止。

假设user是“Big joe”:

${user[0]}-- B

${ user[4]}--J


集合连接运算符

1、连接

序列的连接可以使用+号来进行:

<#list ["Joe","Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>

2、序列切分

使用[firstindex..lastindex]可以获取序列中的一部分,这里的firstindexlastindex表达式的结果是数字。比如:要找序列中的第三个到第五个[3..5]

Map连接运算符

连接:

像连接字符串那样,也可以使用+号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项目优先(覆盖原来的数据)。例如:

<#assign ages = {"Joe":23,"Fred":25} + {"Joe":30, "Julia":18}>


算术运算符

算数运算包含基本的四则运算和求模运算,运算符有:

加法:+

减法:-

乘法:*

除法:/

求模(求余):%

例:${100-x*x+y%2}

比较运算符

测试两个值相等使用=(或者采用JavaC语言中的==,二者是完全等同的。)

测试两个值不等使用!=

=!=两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型(也就是说字符串只能和字符串来比较,数字只能和数字来比较等。区分大小写)。否则将会出错,模板执行中断。

大于:gt / >

小于:lt / <

大于等于: gte / >=

小于等于: lte / <=

逻辑运算符

常用的逻辑操作符:

l 逻辑或:||

l 逻辑与:&&

l 逻辑非:!


逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。

内建函数

内建函数:系统内置的函数。

内建函数以?形式提供变量的不同形式或者其他信息。使用内建函数的语法和访问哈希表子变量的语法很像,除了使用?号来代替点,其他的都一样。例如得到字符串的大写形式:user?upper_case


n 字符串使用的内建函数:

u html: 字符串中所有的特殊HTML字符都需要用实体引用来代替(比如<代替&lt;

u cap_first:字符串的第一个字母变为大写形式

u lower_case:字符串的小写形式

u upper_case:字符串的大写形式

u trim:去掉字符串首尾的空格

n 序列使用的内建函数:

u size:序列中元素的个数

n 数字使用的内建函数:

u int:数字的整数部分(比如-1.9?int就是-1



空值运算


3、常用指令

If
<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
...
<#else>
...
</#if>

这里:

l conditioncondition2等:表达式将被计算成布尔值。

<#if x > 1>是不对的,因为FreeMarker将会解释第一个>作为结束标记。因此,编写<#if (x >1)><#if x &gt; 1>是正确的。

switch casedefault break
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
</#switch>

这里:

l valuerefValue1等:表达式将会计算成相同类型的标量。


list
<#list sequence as item>
...
</#list>

这里:

l sequence:表达式将被算作序列或集合

l item:循环变量(不是表达式)的名称


Include

<#include path>

or

<#include path options>

这里:

l path:要包含文件的路径;一个算作是字符串的表达式。(用其他话说,它不用是一个固定的字符串,它也可以是像profile.baseDir + "/menu.ftl"这样的东西。)

l options:一个或多个这样的选项:encoding=encoding, parse=parse

l encoding:算作是字符串的表达式

l parse:算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)


使用获得机制:

有一个特殊的路径组成,是用一个星号(*)来代表的。它被解释为当前目录或其他任意它的父目录

如果模板在/foo/bar/template.ftl位置上,有下面这行:

<#include"*/footer.ftl">

那么引擎就会在下面的位置上寻找模板,并按这个顺序:

l /foo/bar/footer.ftl

l /foo/footer.ftl

l /footer.ftl

本地化查找:

当一个模板包含另一个模板时,它试图加载以相同的本地化环境加载模板。假定你的模板以本地化en_US来加载,那就意味着是U.S. English。当你包含另外一个模板:

<include"footer.ftl">

那么引擎实际上就会寻找一些模板,并按照这个顺序:

l footer_en_US.ftl

l footer_en.ftl

l footer.ftl


要注意你可以使用ConfigurationsetLocalizedLookup方法关闭本地化查找特性。



类似jsp包含

<#include filename [options]>

options 可以省略,包含encodingparse选项

parse的意思是包含文件是否作为ftl来解析


import

<#impoort path as mapObject>

导入模板中所有变量,放入指定的map

<#import “/lib/common.ftl” as com>


noparse

<#noparse>

...

</#noparse>

FreeMarker不会在这个指令体中间寻找FTL标签,插值和其他特殊的字符序列,除了noparse的结束标记。


assign

<#assign name=value>

or

<#assign name1=value1name2=value2 ... nameN=valueN>

or

<#assign same as above...in namespacehash>

or

<#assign name>

capture this

</#assign>

or

<#assign name in namespacehash>

capture this

</#assign>

这里:

l name:变量的名字。不是表达式。而它可以本写作是字符串,如果变量名包含保留字符这是很有用的,比如<#assign "foo-bar" = 1>。注意这个字符串没有展开插值(如"${foo}")。

l value:存储的值。是表达式。

l namespacehash:(通过import)为命名空间创建的哈希表。是表达式。


macro
<#macro name param1param2 ... paramN>
...
<#nested loopvar1, loopvar2,..., loopvarN>
...
<#return>
...
</#macro>

这里:

l name:宏变量的名称,它不是表达式。然而,它可以被写成字符串的形式,如果宏名称中包含保留字符时这是很有用的,比如<#macro "foo-bar">...。注意这个字符串没有扩展插值(如"${foo}")。

l param1param2等: 局部变量的名称,存储参数的值(不是表达式),在=号后面和默认值(是表达式)是可选的。默认值也可以是另外一个参数,比如<#macro section title label=title>

l paramN,最后一个参数,可以可选的包含一个尾部省略(...),这就意味着宏接受可变的参数数量。如果使用命名参数来调用,paramN将会是包含给宏的所有未声明的键/值对的哈希表。如果使用位置参数来调用,paramN将是额外参数的序列。

l loopvar1loopvar2等:可选的循环变量的值,是nested指令想为嵌套内容创建的。这些都是表达式。


returnnested指令是可选的,而且可以在<#macro></#macro>之间被用在任意位置和任意次数。

没有默认值的参数必须在有默认值参数(paramName=defaultValue)之前。

4Java操作FreeMarker

经典应用

185312340.png

使用FreeMarker模板,生成常用的代码。可以是数据库连接的Dao类。从而方便程序员的开发。

1、编写模板文件



public class ${name}Dao{
    public List<${name}> findAll() {
        return null;
    }
    public int deleteById(int id) {
        String sql = "delete from ${name} where id =" + id;
        Connection conn = JdbcUtil.getConnection();
        Statement st = null;
        int result= 0;
        try {
            st= conn.createStatement();
            result= st.executeUpdate(sql);
        }catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
             JdbcUtil.closeAll(st,null, conn);
        }
        return result;
    }
    /**
    * 根据ID查询
    *
    * @param id
    * @return
    */
    public ${name} findById(int id) {
        Stringsql = "select * from ${name} where id =" + id;
        Connectionconn = JdbcUtil.getConnection();
        PreparedStatementps = null;
        ResultSetrs = null;
        ${name} p = null;
        try {
            ps= conn.prepareStatement(sql);
            rs= ps.executeQuery();
            while(rs.next()) {
                p= new ${name}();
                p.setId((int)rs.getDouble(1));
                p.set${name}Name(rs.getString(2));
                p.setPrice(rs.getDouble(3));
                p.set${name}Information(rs.getString(4));
                p.setImage(rs.getString(5));
                p.setCcrq(rs.getDate(6));
                p.setCount((int)rs.getDouble(7));
                p.setPinpai(rs.getString(8));
                p.setZhekou(rs.getDouble(9));
                doubletypeId = rs.getDouble(10);
                p.setType(new${name}TypeDao().findById((int) typeId));
             }
        }catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.closeAll(ps,rs, conn);
        }
        returnp;
    }
    /**
    * 分页查询
    *
    * @return
    */
    public List<${name}> findByScoll(PageSupport page) {
        String sql = "select * from (select id,${name}name,price,${name}information,p_w_picpath,ccrq,count,"
+"pinpai,zhekou,${name}_type_id ,rownumrn from ${name}) "
+" where rnbetween ? and ?";
        Connection conn = JdbcUtil.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<${name}> list = new ArrayList<${name}>();
        try {
            ps= conn.prepareStatement(sql);
            System.out.println(page.getBegin());
            ps.setInt(1,page.getBegin());
            ps.setInt(2,page.getEnd());
            rs= ps.executeQuery();
            System.out.println(page.getBegin());
            System.out.println(page.getEnd());
            while(rs.next()) {
                ${name} p = new ${name}();
                p.setId((int)rs.getDouble(1));
                p.set${name}Name(rs.getString(2));
                p.setPrice(rs.getDouble(3));
                p.set${name}Information(rs.getString(4));
                p.setImage(rs.getString(5));
                p.setCcrq(rs.getDate(6));
                p.setCount((int)rs.getDouble(7));
                p.setPinpai(rs.getString(8));
                p.setZhekou(rs.getDouble(9));
                doubletypeId = rs.getDouble(10);
                p.setType(new${name}TypeDao().findById((int) typeId));
                list.add(p);
            }
        }catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.closeAll(ps,rs, conn);
        }
         return list;
    }
    public int obtainTotal() {
        String sql = "select count(1) from ${name}";
        Connection conn = JdbcUtil.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        int total = 0;
        try {
            ps= conn.prepareStatement(sql);
            rs= ps.executeQuery();
            while(rs.next()) {
                total= rs.getInt(1);
            }
        }catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.closeAll(ps,rs, conn);
        }
        return total;
    }
    /**
    * 分页查询并且排序
    *
    * @return
    */
    public List<${name}> findByScollandOrder(PageSupport page,Map<String, String> order) {
        return null;
    }
    public synchronized int getLastId() {
        Connection conn = JdbcUtil.getConnection();
        String sql = "select max(id) from ${name}";
        PreparedStatement ps = null;
        ResultSet rs = null;
        int id = 0;
        try {
            ps= conn.prepareStatement(sql);
            rs= ps.executeQuery();
            while(rs.next()) {
                id= (int) rs.getDouble(1);
                id++;
            }
        } catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.closeAll(ps,rs, conn);
        }
        return id;
    }
    public int save(${name} p) {
        Connection conn = JdbcUtil.getConnection();
        String sql = "insert into ${name} values(?,?,?,?,?,?,?,?,?,?)";
        PreparedStatement ps = null;
        int result = 0;
        try {
            ps= conn.prepareStatement(sql);
            ps.setDouble(1,getLastId());
            ps.setString(2,p.get${name}Name());
            ps.setDouble(3,p.getPrice());
            ps.setString(4,p.get${name}Information());
            ps.setString(5,p.getImage());
            ps.setDate(6,new java.sql.Date(p.getCcrq().getTime()));
            ps.setDouble(7,p.getCount());
            ps.setString(8,p.getPinpai());
            ps.setDouble(9,p.getZhekou());
            ps.setDouble(10, p.getType().getId());
            result= ps.executeUpdate();
        }catch (SQLException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.closeAll(ps,null, conn);
        }
        returnresult;
    }
}

2、利用freemarker填充数据,并将填充了数据的模板文件持久化到本地

public class DaoTemplate {
    public static void main(String[] args) throws Exception {
        // 模板,数据-->完整输出
        // 配置
        Configurationcfg = newConfiguration();
        // 在哪个文件夹下找ftl模板文件
        cfg.setDirectoryForTemplateLoading(new File(""));
        // 加载ftl文件
        Templatet = cfg.getTemplate("dao.ftl", "UTF-8");
        String[] aa = {"Product","Topic","User"};
        for (String s : aa) {
            Map<String,Object> data = new HashMap<String, Object>();
            data.put("name", s);
            Filef = newFile(s+"Dao.java");
            FileOutputStreamout = newFileOutputStream(f);
            t.process(data,newOutputStreamWriter(out));
        }
    }
}