JSF的优点缺点及学习方法



JSF的优点缺点及学习方法

先说JSF的优点,我觉得与其他Java前端框架相比,真正称得上优点的就是一点:兼容并包,体系开放。不少人觉得JSF难学,是因为它一下子把太多东西摊在你面前。什么组件化,视图状态,事件,backing bean,绑定,注入,Facelet模板,多语言,导航,校验器转换器,六个生命周期阶段,EL求值,RenderKit,渲染器。看上去笨重得不得了,但事实上只要完全掌握了六个生命周期,就会发现其他东西都像是插件。JSF的API在设计上就已经考虑到了其中每一部分都是可替换的。比如说,你可以把EL Resolver换掉,换上Groovy的实现,或者支持任意一种JVM脚本语言的Backing Bean。也可以把RenderKit换掉,让同一套组件渲染出不同的结果。或者把ViewHandler换掉,让JSF可以直接开发Portlet。自定义模板或组件更是不在话下。喜欢不求甚解的人,就玩玩组件,拼拼页面,也能出弄出个像模像样的东西。喜欢刨根问底的人,只要把FacesServlet背后的东西搞清楚了,能把JSF变成任何自己想要的样子。开放性带来的另一个好处就是可测试性。Managed Bean本身是依赖注入的POJO,单元测试自然是没问题的了。某些必须用FacesContext.getCurrentInstance()的场合,也可以通过在ThreadLocal里放入Mock的方式来解决。JSFUnit更是岂一个爽字了得(就是在服务器端模拟了一个浏览器来做容器内测试)。

JSF的缺点,我觉得主要是两个:

1. 性能问题。JSF是个规范,性能本来跟它没有直接关系。但这种大而全又可扩展的东西本身就是鼓励实现厂商拼了命的往里加新特性。例如seam,第一天用它的@In来注入就被吓了一跳,默认的注入时机居然是方法调用级别的。也就是说,如果一个bean上有10个@In,每当调用这个bean上任何一个public方法时,这些@In上的EL都被求值了一次(好处是被注入的值永远都是最新的,不用担心bean的scope问题)。自然,这种做法是不是会实际成为性能瓶颈,要实际profiling才知道。但这些稀奇古怪的新特性,难保不会因为算法之类的问题在某些场景下出现性能问题。所以个人觉得用JSF的话,一个好的profiling工具是必不可少的。

还有一个可能导致性能问题的地方是组件树规模。在JSP里,多用一个标签只不过是在输出流里多了几个字节。而在JSF里,多一个组件就等于:更大的视图状态、按id查找组件时需要比较更多的组件、JSF生命周期中遍历更多的组件(每一个周期阶段都遍历了一次组件树)。特别是像include、naming container这类不可见的组件在组件树中也算是一个实实在在的组件。而使用facelet更鼓励用大量小组件来拼凑可复用的模板组件,一不小心,组件树就会变得很庞大,有可能引起性能问题。但还是那句,性能问题必须用profiling来验证。现在公司里有些页面就多达上千个组件,但profiling显示瓶颈还是在数据库处理上。

初学者如果不了解JSF生命周期和EL求值的顺序,导致某些EL被重复求值,而这些EL里又包含了耗时的操作,例如说访问数据库,也会引起性能问题。这个也可以通过profiling查出。

2. 不提供完备的概念封装。这个听起来有点玄,其实很浅白,就是看上去虽然JSF看上去大而全,一些书籍也在推销JSF的封装性有多么的好。但事实上JSF所提供的概念体系,并不能对web开发所需要的基础知识作完整的封装和简化。使用组件可以提高你的开发效率,但如果你不懂JavaScript,不懂CSS,仅仅在组件的层面上开发的话,客户随便一句“这里我要圆角”就能让你欲哭无泪。虽然JSF自动跨http请求保留组件的视图状态,但如果你不懂request和session的分别,你就会把所有backing bean都设为session,引起性能问题,或者奇怪request bean里为什么会抛空指针异常。虽然现在大部分JSF组件库都自带AJAX功能,如果你不懂AJAX,就会奇怪为什么我在A bean里修改了B bean的东西,为什么页面上B bean的部分没有跟着变。在这点上,JSF跟Delphi之类的桌面应用开发环境是不同的。在Delphi的设计理念里,你可以完全不懂数据库,只用TTable和TDataSource与数据库交互,也可以完全不懂OLE就直接用组件来操作Word。而在JSF中,虽然大部分时间你可能不需要使用这些基础知识(所以开发速度快),但如果一旦需求超出了JSF封装的概念体系,而你又不懂基础的东西,在这里卡住的时间会让你把之前省下来的时间都耗进去。不过,话又说回来,目前为止还没哪个开发框架可以对web开发在概念上完整封装的。做得最好的可能是.net,现在还是回过头来搞MVC了。.net也觉得只用组件太封闭了,现在再开放一些MVC的特性让你能处理一些底层的需求。而JSF从一开始在各个层次上就都是开放的(基础到本身就是按MVC来架构的,可以跟Servlet和JSP协作,或者通过FacesContext拿到HttpServletResponse),而且从来就没有试图把程序员局限在某一个抽象层面上。

我学习JSF的步骤:

1. 使用组件是入门,如果要用好JSF,至少要搞清楚六个生命周期和它们的执行顺序。例如:首次请求和postback时生命周期有什么不同;每个生命周期中遍历组件树都干了什么; 校验或转换出错了生命周期的执行有什么不同;immediate属性对事件组件自身和其他输入组件的求值有什么影响;FacesContext.renderResponse()和FacesContext.responseComplete()对生命周期执行的影响(包括phase listener)等等。


2. 一定要找齐框架的源码。其实跟踪一下就会发现,JSF没有想象中那么高深莫测。千万不要debug时跟踪到没有代码的框架类,就束手无策,开始大骂框架难用,还不如用jsp,至少源码是自己的。

附找源码的方法:关键是确认当前服务器所用jar包的版本,在managed bean中向控制台输出javax.faces.webapp.FacesServlet.class.getProtectionDomain().getCodeSource().getLocation().toString(),可以知道服务器所用的jsf-api.jar位置。任何没有源码的类都可以先用这个方法找出jar包位置。用解压缩工具打开.jar包,打开在META-INF中的MANIFEST.MF文件,里面通常会有打包时的版本信息,或者打包日期。上网找这个版本的源码,或者到代码库中checkout这个版本的tag,或者checkout这个日期的snapshot。

3. 阅读示例或开源项目的源码。richfaces的代码库就能checkout到不少示例。

4. 真正用JSF做应用的时候,应该用好facelet,到这里才体现出JSF相对于JSP的优势。不过模板引用多了可能不太好理解(主要是对于新加入项目的人),我个人喜欢用graphviz (www.graphviz.org)来画关系图,随便用个脚本语言(我用了scala)或者Java遍历xhtml文档生成dot文件就行了。facelet除了模板标签库(ui标签库)外,还提供了自定义模板组件,函数和Tag Handler的功能。

5. JSF只是个规范,不同厂商都会提供一些额外的特性。要好好理解和利用这些特性,增强的组件库只是其中一点。通常不同的厂商在注入、绑定和EL上都会有一些改进。

6. 自己动手写应用。

7. 入门之后,有空就可以研究一下怎样做JSF的组件和渲染器了。标准JSF的自定义组件方案还是比较复杂的,不过厂商一般都会有自己的简化方案,以便新组件能融入原来的体系。对于项目来说,自定义组件不是必须的,但如果熟悉了自定义组件和渲染器,就基本上能在JSF里为所欲为了。

8. 非常有空的时候,老老实实从头到尾看一遍JSF规范(www.jcp.org)和API的javadoc。