Posts Tagged ‘jsf’

使用seam时,注意实体的equals方法

August 8th, 2008

当你用jsf的<h:selectOneMenu />之类的控件选择实体时,小心你的实体的equals方法,否则你可能就会遇到”value is not valid”的验证错误。比如,如果你的页面里有这样一段:

<h:selectOneMenu value="#{fooAction.foo}">
    <s:selectItems value="#{fooList.resultList}" var="foo" label="#{foo.name}"/>
    <s:convertEntity />
</h:selectOneMenu>

你的Foo实体类的equals方法又是这样写的:

public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    final Foo other = (Area) obj;
    if (id == null) {
        if (other.id != null)
            return false;
    } else if (!id.equals(other.id))
        return false;
    return true;
}

很好,你很可能就有机会遭遇seam下这个神奇的value is not valid验证问题了。到底原因何在?原因就在于seam不仅使用了jsf还使用了hibernate,而你的equals方法没有考虑到被对比的双方可 能一个是实体类,另一个可能是被hibernate动态增强过的类。JSF在客户端提交表单后,会验证客户端选中的元素是否是当初服务端给他的那些元素。 所以,JSF会拿经converter得到的实体与当初的集合里的实体一一调用equals方法对比,如果找不到一个在集合中的实体与提交来的一样,就报 错。如果集合里的对象是hibernate 延迟加载时的一个stub,那正常的对象和这个stub对比时就可能出错。对于上面的例子,getClass() != obj.getClass() 会为真,而 other.id != null 也可能为真。怎么办?改写equals方法,如下:

public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!getClass().isAssignableFrom(obj.getClass()))
        return false;
    final Area other = (Area) obj;
    if (id == null) {
        if (other.getId() != null)
            return false;
    } else if (!id.equals(other.getId()))
        return false;
    return true;
}

isAssignableFrom方法对子类调用时一样为真。other.getId()时,则会初始化stub,取出id的值。

还有一种更简单的方法,那就是不要让用户选择实体,而是选择实体的id,那就没这个问题了。

  • Share/Bookmark

Seam增强了JSF

July 12th, 2008

Seam采用的JSF作为表现层技术,但是标准的jsf有很多的不足之处。

1. JSF对POST方法的http request的依赖太强了,只有POST请求的数据才能直接绑定到后台的组件上,GET方法的则不可以。
2. 根据JSF的规范,当收到一个GET请求后,JSF走完RESTORE VIEW PHARSE 就会直接到RENDER RESPONSE PHARSE,而不会做INVOKE APPLICATION,所以GET方法的请求不能触发业务逻辑。

这些限定让JSF在某些方面不太方便,比如书签功能。因为浏览器的书签都是记下当前页的URL,而不会记下POST请求里的参数。SEAM虽然采用了JSF,但是加入了Page parameter 和 page action,弥补了JSF在这方面的缺陷。

Page parameter是将GET请求时的参数直接绑定到组件上的技术。Page action则是在生成页面之前调用业务逻辑的技术。这两者的配置都可以在WEB-INF/pages.xml或者 *.page.xml里完成。比如:

    <page view-id="/calculator.jsp" action="#{calculator.calculate}">
       <param name="x" value="#{calculator.lhs}"/>
       <param name="y" value="#{calculator.rhs}"/>
       <param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
    </page>

上 面的配置,会让用GET方法访问/calculator.jsp时的参数x, y, op分别绑定到calculator的lhs, rhs和op属性上,然后执行calculator的calculate()方法。如果你只需要在页面上传递参数,而并不需要绑定到某个组件上,只需要写成:

    <page view-id="/calculator.jsp" >
        <param name="result" />
    </page>

这样以GET方法传来的名为result的参数将继续以result为名字在Page Scope内传递下去。

Page action 还可以按条件执行:

    <page view-id="/calculator.jsp" >
       <param name="x" value="#{calculator.lhs}"/>
       <param name="y" value="#{calculator.rhs}"/>
       <param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
       <action execute="#{calculator.calculate}" if="#{calculator.op != null}" />
    </page>

除了在pages.xml, *.page.xml里配置,还可以用seam的组件s:link s:button来实现指定page action,例如:

     <s:link view="/calculator.jsp" action="#{calculator.calculate}" ></s:link>

这 样,当这个链接被点击后,会引发一个对/calculator.jsp的GET请求,并且在生成/calculator.jsp页面之前执行 #{calculator.calculate} 方法。如果用普通的h:commandLink的话,则会提交表单,引发POST请求。

使用page action时要注意,在处理POST请求的时候,page action会在INVOKE APPLICATION PHARSE之后执行。

  • Share/Bookmark

JSF的节奏

May 16th, 2008

一般的网络程序和服务器的交互是完全自由的,用户在浏览器上对页面操作可能会引发对任一URL的任何一种方法的请求,点一个普通的超链接,引发一个GET 操作,单击一个提交按钮,引发一个POST等等,这都是完全自由的。但是在JSF的世界里,浏览器与服务器之间有着一个固定模式的交互方式,这是JSF的 节奏。
让我们看看这个节奏:a.浏览用GET请求一个页面 -> b.JSF生成指定页面的组件树,产生html页面返回 -> c.用户对浏览器上页面控件操作,引发一次对当前页面的POST请求 -> d.JSF重建组件树,执行业务动作,返回一个 重定向 。 然后就是一直循环下去,简要的来看就是 get -> 200 OK -> post -> 302 redirect -> get -> 200 OK -> post -> 302 redirect …。如果导航规则里没有指定POST后到其它页面,后面就不会有redirect,会把POST后的组件树再重新渲染成HTML返回。JSF的组件树 在浏览器的GET请求时被创建,在POST请求时被重建。你配置的导航规则决定了redirect到哪个页面,即浏览器下一次会对哪个页面发GET请求。 你获得的页面里面由h:commandButton h:commandLink等生成的链接按钮等在被触发时,都会引发一个POST请求。你在处理POST请求时放在request里的对象,在经过 redirect到为了GET生成页面时,已经不存在了。在没有SEAM的conversation的时候,我们只好把要夸越多个请求,又不需要存活于整 个SESSION的对象放到SESSION里,让SESSION变得杂乱充满隐患。
为什么JSF要让h:commandButton和h:commandLink都用POST方式来提交数据呢?JSF为了能够在用户点击链接或按钮后能重 建组件树,必然要在返回给客户端的HTML里添加很多额外的参数,这些参数是JSF自动生成的。如果用GET方式,这些信息都将写在URL里可能会超长, 出问题,所以最好的方式就是用POST了。另外,JSF也依赖于GET POST方法来确定你是要新建一个组件树来查看页面,还是要重建一个组件树来执行业务动作。这种方式等于重写了HTTP GET POST的语义,与REST的理念正好相反。如些的限定,也让你写页面不能放开手脚。
最后说一点,我非常不喜欢这种方式。

  • Share/Bookmark

JSF vs MVC

May 14th, 2008

Jboss seam使用的前端展示层技术是JSF,而且目前只支持JSF,不支持struts, struts2, spring mvc等很多其它的web框架。看来jboss seam的设计者是很看重 JSF的。用了几个月的seam之后,我对JSF的认识终于有一些切身的体会(以前虽看过一些资料,但是没有实际应用过)。我以前一直是用像spring mvc这样很传统的MVC框架的,从这些框架转到JSF上面确实让人很是不适应。
从表面上看来JSF也像是个MVC框架,有展示用的页面,有可以抽出来的处理逻辑单元,有负责调度的导航规则,就像是MVC里的view model 和controller一样。但是这两种解决方案实际上很不一样,最主要体现在他们的世界观和方法学上。在MVC的眼里,世界都是由model view controller 组成的,解决问题就是划分这三种组件,并组织好在这种组件间流动的数据,而在JSF的眼里,世界是由组件构成的,解决问题就是组织这些组件,并在组件上发 生事件时,适当地处理事件。如果说MVC是用面向数据流的方法来处理问题,那么JSF就是面向对象了。
JSF确实带来了很多好处,利用现有JSF框架,把别人做好的组件放到页面上就形成了一个想要的页面,比纯手写页面代码要方便得多。JSF内置的 validation和event机制也很好用。怪不得jboss seam选用JSF作为它的展现层技术。使用seam这几个月,还是对JSF这种东西有些不满。JSF的标准组件实在太少太弱了,由其是Table组件, 非常弱,如果不是seam对JSF做了些增强,这个Table其本上就没什么用。SEAM里选用了开源JSF实现库,带来不少组件,看起来都很酷,但是用 起来总觉得不爽,不成熟,爱出错。对于一个页面构成和功能相当复杂,比如像GMAIL那样的(当然这个例子比较夸张),用JSF来做的时候基本上就只有一 个选择,就是自定义组件,很可惜的是自定义组件差不是一件很容易而有趣的事。如果你是一个前端高手,喜欢手写出漂亮而高较的前端代码,对不起,JSF也不 适合你,它生成的那些html和javascript,会让你想要修改它时而发狂。其实我觉得JSF这种东东只能用来做做由一堆堆表单组成的企业应用后 台,做网站。为什么会这样呢?我觉得这都是jsf试图掩盖http基于数据流的本质而造成的。它不想让你看到http请求与回应,只想让你看到组件与事 件,而这样做是要付出代价的。相对的,MVC正好顺应了http的这种特性,在编写页面上你可以有更高的自由度。
如果只是做一个企业内部使用的网站,重业务,轻页面,jsf还是不错的选择,如果你想做一个吸引人的高效的网站,还是用MVC吧!

  • Share/Bookmark