我与Freemarker二三事

最近工作不是很忙,所以想利用这段空闲时间学习学习。之前在微信上看到一篇关于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用来做一些小的,例如生成一个邮件模板等我觉得更为合适。

源代码和模板可以点击这里下载

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值