在下面的部分,我讨论用JSF实现JCatalog项目时几个关键方面和设计决定。首先讨论JSF中managed beans和backing beans的定义和使用。然后,我介绍JSF中怎样处理安全、分页、缓存、文件上传、校验和错误消息定制。
Managed bean, backing bean, view object和domain object model
JSF介绍了两个新的术语:managed bean 和 backing bean。JSF 提供一个强大的managed-bean工厂。被JSF执行的JavaBean对象管理被叫做managed beans。一个managed bean描述一个bean怎样被创建和管理。它没有利用bean的功能性。
Backing bean 定义页面特性和处理逻辑与UI组件的联合。每一个backing-bean属性被绑定到组件实例或者它的值中的一个。一个backing bean也定义一个组件可执行的功能的集合,例如,校验组件的数据,处理组件触发事件,组件激活时与导航相关的执行过程。
一个典型的JSF应用程序在应用程序的每一个页面中连接一个backing bean。然而,有时在真实的世界里,强迫一个backing bean和一个页面一对一的关系不是一个理想的解决方案。它能造成象代码重复这样的问题。在真实世界的场景里,几个页面可能需要共享在后台的同样的backing bean。例如,在JCatalog项目里,CreateProduct和EditProduct页面共享同样的ProductBean的定义。
一个试图对象是在表示层明确使用的模型对象。它包含了必须显示在视图层的数据和校验用户输入,处理事件和与业务逻辑层相结合的逻辑。backing bean是基于JSF应用程序的视图对象。
在这篇文章中Backing bean 和视图对象是可交换的术语。
比较Struts中的Actionform和Action,在JSF中开发backing beans遵循面向对象设计的最好实践。一个backing bean不仅包含视图数据,也包含与数据相关的行为。在Struts 中,Action 和ActionForm包含数据和逻辑分离。
我们都听说过域对象模型。那么,域对象模型和视图对象有什么不同呢?在一个简单的Web应用程序里,一个域对象模型能被用于所有的层,然而,在一些复杂的Web应用程序里面,一个单独的视图对象模型需要被使用。域对象模型是关于业务对象,应该归入业务逻辑层。它包含特定业务对象相关的业务数据和业务逻辑。一个视图对象包含特定数据和行为的表示。JCatalog项目的ProductListBean提供了一个好的例子。它包含表示层数据和逻辑细节,举例来说,与分页相关的数据和逻辑。从域对象模型分离视图对象的缺点是数据映射必须发生在两个对象模型之间。在JCatalog项目中,ProductBeanBuilder和UserBeanBuilder使用基于反射的Commons BeanUtils去实现数据映射。
安全
当前,JSF没有内置的安全特征。例子的安全需求是基本的:用户连接到管理员使用的公司内部网需要的认证是基于用户名和密码,不需要授权。
在JSF里面几个处理用户认证的方法已经被提出:
Use a base backing bean:这个解决方 案是简单的。然而,它使backing beans与特
殊的遗产层次绑定。
Use JSF ViewHandler decorator:这种方法使安全逻辑紧紧地加上一个特殊的Web层技术。
Use a servlet filter:一个JSF 应用程序与其他基于Java的Web应用程序不同。它在一个恰当的地方使用一个过滤器处理认证检查。这种方法使Web应用程序中的认证逻辑减弱了。
在例子应用程序中,SecurityFilter类处理用户认证。当前,受保护的资源只有三个页面,为了简单,它们被硬编码在Filter类里面。可以通过扩展安全规则来改进它,把受保护的资源放到配置文件中。
分页
应用程序的目录页需要分页。表现层能处理分页,这意味着所有数据必须被重新得到存储在这层。分页也能在业务逻辑层、集成层甚至是EIS层处理。JCatalog 项目的假定是在目录中的产品不超过500种。所有产品信息适合保存在用户session中。分页逻辑存在ProductListBean类中。与分页有关的参数“每页的产品”通过JSF的managed-bean工具配置。
缓存
缓存是Web应用程序中改善性能的众多重要技术中的一种。缓存能在应用程序体系结构内的许多层完成。体系结构中的一层减少调用它下面的层时,缓存是非常有益的。JSF managed-bean工具使在表现层实现缓存更容易。通过改变一个managed bean的范围,包含在managed bean中的数据能在不同的范围内缓存。
例子应用程序使用二级缓存。第一级缓存在业务逻辑层里面。
CachedCatalogServiceImpl类维护所有产品和目录的读/写缓存。Spring 管理的类作为一个单独服务bean。所以,一级缓存是一个应用程序范围的读/写缓存。
对于简单的分页逻辑和将来应用程序速度的提高,表现层的会话范围内的产品也被缓存。每个用户维护他session里面自己的ProductListBean。缺点是占用系统内存和存在旧数据。在一个用户session的持续时间里,如果管理员更新了目录,用户可能会看到旧的目录数据。然而,基于这样的假定,目录中部超过500种产品,而
且目录更新不频繁,我们应该能够忍受这些缺点。
文件上传
目前,JSF的Sun参考实现中不支持文件上传。Struts有很好的文件上传能力,然而,Struts外观集成库是必须使用。 在JCatalog项目中,一个图片与每个产品关联。一个用户创建一个新的产品之后,必须上传与之相关的图片。图片保存在应用服务器的文件系统里面。产品ID是图片名.
例子应用程序使用<input type="file">,Servlet 和Jakarta通用的文件上传API,实现一个简单的文件长传功能。这个功能使用两个参数:产品图片路径和图片上传结果页面。它们都通过ApplicatonBean配置。请参考FileUploadServlet类的详细资料。
校验
JSF具有的标准校验是简单基本的,不能满足真实的需求。开发自己的JSF校验是容易的。我在例子应用程序中使用定制标签来开发SelectedItemsRange校验。它校验通过UISelectMany UI组件选择的项目数量:
<h:selectManyListbox value="#{productBean.selectedCategoryIds}" id="selectedCategoryIds">
<catalog:validateSelectedItemsRange minNum="1"/>
<f:selectItems value="#{applicationBean.categorySelectItems}" id="categories"/>
</h:selectManyListbox>
更多的详细资料请参考例子应用程序。
错误消息定制
在JSF里面,你可以设置资源包定制转换和校验时的错误消息。资源包被设置在faces-config.xml里面:
message-bundle>catalog.view.bundle.Messages</message-bundle>
The error message's key-value pairs are added to the Message.properties file:
#conversion error messages
javax.faces.component.UIInput.CONVERSION=Input data is not in the correct type.
#validation error messages
javax.faces.component.UIInput.REQUIRED=Required value is missing.
业务逻辑层和Spring Framework
业务对象和业务服务在业务逻辑层。一个业务对象不仅包含数据,也有与特定对象关联的逻辑。在例子应用程序中标识了三个业务对象:Product,Category和User.
业务服务与业务对象相结合,提供高级的业务逻辑。一个包含直接使用的服务接口的标准的业务接口层应该被定义。在Spring Framework的帮助下,POJO实现了Jcatalog项目业务逻辑层。有两个业务服务:CatalogService包含与目录管理相关的业务逻辑,UserService包含了用户管理逻辑。
Spring是基于控制反转概念(IOC,inversion of control)。在例子应用程序中使用的Spring特征包括:
Bean management with application contexts:Spring能有效地组织我们的中间层对象,垂直处理。Spring 能避免一个实体功能的分解,促进好的面向对象设计实现,举例来说,接口设计。
Declarative transaction management:Spring 使用AOP(aspect-oriented programming)去描述不使用EJB容器的声明性事务处理。这种方法,事务管理能被应用到任何POJO。Spring事务管理不和JTA(Java Transaction API)绑定,使用不同的事物策略工作。在例子应用程序中声明性事务管理Hibernate中的事务。
Data-access exception hierarchy:Spring提供一个值得回味的异常层次代替SQLException。使用Spring数据访问异常层次,Spring数据访问异常翻译者必须在Spring配置文件里面定义:
<bean id="jdbcExceptionTranslator"
class= "org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
在例子应用程序中,如果插入的一个新产品的ID是重复的,一个 DataIntegrityViolationException 会被抛出。这个异常会被捕获然后作为DuplicateProductIdException被抛出。这种方法,DuplicateProductIdException能处理不同的数据访问异常。
Hibernate integration:Spring不强迫我们使用它强大的JDBC抽象特征。它和O/R映射框架集成的很好,尤其是Hibernate。Sping提供有效的、安全的Hibernate会话操作。在应用程序上下文操作Hibernate的配置SessionFactories 和JDBC数据源,使应用程序容易测试。
集成层和HIbernate
Hibernate是一个开源O/R映射框架,它减少使用JDBC API的需要。Hibernate支持所有主流的SQL数据库管理系统。Hibernate Query Language是SQL面向对象的最小的扩展来设计的,在对象和关系世界间提供了一个优雅的桥。Hibernate提供数据恢复和更新的工具,事务管理,数据连接池,programmatic and declarative queries,声明实体关系管理。
Hibernate和其他O/R映射框架相比入侵性较小。使用反射和运行时产生字节码,SQL在系统开始时产生。它允许我们开发符合Java习惯的持久对象,包括联合,继承,多态,聚合和Java集合框架。在例子应用程序中的业务对象是POJO,不需要实现Hibernate的特殊接口。
Data Access Object (DAO)
JCatalog项目使用DAO模式。这个模式抽象和封装了所有对数据源的访问。应用程序有两个DAO接口。CatalogDao和UserDao。它们的实现类是HibernateCatalogdaoImpl和HibernateUserDaoImpl包含与Hibernate相关的管理和持久化数据逻辑。