`

网上关于FreeMarker的三宗罪之争~~~

阅读更多
FreeMarker是Quake Wang推荐我使用的。刚学FreeMarker的时候,发现freemarker真的很棒!简单易用,功能强大。但是用它做了几个项目以后开始不爽了。

一宗罪:freemarker的变量必须有值,没有被赋值的变量就会抛出异常,那个黄黄的freemarker出错页面,真是让人看了太难过了。
freemarker的FAQ上面冠冕堂皇的说,未赋值的变量强制抛错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误。但是实际的效果是:带来的是非常大的编程麻烦,程序里面几乎所有可能出现空值的变量统统需要加上${xxx?if_exists},有些循环条件还需要写if判断,这样不但没有杜绝应该杜绝的错误,反而极大增加了编程的麻烦。

二宗罪:freemarker的map限定key必须是string,其他数据类型竟然无法操作!这一点就不讲了,JavaEye上面已经有人抱怨过了。连Webwork的开发人员Pat Lightboy都在抱怨这一点。


三宗罪:freemarker为了编程方便把不可序列化的东西往session里面放!
freemarker支持在页面里面直接操作Session,request等,例如${Session[...]},方便确实很方便,但是一旦需要做群集,就会报错。
今天是b051问起我这个问题,他在做Tomcat群集的时候发现freemarker报错,HttpSessionHashModel不可序列化。他修改该类源代码,让他实现序列化接口,仍然报错。我一看,HttpSessionHashModel包含的属性:
Java代码

   1. private HttpSession session;  
   2. private final ObjectWrapper wrapper;  
   3.  
   4. // These are required for lazy initializing session  
   5. private final FreemarkerServlet servlet;  
   6. private final HttpServletRequest request;  
   7. private final HttpServletResponse response; 

    private HttpSession session;
    private final ObjectWrapper wrapper;

    // These are required for lazy initializing session
    private final FreemarkerServlet servlet;
    private final HttpServletRequest request;
    private final HttpServletResponse response;



登时晕倒,这样的东西还往Session里面放?bad smell!
严重警告应用需要往群集上面发布应用的同学们,千万别用freemarker!

不过瑕不掩瑜,freemarker也是有优点的:

1、易学易用
我是看了一天文档就用得挺熟练了,freemarker文档写得太好了,例子丰富,照做一遍全都会了。

2、功能强大
比Velocity强大多了,还支持JSP Tag。不过最有意义的是macro功能,可以自定义常用的macro,实现页面常规操作的可复用性。

3、报错信息友好
很多应用服务器的JSP报错信息是无法定位到源代码行的。不过freemarker报错定位很准确,丝毫不差,而且信息丰富,一看就知道怎么回事(虽然那个黄黄的页面看起来让人难受)

三宗罪:freemarker为了编程方便把不可序列化的东西往session里面放!
freemarker支持在页面里面直接操作Session,request等,例如${Session[...]},方便确实很方便,但是一旦需要做群集,就会报错。
今天是b051问起我这个问题,他在做Tomcat群集的时候发现freemarker报错,HttpSessionHashModel不可序列化。他修改该类源代码,让他实现序列化接口,仍然报错。我一看,HttpSessionHashModel包含的属性:

Java代码

   1. private HttpSession session;  
   2. private final ObjectWrapper wrapper;  
   3.  
   4. // These are required for lazy initializing session  
   5. private final FreemarkerServlet servlet;  
   6. private final HttpServletRequest request;  
   7. private final HttpServletResponse response; 

    private HttpSession session;
    private final ObjectWrapper wrapper;

    // These are required for lazy initializing session
    private final FreemarkerServlet servlet;
    private final HttpServletRequest request;
    private final HttpServletResponse response;



登时晕倒,这样的东西还往Session里面放?bad smell!
严重警告应用需要往群集上面发布应用的同学们,千万别用freemarker!



前面两个问题我有同感,但是还能接受...
对于第三个问题,不同意Robbin具有强烈煽动性的:千万别用freemarker!
关键在怎么用~~

只有在使用FreemarkerServlet的时候, HttpSessionHashModel才会被创建和使用..具体可参见 FreemarkerServlet.java 的 createModel() 方法...


我从来不用这个FreemarkerServlet,也就不会出现Robbin所说的第三个问题了..

我们在开发中用的是Springmvc, FreeMarker只在 springmvc 要 rend view的时候被调用(我们重写了FreeMarkerView), 而不是通过FreemarkerServlet:
Java代码

   1. Environment env = template.createProcessingEnvironment(model, writer);;  
   2. freemarkerConfigurer.includeTemplatesToEnv(env);;  
   3. env.process();; 

Environment env = template.createProcessingEnvironment(model, writer);;
freemarkerConfigurer.includeTemplatesToEnv(env);;
env.process();;


其中的model,writer完全在我们自己的控制之中,不可序列化的东西完全可以剔除.

并且, Springmvc 在 expose Session to view 的时候,是这样实现的:
Java代码

   1. if (this.exposeSessionAttributes); {  
   2.             HttpSession session = request.getSession(false);;  
   3.             if (session != null); {  
   4.                 for (Enumeration en = session.getAttributeNames();; en.hasMoreElements();;); {  
   5.                     String attribute = (String); en.nextElement();;  
   6.                     if (model.containsKey(attribute); && !this.allowSessionOverride); {  
   7.                         throw new ServletException("Cannot expose session attribute '" + attribute +  
   8.                             "' because of an existing model object of the same name");;  
   9.                     }  
  10.                     Object attributeValue = session.getAttribute(attribute);;  
  11.                     if (logger.isDebugEnabled();); {  
  12.                         logger.debug("Exposing session attribute '" + attribute +  
  13.                                 "' with value [" + attributeValue + "] to model");;  
  14.                     }  
  15.                     model.put(attribute, attributeValue);;  
  16.                 }  
  17.             }  
  18.         } 

if (this.exposeSessionAttributes); {
                        HttpSession session = request.getSession(false);;
                        if (session != null); {
                                for (Enumeration en = session.getAttributeNames();; en.hasMoreElements();;); {
                                        String attribute = (String); en.nextElement();;
                                        if (model.containsKey(attribute); && !this.allowSessionOverride); {
                                                throw new ServletException("Cannot expose session attribute '" + attribute +
                                                        "' because of an existing model object of the same name");;
                                        }
                                        Object attributeValue = session.getAttribute(attribute);;
                                        if (logger.isDebugEnabled();); {
                                                logger.debug("Exposing session attribute '" + attribute +
                                                                "' with value [" + attributeValue + "] to model");;
                                        }
                                        model.put(attribute, attributeValue);;
                                }
                        }
                }


干净利索!
在FreeMarker rend view 的时候, 它的 Model 中没有一点request,response,session的影子了..
在FreeMarker模版中,要拿到session中的值,只需要${key}就可以了,不必使用${Session[...]}.
一宗罪:freemarker的变量必须有值,没有被赋值的变量就会抛出异常,那个黄黄的freemarker出错页面,真是让人看了太难过了。
freemarker的FAQ上面冠冕堂皇的说,未赋值的变量强制抛错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误。但是实际的效果是:带来的是非常大的编程麻烦,程序里面几乎所有可能出现空值的变量统统需要加上${xxx?if_exists},有些循环条件还需要写if判断,这样不但没有杜绝应该杜绝的错误,反而极大增加了编程的麻烦。


偶倒认为这个是极好的特性,而且很赞同它对于null的处理,原因在FreeMarker的FAQ上已经解释得很清楚了。
在偶们的项目里,是应用不同的Error Handler在开发环境/测试环境/生产环境来解决的。
具体的可以参考:
http://freemarker.sourceforge.net/docs/pgui_config_errorhandling.html
在生产环境下,可以用它自带的TemplateExceptionHandler.IGNORE_HANDLER

再加上好用的if_exists, default这些exists built-in,处理各种null的情况真的是很方便:
${(bar.foo.value)?default("N/A")}

当然,对所有可能为空的值加上built-in确实是一件麻烦的事情,但是偶们所有的页面都是用form macro做的,所以就没有你的烦恼了,:)
robbin 写道

二宗罪:freemarker的map限定key必须是string,其他数据类型竟然无法操作!这一点就不讲了,JavaEye上面已经有人抱怨过了。连Webwork的开发人员Pat Lightboy都在抱怨这一点。


这个绝对是很烦恼的一件事情,其实这个问题是TemplateModel造成的,偶最不能理解的是为啥它需要弄一个TemplateModel来wrap 所有的Object,直接用原始对象不就好了么,还要弄个wrap/unwrap。据说它这样的做法是提高性能,但是偶无法理解......,有人可以解释一下么?
robbin 写道

三宗罪:freemarker为了编程方便把不可序列化的东西往session里面放!


不知道你在说啥,freemarker只是template language,和HTTP session有啥关系啊?


freemarker/velocity这些模本语言和JSP Tag比较起来,最大的优点是性能可以做得很好
而freemarker和velocity比较起来,最大的优点是它在活跃开发,可以不断地进步,webwork选择freemarker作为默认的template,很大原因在于此。 一宗罪,null值处理:

这个也的确让我烦叻一阵子,不过后来不仅习惯叻,而且还真的喜欢上这一点叻。我们的页面上有null值,大概有这样两种情况:

1、这个值本来就是可有可无的。比如表单域的value=""值。这种情况,可以用${foo?default("")}来写,并不十分复杂,而且可以让你狠方便地定义默认值,比如N/A什么的。

2、Action层处理有错,导致null值。这种情况下,FreeMarker就给程序调试提供的有力的保障,也是FreeMarker对 null值如此敏感的初衷。从我学FreeMarker开始,我就强烈地感受到FreeMarker在试图把自己做成模板语言中的强类型语言。

总之,这个null其实不是什么大问题啦,并没有什么不方便。


二宗罪:Map的key必须是String

这个我还真的没遇到过。我写叻段小程序简单地试验叻下,用list遍历map.keySet(),然后用map.get(key)这样的方法查询,是可以的。直接用map[]这样的操作的确是不行。我实际使用中在前台用到Map的情况不多,以前用velocity的时候也没觉得map有多好用,大部分情况我是用n个相同长度的list或数组解决的,感觉比map方便。


三宗罪:freemarker为了编程方便把不可序列化的东西往session里面放!

这个就不说啦,跟freemarker无关。抛开这个不谈,客观地说,WebWork对FreeMarker Result的封装还是非常不错的。


FreeMarker自身的优点的确非常突出,易学,我只用叻3个小时看叻遍文档就基本掌握叻,就可以扔掉 velocity叻,哈哈。而且FreeMarker自己的builtin也的确有趣,虽然大部分我都不用,还是要在 action层自己封装个方法调用,不过常用的比如?html,?date这样的还是狠方便的。

我想说说FreeMarker跟JSP Tag的比较,前面看到Robbin说要回归JSP Tag,我真是痛心疾首啊!要拿FreeMarker去攻击JSP Tag,我都不需要列举JSP Tag/JSTL的缺点,只要把FreeMarker的Macro拿出来往那一亮就OK叻。用老罗的话说,“那简直是太方便叻!太方便叻!!”

我刚才就在重构一个项目的common.ftl,将公共的部分提取出来,细力度的重构。在Code级别我们可以重构,可以代码only once。现在在FreeMarker的帮助下,页面级也可以这样叻!JSP Tag、Velocity不是不能这样,而是都太麻烦,而且功能不强,用叻不仅不省工作量,反而还更烦。而只有FreeMarker,才能让页面达到这样的重构高度。

我们这边的策划总是有新点子,页面也总是改,我写代码是基于敏捷原则,现在页面也是敏捷的,可以说是真正做到的拥抱变化,感觉非常爽。 关于第三宗罪,我今天仔细看了一下代码,确有其事。

在Freemarker自带的FreemarkerServlet的createModel方法中有这样一段代码:
Java代码

   1. // Create hash model wrapper for session  
   2. HttpSessionHashModel sessionModel;  
   3. HttpSession session = request.getSession(false);  
   4. if(session != null) {  
   5.     sessionModel = (HttpSessionHashModel) session.getAttribute(ATTR_SESSION_MODEL);  
   6.     if (sessionModel == null) {  
   7.         sessionModel = new HttpSessionHashModel(session, wrapper);  
   8.         session.setAttribute(ATTR_SESSION_MODEL, sessionModel);  
   9.         initializeSession(request, response);  
  10.     }  
  11. }  
  12. else {  
  13.     sessionModel = new HttpSessionHashModel(this, request, response, wrapper);  
  14. }  
  15. params.putUnlistedModel(KEY_SESSION, sessionModel); 

// Create hash model wrapper for session
HttpSessionHashModel sessionModel;
HttpSession session = request.getSession(false);
if(session != null) {
    sessionModel = (HttpSessionHashModel) session.getAttribute(ATTR_SESSION_MODEL);
    if (sessionModel == null) {
        sessionModel = new HttpSessionHashModel(session, wrapper);
        session.setAttribute(ATTR_SESSION_MODEL, sessionModel);
        initializeSession(request, response);
    }
}
else {
    sessionModel = new HttpSessionHashModel(this, request, response, wrapper);
}
params.putUnlistedModel(KEY_SESSION, sessionModel);


会每次把sessionModel放进HttpSession,不知道究竟什么用意。这里有一个问题是:当我们使用FreeMarker作为sitemesh的decorator的时候,必须使用FreeMarkerServlet,看sitemesh的扩展类:
Java代码

   1. public class FreemarkerDecoratorServlet extends FreemarkerServlet 

public class FreemarkerDecoratorServlet extends FreemarkerServlet



然后再看看webwork使用FreeMarker作为view的时候,

FreemarkerResult在createModel里面引用了 FreemarkerManager.getInstance().buildTemplateModel,而FreemarkerManager的 buildScopesHashModel里面:
TemplateHashModel sessionModel;
Java代码

   1. HttpSession session = request.getSession(false);  
   2. if (session != null) {  
   3.     model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));  
   4. } else {  
   5.     // no session means no attributes ???  
   6.     //            model.put(KEY_SESSION_MODEL, new SimpleHash());  
   7. } 

HttpSession session = request.getSession(false);
if (session != null) {
    model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));
} else {
    // no session means no attributes ???
    //            model.put(KEY_SESSION_MODEL, new SimpleHash());
}


这里每次需要sessionModel,就是直接new一个了事,并没有每次放进HttpSession下次再从HttpSession中取得。

因此使用freemarker作为webwork的view,是没有问题的。但是如果使用freemakrer作为 sitemesh的decorator的时候,还是有这个群集复制的问题。我想修改FreeMarkerServlet,改成这样:
Java代码

   1. HttpSessionHashModel sessionModel;  
   2. HttpSession session = request.getSession(false);  
   3. if(session != null) {  
   4.     sessionModel = new HttpSessionHashModel(session, request, response, wrapper);  
   5. }else {  
   6.     sessionModel = new HttpSessionHashModel(this, request, response, wrapper);  
   7. }  
   8. params.putUnlistedModel(KEY_SESSION, sessionModel);
分享到:
评论

相关推荐

    freemarker 自定义freeMarker标签

    3. 使用自定义标签:在模板文件中,你可以像使用内置标签一样使用你的自定义标签。例如,如果你注册了一个名为`myCustomTag`的自定义指令,你可以在模板中这样使用:`...

    freemarker Demo 适用于freemarker初学

    Freemarker是一个强大的模板引擎,常用于JavaEE应用中的视图层处理,尤其与Struts2等MVC框架配合使用,能实现灵活的动态页面渲染。这个"freemarker Demo"是一个适合初学者的示例项目,旨在帮助新接触Freemarker的...

    freemarker

    标题:Freemarker 描述:孔浩的Freemarker视频笔记,值得一看! 根据给定的文件信息,我们可以深入探讨Freemarker的相关知识点,包括其基本概念、工作流程以及具体的代码实现。 ### Freemarker基本概念 ...

    eclipse的freemarker插件

    总的来说,"eclipse的freemarker插件"是Eclipse开发环境中不可或缺的工具之一,它极大地提升了Freemarker模板开发的便捷性和专业性,使得开发者能够更加专注于业务逻辑,而非模板语法的细节。如果你在Eclipse中处理...

    freemarker.jar

    camel-freemarker-1.6.4.jar, camel-freemarker-2.8.1.jar, com.springsource.freemarker-2.3.15.jar, com.springsource.freemarker-sources-2.3.15.jar, freemarker-1.4.1.jar, freemarker-2-3-18.jar, freemarker-...

    freemarker-2.3.23jar

    3. **控制结构**:Freemarker提供了丰富的控制结构,如条件语句(`<#if>`、`<#else>`、`<#elseif>`)、循环(`<#foreach>`)、以及异常处理(`<#try>`、`<#catch>`)等,使得模板具有一定的逻辑处理能力。...

    FreeMarker手册-Freemarker 2.3.18

    在"FreeMarker手册.pdf"中,你将找到关于这些概念的详细解释、示例代码和最佳实践,对于深入理解和使用FreeMarker是非常有价值的资源。无论你是初学者还是经验丰富的开发者,这本手册都能为你提供必要的指导和支持。

    freemarker-2.3.28.jar

    2. **数据模型绑定**: Freemarker的核心概念之一是数据模型,开发者将Java对象暴露给模板,模板通过这些对象来生成输出。比如,一个Web应用可以将请求上下文中的ModelAndView对象传递给Freemarker,然后在模板中引用...

    freemarker-2.3.22

    3. **模板设计**:Freemarker支持条件语句、循环、包含其他模板等功能,使得模板设计灵活且易于维护。 4. **缓存机制**:Freemarker有内置的模板缓存,可以提高性能,尤其是在高并发环境下。 5. **国际化与本地化*...

    FreeMarker

    3. **指令**:FreeMarker 指令是以 `#` 开头的特殊标签,它们可以执行特定的任务,如`<#assign>`分配变量,`<#include>`引入其他模板,`<#function>`定义函数等。例如,`<#assign name="John">` 将变量 `name` 设置...

    FreeMarker2.3.23官方中文文档

    3. **模板继承与导入**:FreeMarker支持模板的继承,通过`<#macro>`定义宏,可以在多个模板间共享代码段。`<#import>`则可以导入其他模板中的变量和宏。 4. **日期和数字格式化**:FreeMarker提供了丰富的内置函数...

    freemarker 源码、中文API、 freemarker Myeclipse 编辑器

    这个压缩包包含了一些关于Freemarker的重要资源,下面我们将详细探讨这些内容。 首先,`FreeMarker_Manual_zh_CN.pdf`是Freemarker的中文API手册,这是学习和使用Freemarker的关键文档。通过这份手册,你可以了解到...

    一个意义深刻 FreeMarker 入门例子之HelloWord

    本示例“一个意义深刻的FreeMarker入门例子之HelloWord”旨在帮助初学者理解并掌握FreeMarker的基本用法。 在Java Web开发中,FreeMarker与后端控制器如Servlet或Spring MVC中的Controller协同工作,允许开发者将...

    freemarker-2.3.31-API文档-中文版.zip

    赠送jar包:freemarker-2.3.31.jar; 赠送原API文档:freemarker-2.3.31-javadoc.jar; 赠送源代码:freemarker-2.3.31-sources.jar; 赠送Maven依赖信息文件:freemarker-2.3.31.pom; 包含翻译后的API文档:...

    Freemarker简介及标签详解大全

    Freemarker 简介及标签详解大全 FreeMarker 是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯 Java 编写。FreeMarker 被设计用来生成 HTML Web 页面,特别是基于 MVC 模式的应用程序。虽然 FreeMarker ...

    freemarker\Freemarker教程_中文版

    Freemarker是一款强大的模板引擎,用于将数据模型与表示层分离,从而实现在Web开发中的动态页面生成。根据所提供的文件信息,我们可以详细探讨以下几个关键的知识点: ### 1. 创建配置实例 在Freemarker中,`...

    FreeMarker通用的分页

    这不仅节省了开发时间,也降低了维护成本,使得FreeMarker成为Web开发中处理动态内容的理想选择之一。掌握FreeMarker的分页技巧,对于任何希望提升网站性能和用户体验的开发者来说,都是至关重要的。

    freemarker-2.3.30-API文档-中文版.zip

    赠送jar包:freemarker-2.3.30.jar; 赠送原API文档:freemarker-2.3.30-javadoc.jar; 赠送源代码:freemarker-2.3.30-sources.jar; 赠送Maven依赖信息文件:freemarker-2.3.30.pom; 包含翻译后的API文档:...

Global site tag (gtag.js) - Google Analytics