愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

【转载】FreeMarker学习总结

转自KaolaFed 原文https://github.com/kaola-fed/blog/issues/84
作者:xiechuanlong

1.前言

工程中,由于ftl模板是由前端人员写的,因此在需求开发中经常会接触ftl模板,所以有必要对ftl模板进行学习。我们的标题是FreeMarker,而与我们的ftl有什么关系呢,freeMarker其实是一个java模板引擎,用来编译模板,是将ftl模板与数据结合生成我们想要的html等静态文件。就像我们运用regular开发一样,动态模板和数据,经过regular内置的模板引擎编译成我们需要的Living-DOM一样。因此它的工作模式可以用下图简单表示

img5
本文只是一些FreeMarker重要的且常用到的知识点的一个梳理总结(结合自己的理解),非常适合新接触FreeMarker的人快速的理解并在项目中使用它,若想进一步了解并学习高级用法可以参考相应文档和书籍。所以我们接下来关注的是ftl模板是如何运用的。本文内容如下图:

freemarker

2. 主要内容

2.1 ftl模板

ftl模板主要有以下四部分的内容:文本、插值、注释和指令。

  • 文本 与html类似,直接输出
  • 插值: ${},中间是动态数据填充
  • 注释: <#--....-->
  • 指令: 一般是以<# 和<@开头的标签

其中插值和指令是核心部分将详细讲解。

2.1.1 插值

插值是用来插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:

a. 文本区(如<h1>Hello ${name}!</h1>)

b. 字符串表达式(如<#include "/footer/${company}.html">)中。

注1:不能在其地方使用插值:比如<#if ${isBig}>Wow!</#if>、且<#if "${isBig}">Wow! </#if>也是错误的,因为这样参数就是字符串类型了,但是if指令的参数要求是布尔值,所以运行时就会发生错误。

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

  • 数字插值

会自动转换为文本,但是可以通过指令setting来为所有的数字插值设置转换格式,或者运用内建函数为某一个插值来重写默认数值格式。

  • 日期/时间插值(java中含有日期类型):

会自动转换为文本,与数字插值一样可通过指令setting来设置转换格式,或者运用内建函数为某一个插值来重写默认数值格式。

  • 插值为布尔值:

不会自动转换为文本,可以使用内建函数string来转换成文本字形式。

比如:

<div>${a == 2}</div>

这样是会报错中断执行的,使用内建函数如下,假设a是一个布尔值,可以这么使用:

<div>${a?string("yes","no")}</div>

a为true时会输出yes,为false时输出no。

注:1.setting指令可以参看下文的指令部分;2.内建函数可以参看下文的第三部分第三点。

2.1.2 指令

指令有两种类型:预定义指令和用户自定义指令,对于用户自定义指令一般用@来代替#.

常用的预定义指令

  • if指令

if指可以有条件的跳过模板的一部分,这和程序语言中的if是相识的:

<#if condition>
    do something
</#if>

当condition的值为ture时,里面的部分会执行,同样可以运用if else 指令

<#if condition>
    do something1
<#else>
do something2
</#if>

也可以多层判断

<#if condition>
    do something1
<#elseif condition 2>
    do something2
<#else>
   do something3
</#if>
  • list指令

list指令是用来遍历集合的,它的一般格式为:

<#list list as item>repeat</#list>

repeat是会在遍历时重复执行的部分,item是遍历时集合中的每一项值,它的作用域只在<#list …>和</#list>标签之间。

  • include指令

include指令可以在当前模板中导入其他文件,即插入其他文件的内容。

比如a.html文件:

<hr>
<div>this is file a</div>

b.ftl引入a.html文件(假设在同一个目录)

<div>
    <h1>this is file b</h1>
    <#include "./a.html">
</div>

则a文件的内容会被插入在b文件中,开发中我们一般会将一些公用内容写在一个ftl文件中然后,在其他页面用过include指令引入。

  • assign指令

使用assign指令可以创建一个或多个新的变量,或者替换一个或多个已经存在的变量。但仅仅顶级变量可以被创建/替换(也就是说不能创建/替换 obj1.name变量)。

<#assign name="ccc">

则在assign指令后面的ftl中都可以使用变量name.

  • escape指令

在ftl文件中,我们会看到内容都被这个标签所包裹起来,这个指令的作用是在模板文件中转义<#escape>和</#escape>之间的插值。
例如如下模板:

<#assign m="<h1>hello</h1>">
${m}
<#escape x as x?html>
    ${m}  
</#escape>

输出的html文件应为:

<h1>hello</h1>
&lt;h1&gt;test&lt;/h1&gt;

浏览器解析后的结果为:

img6

  • noescape指令

noescape指令的作用是不转义,ftl模板默认不转义的,但是运用的时候会用到escape指令转义,我们可能有时候在escape指令包含的区域有一小部分不需要转义,这时候noescape就起作用了,用来抵消escape的指令,所以一般看到noescape指令都是运用在escape中的。

仍以上述例子描述:

<#assign m="<h1>hello</h1>">
${m}
<#escape x as x?html>
    ${m}  
    <#noescape>${m}</#noescape>
</#escape>

输出的html文件应为:

<h1>hello</h1>
&lt;h1&gt;test&lt;/h1&gt;
<h1>hello</h1>

浏览器解析后的结果为:

img7

  • setting指令

setting是为模板设置默认的初始值(并不是我们所理解的默认变量的初始值,而是设置一些默认的行为,比如日期以哪种格式显示),可设置的值有很多:location, number_format, data_format等
如下我们对数字进行格式化设置

<#assign x=42>
<div>正常输出:${x}</div>

<#setting number_format="currency">
<div>格式化为货币形式输出:${x}</div>

浏览器中输出为:

img8

用户自定义指令

  • macro指令

macro宏指令是用来用户灵活定义自己的指令,在模板中使用宏作为自定义指令,这样就能进行重复性的工作。

  • 基础自定义指令:
<#macro greet>
  <div>Hello world!</div>
</#macro>

这样就定义了一个指令greet,将宏引入其他文件就可以直接使用了,为了区别预定义指令,用户自定义指令以@开头,可以用以下两种方法:

<@greet><//@greet>
<@greet />
  • 含有参数的自定义指令(可以有一个或多个):
<#macro greet person>
  <div>Hello ${person}!</div>
</#macro>

可以用如下方法使用宏

<@greet person="Peter Sun">
<@greet person="Peter Yang"/>
  • 自定义指令嵌套内容:
<#macro border>
    <table border=4 cellspacing=0 cellpadding=4>
        <tr>
            <td>
                <#nested>
            </td>
        </tr>
    </table>
</#macro>

<#nested>编译时就会替换为嵌套的内容:

<@border>The bordered text</@border>

输出即为

<table border=4 cellspacing=0 cellpadding=4><tr><td>
    The bordered text
</td></tr></table>

注: nested指令可以被多次调用


3.一些值得注意的内容

  • 处理不存在的变量

当freemarker试图访问值为null的变量和不存在的变量时会报错,并中断执行。(很多脚本语言和模板语言都能容忍不存在的变量,通常它们将这些变量视为空字符串或0,还有逻辑false)

<#if hasContent> do something </#if>

这是我们经常会用的判断语句,但是当hasContent未定义或者为null时就会报错,在freemarker中应该怎样处理这种情况呢。

  1. 在if指令中我们可以用双问号??,若变量缺失或为null则表示为false.
<#if hasContent??> do something </#if>
  1. 在插值中我们可以运用默认值的方法(!和默认值):
<div>${user!"Anonymous"}</div>

若user未定义或为null,,模板将会将user的值表示为字符串”Anonymous”,若user并没有丢失,那么模板就会表现出”Anonymous”不存在一样。

  • 多级访问的变量

对于多级变量,我们必须要确保前面几级的变量都存在,否之用上面两种方法也会报错中断的。比如:

<div>${animale.python.price!0}</div>

仅当animals.python存在而仅仅最后一个子变量price可能不存在才有效,animal或python不存在也会报错。此时我们可以添加括号()。

${(animale.python.price)!0}


<#if (animale.python.price)??>

这样对于任何animale,python或price不存在时就可以走上面两种方法的逻辑了。

  • ?的运用

?运用在变量或常量后面,可以紧接一个关键字或者内建函数

  • 后面紧接一个关键字如(较为常用的exists, has_content等)

exists也可以处理if条件判断中变量不存在的情况

<#if name?exists>
  • 后面紧接一个内建函数,不同的类型变量后面可以接的不同的内建函数(我们可以理解为与regular中的内建过滤器一样的功能)。
    -比如对于字符串的内建函数
${"hello world"?cap_first}

cap_first的内建函数作用是字符串首字母大写。

更多内建函数可参考:http://www.zheng-hang.com/chm/freemarker2_3_24/ref_builtins_string.html


需要了解更多有关freemarker的内容可前往:

http://www.zheng-hang.com/chm/freemarker2_3_24/ref.html

http://freemarker.org/

完结