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之后执行。
最近公司又在招人,头安排我对一些应聘者做电话面试,让我遇到了不少华而不实的家伙。这些家伙的简历上密密麻麻会写了很多项目经验,都说自己做过需求做过架构,有项目管理经验。但是细细问下去,你会很失望,有些人会把hibernate 一级缓存和二级缓存搞混,有些人声称自己从来没有用过hibernate的二级缓存,有些人简历上写了一大堆模块,实际上只做了其中一两个,还有些人号称精通J2EE,却不知ejb3是何物!而这些人也只是用struts + spring + hibernate做过一些开发,而且并不能深入理解spring和hibernate的工作细节。
我不相信对基础工具不熟悉的人能做个架构师。我也不相信只会用struts+spring+hibernate的就可以做架构师。我更不相信架构师是高于程序员之上,只用画画图,写写文档,放放PPT,动动嘴皮子就可以做得了的。那些声称不想编码,只想做管理做架构的人,是不折不扣的妄想者。
做架构师的首要条件是基础扎实,连jdk里的api都不熟悉的家伙,是不可能做架构师的。要做架构师,还要能对新技术敏感,只围着自己的小圈子转,不看着未来世界的家伙,只能落后于这个领域,更别谈做架构。做架构师还得要大量编码,不写程序,还能算是程序员吗?不写程序,你做的架构会越来越没有根基。
用struts+spring+hibernate做过几个项目就可以做java架构师了吗?做梦吧!
尽管seam文档和其它相关的资料中,都声称entityManager的抽象程度已经很高了,没有使用DAO模式的需要,但是从这次的项目实践来看,将 部分的数据访问放到DAO里是很有必要的。首先,使用DAO有易于做单测。如果不使用DAO,需要访问数据库的Action就注入一个 entityManager直接访问数据库,这样写起来方便,但是在单测这个action的时候,要么得做一个假的entityManager,要么得真 的创建一个entityManager并准备好数据。这些都比做一个假的DAO来得要复杂。其次,如果每个ACTION里都自己拼接sql来查询数据,这 些查询SQL将会分散到各个ACTION中,难以管理和维护。再次,DAO更易于重用。很多的时候,一个查询语句在很多地方都初用到,如果每个地方都写一 遍自己的SQL,显然是不好的,放到一个单独的组件里做成DAO,将会更加易于重用。任何需要的ACTION都只要注入这个DAO就可以用了。
那么如何实现DAO才好呢?为了尽量让这个被经常调用到的组件轻量化,我觉得使用的statelss scope的普通java bean 就是行了,为了便于测试使用dao的其它组件,为DAO设计成接口与实现分离会更加地好。
几乎所有对seam进行介绍的文档中都会用大量的篇幅来说明会话(conversation),因为它是seam的发明,让seam与众不同的一个地方。但是要完全理解和运用conversation并不是容易呀。会话是一种用来存储程序上下文数据的容器,和session类似,只不过它的生存周期和session有很大的差别。
会话分成两类,临时会话和长会话。临时会话会开始于服务器端处理客户端的post请求,结束于下一个页面的生成。也就是说,它存在于post->redirect->get 这样一个小周期内。典型的例子是facesmessage组件。这是一个会话内的组件。我们在处理post请求时,写的message,在下个页面生成的时候依然有效。比如,用户提交一个注册表单,表单POST提交后,服务器调用注册方法,在方法结束的时候,调用facesmessage设定信息“注册成功了!”。然后服务器发送一个跳转的response让浏览器转向index页面。在index页面里,我们输出facesmessages里的内容,这时,这个message能够成功的拿到。因为那个facesmessage组件跨越了那一次跳转。之后,如果提交index上的表单的话,又会有一个新的临时会话产生,之前的会话里的内容将不再存在。长会话可以存活得更长。如果在一个短会话内,程序中执行了带有@Begin的方法,或者在page.xml里有配置<begin-conversation/>,那么这个临时会话,就会变成一个长会话。如果服务端在执行方法时遇到标有@End的标注,或者在page.xml里遇到<end-conversation />,当前的长会话,将会结束;否则,它会一直持续下去。
会话与JPA的entityManager的关系。一个entityManager与hibernate的session相当。JPA里定义的entityManager一级缓存的刷新方式只有两种:Auto和commit。seam发明了第三种方式,Manual。Auto意味着,当执行到select语句时,entityManger会自动执行一下flush()方法,commit会在事务提交时flush();而Manual则是由你自己来手动控制flush()。每次开始一个会话的时候,seam都会为你开启一个entityManager,这个entityManger会一直打开,直到conversation结束。这是seam的作者Gavin最骄傲的地方了。能让hibernate的session持续在一个会话之内,可以在会话期内的几次服务器与客户端的几次交往中避免LazyInitializationException,最大程度的发挥hibernate延迟载入的能力。其实Gavin最初要把服务端做成这样有状态的,就是因为他认为与orm协作的最佳方式,就是让服务端有状态。
长会话的结束。默认的情况下,当遇到<end-conversation />或@End时,会话并不会立刻结束,而是会在下个页面生成之后才结束,这是为了避免生成页面时无法取到前一个页面POST时处理的结果。但是有的时候,我们确实需要在POST之后,下个页面生成之前就结束掉。这时需要用@End(beforeRedirect=true),或
<end-conversation before-redirect=”true” />。
会话的维持。我们都知道session是靠一个叫JSESSIONID的cookie或者请里的参数来维持的。类似的会话也是通过这么一个特殊的请求参数来控制的,默认为cid。观查cid的变化,能帮助我们查看conversation的变化。
为什么要发明会话呢??这得从seam的作者Gavin说起。这个牛人发明了杰出的ORM框架hibernate,但是却发现现在的web开发方式并没有正确的使用它。和
hibernate搭档最多的当属spring了。在结合spring使用hibernate的时候,我们很难把握得好hibernate session的范围,如果太窄,那么在这个范围之外对持久对象的调用很容易产生LazyInitializationException。所以后来OpenSessionInView的方式很流行。但这还是不能完全解决问题。OpenSessionInView把session的范围扩大到了对一个http request的处理过程之中,而不能处理跨页面的情况。如果把hibernate session扩大到 http session范围内,这显然会让session太长,引发其它问题。所以Gavin觉得需要一种能够跨越页面,并且又不会像http session那样太长的Scope,于是conversation就诞生了。在开发中,尤其是以提交表单方式为主的企业开发中,常常需要用几个页面来配合完成一个用例,这几个页面内的数据往往耦合性强,需要被放到同一个上下文中,而这个上下文用conversation来做也是很自然的。对于Gmail这样的应用,conversation没什么意义。
Conversation是个好东西,但是一定要小心的使用。一来它存在着性能问题 ,二来用的太多,不小心控制conversation的开始和结束,就会导致conversation的混乱,那个时候会导致一些奇怪的问题。
这两年REST(Representational State Transfer)随着ajax, web2.0, ROR逐渐火了,起来。不得不承认REST确实是一种在互联网环境下非常好的架构风格。REST中一个非常重要的约束,就是服务端无状态,将大部分的状态管理向客户端转移。而SEAM正好朝向REST的反面走去,而且是走得很彻底。SEAM是完全的服务器端有状态,所有的状态都在服务器端来管理。所以seam的文档里都会自称自己是一个stateful的框架。要说服务器端有状态,其实JEE的标准的web container基本上都是以服务器端有状态的方式来运行的, servlet的 session scope, application scope就是这个服务器端状态的容器。SEAM为了更好的进行服务端的状态的管理,还添加了另外几个scope:
- 会话 Conversation scope : 跨越多个页面,但是比session要短
- page scope : 在同一个页面内同一个棵JSF组件树上保存状态,不同于jsp的 page scope
- business scope : 跨越多个session的业务流上下文
有了session, 服务器能记住每一次的客户端的请求是由谁发送来的;有了conversation, seam更有能力知道用户正在处理哪几个页面,做哪个用例;而 business scope 更是让seam有能力记住用户的操作是在哪个业务流程里的。这种想要让服务器记住一切与客户端交互上下文,维持交互状态的设计,让SEAM变成一个彻底的REST风格的对立者。
在SEAM里面集成了RichFaces等ajax框架,但是java script在seam和 REST的眼中,地位完全不一样。在REST里,java script被用作是客户的VM语言,肩负着在客户端维护状态,展现资源的重任,而在SEAM里,ajax只不过是一锦上添花的小玩具而已。因为即使你用ajax方式来发一个请求,服务端还是要走一个非常复杂的流程(各种状态的恢复和维护),这种ajax并不会带来多少的性能上的提升。
REST风格是针对于互联网来说有,互联网网存在着高延迟和高并发的特性,数据的传输需要花费很多的时间,对一台服务器的访问量可能由于一则花边新闻而骤然升高,在这种情况下,服务端和客户的缓存显得尤为重要。客户端缓存可以减少不必要的请求,服务端的缓存可以减少对相同资源的处理时间。为了能让缓存尽量的发挥它的作用,所以很强调服务端无状态的特性。而每一次请求都要包含状态数据的SEAM应用在应对高延迟和高并发时,显然没有REST风格有优势。不过,从目前的情况来看,SEAM也就是把自己定位成企业应用的WEB框架,而企业网络,和因特网相比则没有那么高的延迟和并发了。
这样看来,如果是做互联网应用,REST风格的架构要比SEAM这种架构要好;而如果是做企业应用,SEAM还是个不错的选择的。