前两天,周末在家无事,想起以前的项目是基于struts2开发的,而在struts2中又大量运用了freemarker,所以就想也去试用下freemarker。
在struts2的guide文章中,说明了为什么要使用freemarker的理由。
The framework utilizes FreeMarker because the engine includes strong error reporting,
built-in internationalization and powerful macro libraries.
即准确的错误地方以及内置的国际化处理以及强大的自定义宏处理。
从网上下载了一个简单的freemarker中文文档以及官方的手册,简单看了下,然后开始处理中。首先根据每个界面都需要引用相同的html代码以及结尾处理。写了一个简单的html.ftl信息,然后在每个界面都包含进来。如下
<#assign g=JspTaglibs["/WEB-INF/gtip.tld"] />
<#macro body title="标题">
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<#include "header.ftl" />
</head>
<body>
<#nested />
</body>
</html>
</#macro>
就是简单的一个宏处理,以及声明包含一个自定义的jsptag信息,将title信息写进head中的title标签,然后引用公共使用的header.ftl(即相应的js以及css),最后在body标签中调用相应的代码信息。
这样在其他界面中,只需要<#include "html.ftl"/>,然后再调用<@body>这个宏即可。
接下来,就是将每个界面的jsp修改为ftl了,除了在struts.xml中修改每个result的type为freemarker之外,还需要将每个jsp重新copy成ftl,然后进行改造。
对于每个ftl,首先将原来的<s:调用方式,修改为freemarker的<@s.的调用方式。如<s:iterator>修改为<@s.iterator>这样的形式。我使用了一个正则表达式来进行字符替换,使用 (?<=<(?:/)?)(\w): 来匹配相应的标签,并使用 @\$1. 来替换掉。上面的字符会匹配<s:iterator中的<s:,也可以匹配</s:iterator中的</s:,直接将其替换即可。
其次,将其中的<s:property value="XX"/>的形式(经第一步之后,变成了<@s.property value="XX"/>了),修改为${xxx}的形式。因为使用freemarker嘛,所以要修改过来。这里也使用了一个正则表达式进行替换,由 <@s.property value="#?([\w\.]+)"/> 替换为 \\$\{\$1} ,即可实现这个修改。
接下来修改其他标签。
将<@s.iteraotr标签,修改为<#list >的形式;
将<@s.if></@s.if><@s.else>修改为<#if><#else>的形式。
花了近一天的时候,终于将相应的界面修改了。最后重新启动工程进行运行,结果出问题了。问题有以下几个:
1,如果ftl界面不能完全解析,则相应的界面将完全以源代码的形式显示在界面上。
这个问题,由于比如其中的某些type没有正则的匹配相应的ftl,而后台以dispatcher的形式返回回来之后,界面就直接将整个ftl文件完整地显示在界面上了。这样对于文件的保护,而且对于开发来说都是不大好的。
2,首页以及登陆界面的问题。
如果将首页以及相应的前台界面修改为ftl的形式,那么在进行访问的时候就必须以非ftl的形式进行访问了,如通过xxx.do的形式进行访问,这样还必须给相应的ftl文件追加相应的action匹配。如原来的login.jsp,现在就必须以login.do进行访问,而且需要给这个映射追加一个action映射信息。如果前面界面有很多,面分散在不同的目录中,那么就需要有多个不同的匹配来进行, 这样无凝增加了无谓的工作。
当然有一个解决办法就是保存前台界面为jsp不变,但又出现新的问题,即当前台界面引用其他界面时,又会出现相同的问题。即其他界面均修改为ftl,而ftl中有相应的freemarker解析标签。如login.jsp引用error.ftl时,将会出现error.ftl中的信息不能被解析的问题。当然可以保存error.ftl和error.jsp同时存在。但这样就失去了freemarker的作用了。
3,最重要的,toString问题和null问题以及map问题
3.1 freemarker没有对list以及map结构有一个很好的toString支持,即直接调用${x}时,如果x为一个list时,将直接显示一个List@xxx的形式。这样,就要求我们必须为其编写一个sequenceToString的macro,而且还需要在调用中对x的类型进行判断(通过?_isXXX的形式),再进行相应的处理。
3.2 map问题,即freemarker中不能支持非string的key值,这样在进行一些复杂迭代时就需要作一些其他的转换,如将一个map拆分为两个或多个map。
3.3 三元运算符的支持。由于缺少三元运行符,所以在一些复杂的判断中,为一个变量进行赋值时,就需要用多个<#if和<#else>进行处理。本来只需要一个三元运算就能解决的问题,由用<#if和<#else>就需要写很长一段代码。
3.4 null问题。这是最重要的,freemarker没有一个默认的null处理,甚至也不接受一个null值。如x=null(X变量是存在的),打印${x}时,将直接报一个错误,而不是打印类似的""值或null等。这样在进行很多显示中,都需要在相应的变量后面加一个!,如${x!}这样的形式,很是麻烦。
在<#list中,也需要使用类似<#if (list)!>的形式。而由于缺少对null的支持,需要写一个类似x==null这样的判断时,就需要写成!(x??)的特殊形式,而且到处均需要写成(XX?method?method)?或!的形式以进行null的处理。
导致我决定不使用freemarker的原因,就是现阶段没有发现其比struts2标签更方便。尤其在对于null的处理上,ognl标签相比来说更容易使用,当碰到出现类似nullPointer的时候,struts2标签在处理时将直接采用默认的处理方式,如显示为""(对于iterator或if时,将直接返回false,以结束迭代或判断)。这样就不需要开发人员再去进行无谓的null处理,而这个结果正是在web开发中所需要的。
网上再找了下,freemarker对于一些模板文件的生成还是很有用的,比如在项目中,就依照struts2的标签开发用freemarker生成一个tree树,再采用标签进行封装来调用。如下所示:
<#-- 写一个子集列表 -->
<#macro writeOne node expand>
<li>
<input type="checkbox" name="${node.nameParam}" value="${node.id}"
<#if node.checked>
checked="checked"
</#if>
/>
<span>${node.name}</span>
<#if expand && (node.childList?? && node.childList?size > 0)>
<ul>
<@write nodeList=node.childList expand=expand/>
</ul>
</#if>
</li>
</#macro>
<#-- 写上级结点 -->
<#macro write nodeList expand>
<#list nodeList as node>
<@writeOne node=node expand=expand/>
</#list>
</#macro>
<@write nodeList=parameters.nodeList expand=parameters.expand/>
最后决定,在进行jsp开发上,还是使用jsp+struts2标签进行开发,而使用一些模板文件生成的时候,或许可以考虑一下freemarker。至少对于我来说,freemarker已经没有那么大的吸引力了。