第2篇 整合开发
第2章 JSF的基本用法
JSF规范本身就是Java EE 5的MVC规范,它提倡以页面组件的方式隐藏传统Web应用开发的HTTP细节,允许开发者以传统桌面编程的方式来开发Web应用。JSF通过将托管Bean(Managed-Bean作用等同于控制器)的属性、方法直接绑定到页面组件的value属性或者action属性,就可以非常方便地实现系统的MVC控制。
JSF作为Java EE的规范一经提出,就得到许多Web开发者的青睐,为了让开发者可以在实际项目中使用JSF作为MVC框架,Sun公司为JSF规范提供了JSF RI(Reference Implementation)实现;不仅如此,Apache也为JSF规范提供了一套实现:MyFaces,这样就给了开发者很大的选择空间。随着Java EE规范的不断深入,越来越多的Java EE开发者选择使用JSF作为前端MVC框架,不仅以EJB为核心的经典Java EE应用大都采用JSF作为前端MVC框架,即使那些基于Hibernate、Spring框架的轻量级Java EE应用,也有不少选择JSF作为前端MVC框架。
2.1 MVC和JSF
JSF并不是最早出现的MVC框架,但它是一款比较具有革命意义的MVC框架,它与Struts这种框架不同,Struts依然是基于请求-响应模型的,只是提供了更简捷的封装;而JSF则采用面向对象的事件通信机制,Web编程中请求-响应模型完全被隐藏起来了。
2.1.1 MVC和常见MVC框架
早期的Java Web开发,大都采用Model 1模型进行开发,Model 1模型主要以JSP为主,辅助以少量JavaBean处理数据库连接等功能即可。对于Model 1模型的Web应用,整个应用几乎全部由JSP页面组成,JSP页面接收处理客户端请求,对请求处理后直接做出响应。用少量的JavaBean来处理数据库连接、数据库访问等操作。图2.1显示了Model 1的程序流程。
图2.1 Model 1的程序流程
Model 1模式的实现比较简单,适用于快速开发小规模项目。但从工程化的角度看,它的局限性非常明显:JSP页面身兼View(视图)和Controller(控制器)两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展和维护的难度。
早期有大量ASP和JSP技术开发出来的Web应用,这些Web应用都采用了Model 1架构。
Model 2已经是基于MVC架构的设计模式。在Model 2架构中,Servlet作为前端控制器,负责接收客户端发送的请求,在Servlet中只包含控制逻辑和简单的前端处理;然后,调用后端JavaBean来完成实际的逻辑处理;最后,转发到相应的JSP页面处理显示逻辑。其具体实现方式如图2.2所示。
正如图2.2中看到的,Model 2下JSP不再承担控制器的责任,它仅仅是表现层角色,仅仅用于将结果呈现给用户,JSP页面的请求与Servlet(控制器)交互,而Servlet负责与后台的JavaBean通信。在Model 2模式下,模型(Model)由JavaBean充当,视图(View)由JSP页面充当,而控制器(Controller)则由Servlet充当。
由于引入了MVC模式,使Model 2具有组件化的特点,更适用于大规模应用的开发,但也增加了应用开发的复杂程度。原本需要一个简单的JSP页面就能实现的应用,在Model 2中被分解成多个协同工作的部分,需要花费更多的时间才能真正掌握其设计和实现过程。
图2.2 Model 2的程序流程
Model 2已经是MVC设计思想的架构了,下面简要介绍MVC设计思想的优势。
注意
对于非常小型的Web站点,如果后期的更新、维护工作不是特别大,可以使用Model 1模式来开发应用,而不是使用Model 2模式。虽然Model 2提供了更好的可扩展性及可维护性,但增加了前期开发成本。从某种程度上讲,Model 2为了降低系统后期维护的复杂度,却导致前期开发的更高复杂度。
MVC并不是Java语言所特有的设计思想,也并不是Web应用所特有的思想,它是所有面向对象程序设计语言都应该遵守的规范。
MVC思想将一个应用分成三个基本部分:Model(模型)、View(视图)和Controller(控制器),这三个部分以最少的耦合协同工作,从而提高应用的可扩展性及可维护性。
起初,MVC模式是针对相同的数据需要不同显示的应用而设计的,其整体结构如图2.3所示。
图2.3 MVC结构
在经典的MVC模式中,事件由控制器处理,控制器根据事件的类型改变模型或视图,反之亦然。具体地说,每个模型对应一系列的视图列表,这种对应关系通常采用注册来完成,即:把多个视图注册到同一个模型,当模型发生改变时,模型向所有注册过的视图发送通知;接下来,视图从对应的模型中获得信息,然后完成视图显示的更新。
从设计模式的角度来看,MVC思想非常类似于观察者模式,但与观察者模式存在少许差别:观察者模式下观察者和被观察者可以是两个互相对等的对象;但对于MVC思想而言,被观察者往往只是单纯的数据体,而观察者则是单纯的视图页面。
概括起来,MVC有如下特点:
多个视图可以对应一个模型。按MVC设计模式,一个模型对应多个视图,可以减少代码的复制及代码的维护量,一旦模型发生改变,也易于维护。
模型返回的数据与显示逻辑分离。模型数据可以应用任何的显示技术,例如,使用JSP页面、Velocity模板或者直接产生Excel文档等。
应用被分隔为三层,降低了各层之间的耦合,提供了应用的可扩展性。
控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起,完成不同的请求。因此,控制层可以说是包含了用户请求权限的概念。
MVC更符合软件工程化管理的精神。不同的层各司其职,每一层的组件具有相同的特征,有利于通过工程化和工具化产生管理程序代码。
相对于早期的MVC思想,Web模式下的MVC思想则又存在一些变化,因为对于一个应用程序而言,我们可以将视图注册给模型,当模型数据发生改变时,即时通知视图页面发生改变;而对于Web应用而言,即使将多个JSP页面注册给一个模型,当模型发生变化时,模型也无法主动发送消息给JSP页面(因为Web应用都是基于请求/响应模式的),只有当用户请求浏览该页面时,控制器才负责调用模型数据来更新JSP页面。
注意
MVC思想与观察者模式有一定的相似之处,但并不完全相同。经典的MVC思想与Web应用的MVC思想也存在一定的差别,引起差别的主要原因是因为Web应用是一种请求/响应模式下应用,对于请求/响应应用,如果用户不对应用发出请求,视图无法主动更新自己。
目前开发领域主流的MVC框架也非常多,其中Struts 1.x、Struts 2.x依然是MVC框架的主流,接下来应用比较广泛的就应该是JSF了。下面简单介绍这些主流的MVC框架。
2.1.1.1 Struts
Struts目前有两个差别较大的版本,一个是传统的Struts 1.x,另一个是由WebWork演化而来的Struts 2.x。就Struts 2.x而言,由于它采用了基于拦截器(实际上就是AOP思想)的设计方式,因此非常灵活,而且扩展性很强。而且由于Struts是最早出现的MVC框架之一,因此使得Struts 1.x、Struts 2.x都拥有非常广泛的开发人群。
就Struts 1.x、Struts 2.x的设计模型来看,它们依然是以Servlet为中心,基于请求-响应架构的,这类MVC框架的典型特征就是:它们总是通过提交表单来发送POST请求,MVC框架所做的事情就是把一系列通用的步骤(如解析请求参数、对字符串类型请求参数执行类型转换、完成输入校验)抽取出来,由MVC框架完成,从而保证开发者能更好地专注于业务代码的编写。
由于Struts 1.x、Struts 2.x和JSP、Servlet原有的请求-响应模型极为接近,非常接近于Web程序员的开发思维,因此很容易就可以获得Web程序员的青睐,这也是为何Struts 1.x、Struts 2.x占据了很大开发市场的重要原因之一。
但这类MVC框架也有与生俱来的弱点:
基于请求-响应的架构编程相对比较烦琐,开发效率比较低下。
请求-响应的架构不太符合面向对象的编程思维,尤其是对于习惯了事件通信机制的C/S程序员而言,请求-响应架构显得更加烦琐,而且难于理解。
2.1.1.2 Tapestry
Tapestry并不是一种单纯的MVC框架,它更像MVC框架和模板技术的结合,它不仅包含了前端的MVC框架,还包含了一种视图层的模板技术,使用Tapestry完全可以与Servlet/JSP API分离,是一种非常优秀的设计。
通过使用Tapestry,开发者完全不需要使用JSP技术,只需要使用Tapestry提供的模板技术即可,Tapestry实现了视图逻辑和业务逻辑的彻底分离。
Tapestry使用组件库替代了标签库,没有标签库概念,从而避免了标签库和组件结合的问题。Tapestry是完全组件化的框架。Tapestry只有组件和页面两个概念,因此,链接跳转目标要么是组件,要么是页面,没有多余的path概念。组件名,也就是对象名称,即组件名称和path名称合二为一。
Tapestry具有很高的代码复用性,在Tapestry中,任何对象都可看做可复用的组件。JSP开发者是真正面向对象的,而不是URL解析。对于对页面要求灵活度相当高的系统,Tapestry是第一选择。精确的错误报告,可以将错误定位到源程序中的行,取代了JSP中那种编译后的提示。
因此,笔者一直对Tapestry情有独钟:如果技术允许,使用Tapestry会带给整个应用更加优雅的架构、更好的开发效率。
但是,在实际开发过程中,采用Tapestry也面临着一些问题必须考虑:
Tapestry的版本更新速度过快,而且前后版本的兼容性不是很好。
Tapestry的学习成本相对较大,因而国内开发群体不是非常活跃,文档也不是十分丰富。官方的文档偏重于理论讲解,缺乏实际的示例程序。
Tapestry的组件逻辑比较复杂,再加上OGNL表达式和属性指定机制,因而难以添加注释。
2.1.1.3 Spring MVC
Spring提供了一个细致完整的MVC框架,该框架为模型、视图、控制器之间提供了一个非常清晰的划分,各部分耦合极低。Spring的MVC是非常灵活的,它完全基于接口编程,真正实现了视图无关。视图不再强制要求使用JSP,可以使用Velocity、XSLT或其他视图技术,甚至可以使用自定义的视图机制——只需要简单地实现View接口,并且把对应视图技术集成进来。Spring的Controllers由IoC容器直接管理,因此,单元测试更加方便。
Spring MVC框架以DispatcherServlet为核心控制器,该控制器负责拦截用户的所有请求,将请求分发到对应的业务控制器。
Spring MVC还包括处理器映射、视图解析、信息国际化、主题解析、文件上传等。所有控制器都必须实现Controller接口,该接口仅定义ModelAndView handleRequest(request,response)方法,通过实现该接口来实现用户的业务逻辑控制器。
Spring MVC框架有一个极好的优势,就是它的视图解析策略:它的控制器返回一个ModelAndView对象,该对象包含视图名字和Model,Model提供了Bean的名字及其对象的对应关系。视图名解析的配置非常灵活,抽象的Model完全独立于表现层技术,不会与任何表现层耦合:JSP、Velocity或者其他的技术——都可以和Spring整合。
总体上来看,Spring MVC框架致力于一种完美的解决方案,并与Web应用紧紧耦合在一起。但这也导致Spring MVC框架出现了一些缺点:
Spring的MVC与Servlet API耦合,难以脱离Servlet容器独立运行,降低了Spring MVC框架的可扩展性。
太过细化的角色划分,太过烦琐,降低了应用的开发效率。
过分追求架构的完美,有过度设计的危险。
就笔者对各大公司实际开发的了解,实际使用Spring MVC作为MVC框架的公司并不多。
2.1.2 JSF的优势
相对于传统的基于Servlet模型的MVC框架(如Struts)而言,JSF最大的特色在于UI组件和事件机制。所谓UI组件,就是JSF框架提供的一系列JSP自定义标签库,通过这些自定义标签库,开发者可以高效地开发JSP视图页,而且UI组件属于服务器组件(有ASP.NET编程经验的读者应该很熟悉),服务器组件的最大特色在于该组件的value可以“自动”绑定到托管Bean的属性——这里给自动加上引号是因为所谓的自动其实是由JSF框架替开发者做了,无须开发者关心而已。对于JSF的事件机制而言,笔者觉得Sun显然是为了照顾那些不太习惯请求-响应架构的程序,JSF框架接管了请求-响应架构模型,它允许开发者以面向对象世界里的事件通信模型来开发Web应用,这对许多从C/S开发过渡到B/S开发的程序员非常具有吸引力。
首先来看传统JSP、Servlet的编程模型中对一次HTTP请求必须要完成的处理步骤。
(1)通过JSP的HttpServletRequest对象获取服务器请求参数。
(2)由于所有请求参数都是String类型的,开发者需要将String类型的参数转换为实际所需的类型。转换过程中可能出现转换失败,比如要求数值,但用户输入了字母abc,这就要求程序员处理转换失败的异常。
(3)输入有效性校验。开发者必须验证用户输入是否有效,比如保证人的年龄不能是负数,也不能超过150。有效性校验可以保证底层数据库的完整性。
(4)如果用户输入不能通过类型转换或不能通过有效性校验,程序必须通过显示页面向用户返回错误提示。
(5)如果用户输入可以成功通过类型转换,也能成功通过有效性校验,接着调用业务逻辑组件来处理用户请求。
(6)将调用业务逻辑组件的结果返回给用户。如果调用业务逻辑组件处理成功,返回处理成功的提示;如果处理失败,返回处理失败的提示。
……
上面这6个步骤就是传统JSP开发中程序员对一次HTTP请求必须要做的事情。注意:这只是处理一次请求,已经变得如此烦琐了,因此这将严重制约Web应用的开发效率。
在这样的背景下,MVC框架应运而生,MVC框架可以代替程序员完成上面6个步骤中的通用步骤,让程序员可以更专注于业务代码的实现,从而提高开发效率。对于上面6个步骤而言,其中(1)(2)(3)(4)(6)都具有一定的通用性,开发者只要进行简单的配置即可。Struts 1.x、Struts 2.x就是这种MVC框架的代表之作。
如果开发人员本来是就是从事Java Web开发的,他们已经习惯上面6个步骤的开发流程,也就对请求-响应的程序架构非常熟悉了,那么当他们使用这类MVC框架时,会感觉有如释重负的感觉——因为MVC框架终于为他们做了大量事情。
但程序员并不容易满足,程序开发里还有一种所谓的RAD开发,RAD是Rapid Application Development的缩写,也就是快速应用开发的意思,典型的RAD开发工具有PowerBuilder、Delphi等。RAD开发工具允许开发者通过拖控件、双击控件编脚本的方式来快速开发,从而提高软件的开发效率。
对于习惯了RAD开发的程序员而言,要他们去理解Web编程中请求-响应的模型就显得比较困难了,要他们习惯上面Web编程中的6个开发步骤显得尤为烦琐。
在这样的背景下,Microsoft率先推出的ASP.NET Web编程颠覆了传统Web编程模型,它提出的服务器组件可以将客户端事件与服务器端应用代码直接关联到一起,这种服务器组件给广大程序员带来极大的冲击,很轻易地就俘获了那些习惯了RAD开发的程序员。
作为与ASP.NET编程的对抗性产品,Sun公司推出了JSF规范,并为之提供了JSF RI实现,JSF主要具有如下几大优势:
允许通过拖放组件的方式来快速开发JSP页面。
允许使用服务器端业务代码来响应客户端事件(假象而已,但对开发者透明)。
将页面上的UI组件的值绑定到服务器端的数据模型。
利用可重用和可扩展的UI组件来构造用户界面。
跨请求保存和恢复UI状态。
借助于JSF的这几大优势,JSF允许开发者无须了解Web编程的请求-响应模型,从而让程序员可以使用RAD方式来开发Web应用,这也就是JSF和传统MVC框架的最大区别。
对于应用程序设计人员而言,JSF提供了一个与开发桌面应用相似的开发模型,我们完全可以采用基于事件的通信机制,而无须理会请求-响应编程模型,从而避免了处理HTTP请求中烦琐的细节问题。在最理想的状态下,完全可以实现早期PowerBuilder那种RAD开发方式:拖控件、编脚本,就可以实现一个完整的Web应用(当然,如果需要Web应用具有较好的可扩展性、可维护性,则应该有更好的设计)。
JSF中的UI组件可以直接绑定服务器端的托管Bean,因此绝大部分应用不再需要开发者去处理HTTP细节。通过使用UI组件可以将页面操作自动映射到托管Bean中,UI组件以事件通信的机制直接与托管Bean发生交互。