FreeMarker是什么?
摘自百度百科
FreeMarker允许Javaservlet保持图形设计同应用程序逻辑的分离,这是通过在模板中密封HTML完成的。模板用servlet提供的数据动态地生成 HTML。模板语言是强大的直观的,编译器速度快,输出接近静态HTML页面的速度。
FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写
FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序
虽然FreeMarker具有一些编程的能力,但通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据
FreeMarker不是一个Web应用框架,而适合作为Web应用框架一个组件
FreeMarker与容器无关,因为它并不知道HTTP或Servlet;FreeMarker同样可以应用于非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>
除了标签以#开头外,其他和HTML,XML的语法很像。如果标签没有嵌套内容(在开始标签和结束标签之内的内容),那么可以只使用开始标签。
指令有两种类型:预定义指令和用户自定义指令。对于用户自定义指令使用@来代替#。
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_format,date_format和datetime_format设置规则来转换为字符串,这要看日期信息中是只包含日期、时间还是全包括了。如果它不被探测出来是那种日期类型时,就会报错。
C.如果值本来就是字符串类型的,不需要转换。
D.如果freemarker引擎在传统兼容模式下:
1、如果值是布尔类型,那么就转换成“true”,false值将会转换为空串。
2、如果表达式未被定义(null或者变量未定义),那么就转换为空串
3、否则就会发生错误终止模版执行
E.否则就会发生错误中止模板执行
2、表达式
直接指定值
1.字符串
直接输出插值中的值,而不是变量的值。可以是字符串、数值、布尔、集合、map对象等。在文本中确定字符串的方法是看双引号和单引号,这两种形式是相等的。如果文本本身包含用于字符串引用的引号或反斜杠时,应该在他们的前面加一个反斜杠,这就是转义。转移允许你直接在文本中输入任何字符,也包括反斜杠。
例如:${“测试 C:\\”}、${‘测试’}
2.数值
输入不带引号的数字就可以直接指定一个数字,必须使用点作为小数的分隔符而不能是其他的分组分隔符。可以使用-或+来表明符号(+是多余的)。科学记数法暂不支持使用(1E3就是错误的),而且也不能在小数点之前不写0(.5也是错误的)。
3. 布尔
直接写true或false就表征一个布尔值了,不需使用引号。
4. 集合
指定一个文字的序列,使用逗号来分隔其中的每个子变量,然后把整个列表放到方括号中。
<#list[“winter”,“spring”,“summer”,“autumn”] as x>
${x}
</#list>
5. map
类似json的样子
用来检索的名字就必须是字符串类型的。
输出变量值
可以是顶层变量,也可以是list或map中的,可以使用.来访问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]可以获取序列中的一部分,这里的firstindex和lastindex表达式的结果是数字。比如:要找序列中的第三个到第五个[3..5]
Map连接运算符
连接:
像连接字符串那样,也可以使用+号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项目优先(覆盖原来的数据)。例如:
<#assign ages = {"Joe":23,"Fred":25} + {"Joe":30, "Julia":18}>
算术运算符
算数运算包含基本的四则运算和求模运算,运算符有:
加法:+
减法:-
乘法:*
除法:/
求模(求余):%
例:${100-x*x+y%2}
比较运算符
测试两个值相等使用=(或者采用Java和C语言中的==,二者是完全等同的。)
测试两个值不等使用!=。
=或!=两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型(也就是说字符串只能和字符串来比较,数字只能和数字来比较等。区分大小写)。否则将会出错,模板执行中断。
大于:gt / >
小于:lt / <
大于等于: gte / >=
小于等于: lte / <=
逻辑运算符
常用的逻辑操作符:
l 逻辑或:||
l 逻辑与:&&
l 逻辑非:!
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。
内建函数
内建函数:系统内置的函数。
内建函数以?形式提供变量的不同形式或者其他信息。使用内建函数的语法和访问哈希表子变量的语法很像,除了使用?号来代替点,其他的都一样。例如得到字符串的大写形式:user?upper_case。
n 字符串使用的内建函数:
u html: 字符串中所有的特殊HTML字符都需要用实体引用来代替(比如<代替<)
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 condition,condition2等:表达式将被计算成布尔值。
<#if x > 1>是不对的,因为FreeMarker将会解释第一个>作为结束标记。因此,编写<#if (x >1)>或<#if x > 1>是正确的。
switch casedefault break
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
</#switch>
这里:
l value,refValue1等:表达式将会计算成相同类型的标量。
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
要注意你可以使用Configuration的setLocalizedLookup方法关闭本地化查找特性。
类似jsp包含
<#include filename [options]>
options 可以省略,包含encoding和parse选项
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 param1,param2等: 局部变量的名称,存储参数的值(不是表达式),在=号后面和默认值(是表达式)是可选的。默认值也可以是另外一个参数,比如<#macro section title label=title>。
l paramN,最后一个参数,可以可选的包含一个尾部省略(...),这就意味着宏接受可变的参数数量。如果使用命名参数来调用,paramN将会是包含给宏的所有未声明的键/值对的哈希表。如果使用位置参数来调用,paramN将是额外参数的序列。
l loopvar1,loopvar2等:可选的循环变量的值,是nested指令想为嵌套内容创建的。这些都是表达式。
return和nested指令是可选的,而且可以在<#macro>和</#macro>之间被用在任意位置和任意次数。
没有默认值的参数必须在有默认值参数(paramName=defaultValue)之前。
4、Java操作FreeMarker
经典应用
使用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));
}
}
}
转载于:https://blog.51cto.com/distantsaviour/1211473