赞
踩
最近工作不是很忙,所以想利用这段空闲时间学习学习。之前在微信上看到一篇关于Freemarker的文章(点击这里查看),加之现在的项目中使用到了Freemarker,当时为了急于完成在网上找了些速成的资料,所以我决定利用这段时间学习学习Freemarker。阅读了官方文档,总结了一些自己觉得还挺有用的东西,整理在这里。
一、Freemarker是个啥玩意儿?
官方文档上这么说:
Freemarker是一个模板引擎,基于模板和交换数据的用于创建文本输出的工具。作为一个java类库,可以作为一个组件供程序员嵌入他们的产品中。
例如,众所周知的MVC架构模式,在这其中Freemarker的作用其实相当于JSP,可以作为view层进行数据的展示。
既然可以MVC了,那么view层和controller层就可以分开开发了,所以我们需要了解一下模板设计者和程序设计者都需要干点啥。
二、模板设计者需要干点啥?
官方文档上这么说:
模板+数据模型=输出。
所以一个有点牛逼的模板设计者需要知道下面的这些东西。
1.关于模板那些事
Freemarker的模板使用的是FTL(FreemarkerTag Language),类似于HTML的语法,归纳如下:
1)模板上有点啥?
${}:表达式输出,使用${}会将其中的内容进行输出
FTL tags:标签。与html标签类似,但是必须以#开头,例如<#list></#list>。Freemarker自带的标签以#开头,用户自定义的标签以@开头。
注释:与html注释类似,使用<#-- -->,但与html注释不同的是,html注释会将注释体也输出到页面上,但是freemarker不会输出注释体。
在模版中,只要不是${},FTL tags,注释的内容,freemarker都会认为其是静态的文本,会直接将其输出。
2)模板咋写啊?
通过第一点可以知道Freemarker类似于HTML,但是它又与HTML有些许的不同,Freemarker有自己的一些语法,总结如下:
i.If判断:
语法:
- <#if expression>
- <#elseif expression >
- <#else>
- </#if>
ii.集合迭代:
- <#list 集合名 as 变量名>
- ${变量名.属性}
- </#list>
如果集合里元素为0,那么迭代集合的时候,依然会输出list标签中的内容。例如:
- <#list studentsList as student>
- <h1>id:${student.id}</h1>
- <h1>name:${student.name}</h1>
- </#list>
如果studentsList没有元素,那么依旧会有如下输出
id:
name:
所以为了避免这一问题。可以使用如下的表示:
- <#list studentsList>
- <#items as student>
- <h1>id:${student.id}</h1>
- <h1>name:${student.name}</h1>
- </#items>
- </#list>
如果集合元素为空,那么还可以使用<#else>来进行为空时的操作,这时就会去do something,例如:
- <#list studentsList>
- <#items as student>
- <h1>id:${student.id}</h1>
- <h1>name:${student.name}</h1>
- </#items>
- <#else>
- Do Something…
- </#list>
可以使用<#sep>something</#sep>来分割输出的内容。例如:
<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, </#list>
那么就会将输出的水果用逗号分开,like <p>Fruits: orange, banana
对于集合而言他有下面的属性:
集合本身有以下属性:
index:索引,从0开始
counter:计数,从1开始
item_parity:奇偶性,返回当前counter的奇偶性
iii.引用<#include url>
使用这个标签,可以引入外部的另一段内容到当前的文件中。
iv.内嵌函数:
Freemarker内置了很多方便好用的函数,不同于java的以.调用,freemarker以?调用。例如:
upper_case:大写转换
cap_first:首字母大写
length:获取长度
size:获取集合大小
3)要是有变量找不到咋整?
在freemarker中,一个不存在的变量a和a被赋值为null是一样的。
在freemarker中使用!为其设定一个默认值来解决变量缺失的问题。例如:
<h1>Welcome,${user!”visitor”}</h1>
如果变量user缺失了,那么模版引擎在解析到这里时会输出visitor。如果user没有缺失,那么会依旧只输出user的变量值。
通过.来调用属性,例如animal.python.price,如果使用!来为其设置默认值,需要将整个表达式都括起来,像是这样(animal.python.price)!0。因为如果animal或者python缺失了,那么freemarker解析到这里时就会抛出未定义变量的错误。一旦将其括起来作为一个整体了,那么即使animal或者python缺失了,那么也会返回设置的默认值0。
4)一个很重要但是很简单的东西,数据模型,data-model:
Freemarker的数据模型其实就是一个hash,可以将其理解为容器,存放了很多的变量与值,和java的collection是一个概念。
5)FreeMarker的几员虾兵蟹将(数据类型):
Freemarker中的数据类型和其他编程语言的变量类型类似,但是它还可以为一个变量设置多个类型。例如,mouse既可以是字符串类型,又可以是一个hash。就像是这样,mouse=”Jerry”,mouse.size=”small”,mouse.price=20。那么有如下的输出:
${mouse}作为字符串输出为Jerry,${mouse.size}作为hash输出为small,${mouse.prince}作为hash输出为20。
i.基本变量类型:
字符串
--使用索引来获取字符串的单个字符,例如:user=”Tom”,user[0]就输出T。
--使用字符串+range的形式来获取子字符串。例如:a=”ABCDEF”,a[1..3]就输出BCD
--字符串操作可以使用+来进行拼接。但是可能会因为数值格式,日期格式等因素而导致拼接的结果不正确,或者解析异常。例如:”url?id”+id有可能会出现url?id=1 234的情况。为了避免这种情况的发生,可以使用Freemarker内置的函数?c来将其进行转换,就像是这样,”url?id”+id?c或者”url?id=${id?c}”。
--数字字符串不能与数值进行运算。只能使用+操作进行字符串拼接。
数值
--数值表达0.02时,必须有小数点前的0,例如.02是错误的。
布尔类型Boolean
日期类型Date
ii.容器:
hash
--使用+连接多个hash
序列:类似于java的list,序列中的元素以逗号分隔
--在序列中会使用到range,有以下用法:
start..end:含头含尾
start..<end或者start..!end:含头不含尾
start..*length:从start开始,找到length个元素。length为正向后查找,反之向前查找
--使用+连接多个序列。例如[1,2]+[4,5]生成的新序列就是[1,2,4,5]。
--使用序列[range]的形式来获取子序列。例如:<#list [1,2,3,4,5][1..3] as i>${i}</#list>输出的就是[2,3,4]
集合:类似于java的map
6)还有一些面包渣:
i.关于range:
--range表达式本身不用带方括号,只有跟在序列后才需要方括号。
例如:<#assgin myRange=0..<x>是正确的。
<#assgin myRange=[0..<x]>是不正确的。
<#assgin mySeq[0..<x]>是正确的。
--range表达式操作符两边可以进行计算,而不需要将两边的计算使用圆括号包裹起来。
例如,x+1..y-2
--操作符是一体的,他们之间不能有空格。
--range并不真正存储它所包含的数值,因此0..1和0..100000000在创建和内存消耗上是一样的。
ii.Freemarker Template的结构:
包含文本,插值,FTL标签和注释。
FTL区分大小写。
FTL标签内部不允许再使用标签,例如:
<#if <#include “foo”>=”bar”>…</#if>,这是错误的用法。
注释可以写在任何正常的位置,只要保持正确的注释格式即可。
iii.注意:
--最好是将复杂的表达式用括号括起来,以便阅读和理解。
--对于一个负的序列索引,Freemarker将会解析错误。
--使用??来判断一个值是否为空,返回true和false两个值。
--如果向自定义标签传值,不需要${},直接将值的变量代入即可。
三、程序开发者需要干点啥?
首先介绍两个非常重要的类:
freemarker.template.Configuration:模板,环境等的配置对象。
freemarker.template.Template:模板对象
首先来看一段代码:
- public class MyTest {
-
- private Configuration configuration=new Configuration(Configuration.VERSION_2_3_23);
-
- private Template template=null;
-
- /*
- * configuration加载模版的三种方式:
- 1.根据类路径加载模版
- public void setClassForTemplateLoading(Class clazz, String pathPrefix);
- 2.根据文件系统加载模版
- public void setDirectoryForTemplateLoading(File dir) throws IOException;
- 3.根据servlet上下文加载模版
- public void setServletContextForTemplateLoading(Object servletContext, String path);
- *
- */
-
- public MyTest() {
- configuration.setClassForTemplateLoading(MyTest.class, "/config/template");
- configuration.setDefaultEncoding("UTF-8");
- //开发阶段使用
- configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
- /*
- * 生产阶段使用
- configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
- */
- try {
- //通过setSharedVariable可以将一些变量设置为公共的共享的变量,在任何使用该configuration获取template的模版中都可以访问共享变量
- configuration.setSharedVariable("author", "Earl");
- } catch (TemplateModelException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 获取模板
- * @param templatePath
- * @return
- * @throws TemplateNotFoundException
- * @throws MalformedTemplateNameException
- * @throws ParseException
- * @throws IOException
- */
- private Template getTemplate(String templatePath) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException{
- template=configuration.getTemplate(templatePath);
- return template;
- }
-
- /**
- * 向模板传递集合,迭代生成xml文件
- */
- @Test
- public void testManyPerson(){
- List<Person> persons=new ArrayList<Person>();
- Person p1=new Person("王一", 1, 20);
- Person p2=new Person("张二", 2, 21);
- Person p3=new Person("刘三", 1, 22);
- Person p4=new Person("李四", 2, 23);
- Person p5=new Person("赵五", 2, 24);
- Person p6=new Person("孙六", 1, 25);
- Person p7=new Person("杨七", 2, 26);
- Person p8=new Person("孔八", 1, 27);
- persons.add(0,p1);
- persons.add(1,p2);
- persons.add(2,p3);
- persons.add(3,p4);
- persons.add(4,p5);
- persons.add(5,p6);
- persons.add(6,p7);
- persons.add(7,p8);
-
- Map<String, Object> personMap=new HashMap<String, Object>();
- personMap.put("persons", persons);
- templateProcess(personMap, "d://persons.xml", "person_list_test.flt");
- }
-
- /**
- * 向模板传递一个对象,测试${}
- */
- @Test
- public void testOnePerson(){
- Person p1=new Person("王麻子", 1, 20);
- Map<String, Object> personMap=new HashMap<String, Object>();
- personMap.put("person", p1);
- templateProcess(personMap,"d://person.xml","person_test.flt");
- }
-
- /**
- * 向模板传递一个集合,并将模板返回的html代码输出
- */
- @Test
- public void testHtml(){
- List<Person> persons=new ArrayList<Person>();
- Person p1=new Person("王一", 1, 20);
- Person p2=new Person("张二", 2, 21);
- Person p3=new Person("刘三", 1, 22);
- Person p4=new Person("李四", 2, 23);
- Person p5=new Person("赵五", 2, 24);
- Person p6=new Person("孙六", 1, 25);
- Person p7=new Person("杨七", 2, 26);
- Person p8=new Person("孔八", 1, 27);
- persons.add(0,p1);
- persons.add(1,p2);
- persons.add(2,p3);
- persons.add(3,p4);
- persons.add(4,p5);
- persons.add(5,p6);
- persons.add(6,p7);
- persons.add(7,p8);
-
- Map<String, Object> personMap=new HashMap<String, Object>();
- personMap.put("persons", persons);
- personMap.put("title", "person");
- StringWriter sw = templateProcess(personMap,"html_test.flt");
- System.out.println(sw.toString());
-
- }
-
- /**
- * 测试方法的调用
- */
- @Test
- public void testMethodCall(){
- String s1="hello,world";
- String s2="你好,世界";
- Map<String, Object> paraMap=new HashMap<String, Object>();
- paraMap.put("s1", s1);
- paraMap.put("s2", s2);
- paraMap.put("StringLength", new StringLengthMethod());
- System.out.println(templateProcess(paraMap, "method_call_test.flt"));
- }
-
- /**
- * 测试用户自定义标签
- */
- @Test
- public void testUserDirective(){
- String s1="hello,world";
- Map<String, Object> paraMap=new HashMap<String, Object>();
- paraMap.put("s1", s1);
- paraMap.put("uppercase", new UpperCaseDirective());
- System.out.println(templateProcess(paraMap, "user_directive_test.flt"));
- }
-
- /**
- * 将生成的内容写入字符串
- * @param paraMap 数据集
- * @param templateName 模版名称
- * @return 生成的内容
- */
- private StringWriter templateProcess(Map<String, Object> paraMap,String templateName) {
- StringWriter sw=new StringWriter();
- try {
- template=getTemplate(templateName);
- template.process(paraMap, sw);
- } catch (TemplateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }finally{
- try {
- sw.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return sw;
- }
-
- /**
- * 将数据集与模版结合,生成文件
- * @param paraMap 数据集
- * @param outputPath 生成文件输出路径
- * @param templateName 模版名称
- */
- private void templateProcess(Map<String, Object> paraMap,String outputPath,String templateName) {
- Writer out=null;
- try {
- out=new FileWriter(outputPath);
- template=getTemplate(templateName);
- template.process(paraMap, out);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (TemplateException e) {
- e.printStackTrace();
- }finally{
- if(out!=null){
- try {
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
-
-
- }
Freemarker的开发使用其实还是蛮简单的,大概过程是使用Configuration配置模板路径,再使用Template得到模板,得到模板之后,就可以根据需求对模板进行操作了。基本的使用可以参考上面代码中的testOnePerson(),testManyPerson(),testHtml()。
下面来说说方法的调用以及用户的自定义标签。
--方法的调用:
用户自定义的方法的开发需要实现freemarker.template.TemplateMethodModelEx,并实现其exec(java.util.List arg0)。开发还是比较简单的,如下:
- public class StringLengthMethod implements TemplateMethodModelEx {
-
- @Override
- public Object exec(List arg0) throws TemplateModelException {
- //arg0就是调用方法的参数集合。
- //在模版中无需关心参数,只需将需要的参数进行传递即可
- //在此处根据需要取得集合中的参数
- String paraStr=(String) arg0.get(0).toString();
- SimpleNumber length=new SimpleNumber(paraStr.length());
- return length;
- }
- }
在使用时,需要将方法对象传递到模板上,可参考如下的代码:
- /**
- * 测试方法的调用
- */
- @Test
- public void testMethodCall(){
- String s1="hello,world";
- String s2="你好,世界";
- Map<String, Object> paraMap=new HashMap<String, Object>();
- paraMap.put("s1", s1);
- paraMap.put("s2", s2);
- paraMap.put("StringLength", new StringLengthMethod());
- System.out.println(templateProcess(paraMap, "method_call_test.flt"));
- }
同时在模板上如下使用即可:${StringLength(s1)}
这样的话就可以通过${}输出字符串s1的长度了。
--用户自定义标签:
用户自定义标签类需要实现freemarker.template.TemplateDirectiveModel,并实现其execute(Environment arg0,Map arg1, TemplateModel[] arg2, TemplateDirectiveBody arg3)方法,如下:
- public class UpperCaseDirective implements TemplateDirectiveModel {
-
- @Override
- public void execute(Environment arg0, Map arg1, TemplateModel[] arg2,
- TemplateDirectiveBody arg3) throws TemplateException, IOException {
- //arg1为自定义标签的属性,key存储的是属性名,value存储的是相应的值。
- //arg0为系统环境变量,用于输出
- //arg3为标签体,生成标签
- //arg2为循环代替变量
- String attr=arg1.get("name").toString();
- //官方文档推荐使用DefaultObjectWrapper或者用户自定义的继承自DefaultObjectWrapper的子类wrapper
- //官方文档不推荐使用DefaultObjectWrapper的构造方法创建wrapper对象,推荐使用DefaultObjectWrapperBuilder,以保证wrapper为单例
- DefaultObjectWrapper wrapper=new DefaultObjectWrapperBuilder(new Version("2.3.23")).build();
- arg0.setVariable("upperName", wrapper.wrap(attr.toUpperCase()));
- arg3.render(arg0.getOut());
- }
- }
在使用时,同样需要将自定义标签对象传递到模板上,如下:
- /**
- * 测试用户自定义标签
- */
- @Test
- public void testUserDirective(){
- String s1="hello,world";
- Map<String, Object> paraMap=new HashMap<String, Object>();
- paraMap.put("s1", s1);
- paraMap.put("uppercase", new UpperCaseDirective());
- System.out.println(templateProcess(paraMap, "user_directive_test.flt"));
- }
同时在模板上使用<@uppercase name=s1>${upperName}</@uppercase>,upperName就是在自定义标签类中处理完成输出的数据。使用用户自定义的标签,一定是以@开头,不是以#开头了。
总结:以上便是我最近一小段时间对Freemarker的一些了解。本文开头我提到的那篇关于Freemarker的文章,里面对比了Freemarker和jsp。其实在我看来,实际应用中,解决问题的办法很多,没有必要非得说哪个更好,两者在解决各自擅长的问题上都是有很大的优势的。但是可能我的认识里,我感觉在我们伟大的祖国应该是鲜有公司使用Freemarker作为jsp的替代品的,毕竟中国的程序员是需要前端后端通吃的。这就勾起了我曾经看过的一篇关于印度程序员的文章的回忆。为何说印度程序员很多,抛开英语是他们的第二语言,就他们本身在工作中的角色而言,他们的分工是很明确的。做前端的就是做前端的,写后台的就是写后台的,他们只需要关心自己的那一部分就好了。但是中国的程序员必须两者兼顾,所以就我目前的工作经验来说,我觉得使用jsp来作为展示,开发调试更加的方便和快捷,而Freemarker用来做一些小的,例如生成一个邮件模板等我觉得更为合适。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。