二,High-level architecture design(总体架构设计) 设计Web应用系统的下一步是总体的架构设计。它包括将应用程序细分为功能组件,将这些组件划分成若干层。总体架构设计对于具体技术使用是中立的。 Multitiered architecture(多层架构) 多层架构把整个系统划分成明显的功能单元:客户端,表示层,业务逻辑,综合(Integration),EIS。这种架构确保了责任的明确划分,使系统更加易于维护和扩展。三层或多层系统被证明比没有业务逻辑层的c/s系统更加灵活和可扩展。 客户层是数据模型被消费和表示的地方。对于一个Web应用来说,客户层通常是Web浏览器。基于浏览器的瘦客户端没有包含表示逻辑,它要依靠于表示层。 表示层将业务逻辑层的服务暴露给用户。它知道如何处理一个客户端的请求,如何同业务逻辑层交互,如何选择下一个view去显示。 业务逻辑层包含了一个应用程序的业务对象和业务服务。它从表示层收到请求,根据请求处理响应的业务逻辑。业务逻辑层组件大大受益于系统级服务(比如安全管理,事务管理,资源管理)。 集成层是业务逻辑层和EIS(Enterprise Information System)层之间的一座桥梁。它把同EIS层交互的逻辑封装起来。有时候把集成层和业务逻辑层合起来称作中间层。 应用程序数据在EIS层持久化。它包括关系数据库,对象数据库和遗留系统。 JCatalog的架构设计 下图显示了JCatalog的总体架构设计以及它如何实现多层体系结构。
应用程序使用了一个多层非分布式的框架,上图向我们显示了应用层次是如何划分的,每一层使用的具体技术。这张图同时作为示例应用程序的部署图。对于一个配置的架构,表示层,业务逻辑层,集成层都位于同一个Web容器中。定义良好的接口隔离每一层的职责。配置的架构让应用简单,可扩展。 对于表示层,经验告诉我们最佳的方法是选择一个已经存在的,经过考验的Web应用框架,而不是自己设计和构建一个框架。我们有一些Web应用框架可供选择,比如Struts, WebWork, JSF。我们为JCatalog选择JSF作为表示层框架。 无论EJB还是POJO都可以用来构建业务逻辑层。如果应用程序是分布式的,则拥有远程接口的EJB是一个很好的选择。而我们的JCatalog是一个典型的没有远程访问需求的Web应用,所以在Spring框架下的POJO被我们用来实现业务逻辑层。 集成层在关系数据库上处理数据的持久化工作。有不同的方案可以用来实现集成层: l Pure JDBC:这是最灵活的方案;然而底层的JDBC用起来十分笨重,而且劣质的JDBC代码性能也不好。 l Entity beans:对于隔离数据访问代码和处理O/R映射数据持久化,CMP是一个很昂贵的方案。它是一个以app server为中心的方案。一个entity bean不会使应用依赖于某个数据库,却会让应用依赖于某个EJB容器。 l O/R mapping框架:O/R mapping框架是一个以对象为中心的实现数据持久化的方案。以对象为中心的应用很容易开发而且非常的轻便。在这个领域有不少的现成框架:JDO, Hibernate, TopLink, CocoBase等等。我们在本应用中使用Hibernate。
现在我们结合每一层来讨论一下具体的设计问题。因为JSF是相对新的技术,我们会重点讨论它的。 表示层和JSF 表示层收集用户的输入,表示数据,控制页面导航,将用户输入委托给业务逻辑层。表示层也能够验证用户输入和维护应用会话状态。在下面我们会讨论表示层的设计考虑事项和模式,以及为什么我们选择JSF来实现JCatalog的表示层。 Model-View-Controller MVC是Java蓝皮书强烈建议的交互型应用程序使用的结构设计模式。MVC分割设计关注,从而能够减少代码的重叠,集中控制,使应用更加可扩展。MVC同时帮助不同技能的开发者集中于他们擅长的技能方面,通过清晰定义的接口合作在一起。MVC是表示层的结构设计模式。 JavaServer Faces JSF是面向基于Java的Web应用而开发的server-side的UI组件框架。JSF包括了一组API,这些API用来表现UI组件以及保持它们状态;处理事件,服务器端的验证,以及数据转换;定义页面导航;支持国际化和accessibility;以及对这些功能提供可扩展性。同时它还包括两个JSP custom tag libraryies,用来在JSP页面中表示UI组和关联组件与服务器端对象。(实际上JSF现在是一个规范和一组接口以及他提供的参考实现,你也可以自己做你自己的JSF实现,当然难度比较大,如果后面没特指的话“实现”指的就是自带的参考实现) JSF和MVC JSF非常适用于基于MVC的表示层框架。它对行为和表示有着清晰的划分。它支持我们熟悉的UI组件和Web层的概念,却不会把你限制在某些脚本技术或标记语言上。 JSF的backing beans是model层(更多关于backing beans在后面的章节)。它们也可以包含动作,这些动作是作为控制器层的一个扩展以及把用户的请求代理给业务逻辑层。请注意,从整个应用程序的框架来看,业务逻辑层也常常被称为model层。(注意和这里的model层区别开)包含JSF标签的JSP页面是作为View层。而Faces Servlet则提供controller的功能。 为什么使用JSF? JSF不仅仅是另一个Web框架,下面是它与一般的Web框架的不同: l 象Swing一样的面向对象的Web应用开发:服务器端声明的,有event listeners和handlers的UI组件模型(就像Swing的组件),促使能够面向对象的Web应用开发。 l Backing-bean management:Backing bean是在页面中与UI组件关联对应的JavaBeans。Backing bean management将UI组件对象的定义,与保持数据执行应用相关处理的对象区分开来。JSF的具体实现在恰当的范围内储存和管理这些backing-bean的实例。 l 可扩展的UI组件模型:组成JSF应用的JSF UI组件是可配置,可重用的元素。你可以继承这些标准的UI组件来开发更为复杂的组件,比如menu bar,tree组件等等。 l 灵活的表现模型:Renderer把UI组件的功能和它的view分开。不同的Renderer可以被创造出来,用来定义同一种客户端或不同客户端的同一个组件的不同的外观。(简单介绍一下,也就是说你可以定义HTMLRenderer, WMLRenderer来对同一组件生成HTML和WML格式的外观。) l 可扩展的转换和验证模型:你可以在标准的converter和validator的基础上开发你的converter和validator提供更强大的功能。
尽管JSF很强大,但它现在还不成熟。JSF自带的component, converter, validator是很基本简单的。而且每一个组件一个的validation model还不能处理组件和validator之间多对多的validation。JSF标签同JSTL还不能无缝连接。
在下面的章节中,我们将讨论用JSF实现JCatalog的关键部分和设计决定。首先我们讨论一下JSF中managed bean和backing bean的定义和使用。然后再介绍在JSF中如何处理安全,分页,caching,文件上传,验证以及定制的错误信息。
Managed bean, backing bean, view object, and domain object model JSF引入了两个新概念:managed bean和backing bean。JSF提供了强大的管理bean的机制。一个被JSF管理的JavaBean对象叫做managed bean。一个managed bean描述了一个bean如何创建和管理的,这些和bean的功能无关。
Backing bean定义了页面上的UI组件的属性和处理逻辑。每一个backing bean的属性对应一个组件或者组件的值。Backing bean同时定义了一组执行组件功能的方法,比如验证组件的数据,处理组件触发的事件,当组件activate时处理与导航相关的操作。
一个典型的JSF应用中的每一页面都有一个backing bean。然而,实际中强制页面和backing bean的一对一关系不是一个好的做法。它会导致类似代码重复的问题。实际情况中,一些页面也许会共享同一个backing bean。例如在JCatalog中,CreateProduct和EditProduct页面共享同一个ProductBean定义。
一个View对象是一个只在表示层使用的model对象。它包含着必须在View层显示的数据,包含着验证用户输入,处理事件,同业务逻辑层交互的逻辑。在基于JSF的应用中,backing bean就是view对象。在本文中,backing bean和view对象是可互换的概念。 与Struts中的ActionForm和Action概念相比,使用JSF中的backing bean开发更加符合OO设计习惯。一个backing bean不仅仅包含显示数据,还包括与这些数据相关的行为。而在Struts中,ActionForm和Action分别包含数据和逻辑。
我们大家都听说过domain object model(域对象模型)。那么domain object model和view object有什么不同呢?在一个简单的Web应用中,一个域对象模型经常穿越所有的层使用。然而在稍复杂的Web应用中,一个独立的view object是很需要的。Domain object model是关于业务对象(BO)的,应该属于业务逻辑层。它包含业务数据和与特定业务对象关联的业务逻辑。一个view object包含着显示相关的数据和行为。JCatalog的ProductListBean就是view object的一个好例子。它包含着表示层的数据和逻辑,比如分页相关的数据和逻辑。将view object和domain object model分开的一个缺点就是必须在两个对象模型之间进行data mapping。在JCatalog中,ProductBeanBuidler和UserBeanBuilder使用了基于反射的Commons-BeanUtils包来实现data mapping。
安全 目前,JSF并没有内建的安全特性。示例应用的安全需求是很简单的:仅当用户要登录到administration intranet时需要基于用户名密码的认证,而且不需要授权。 对于在JSF中的用户认证,有以下方案: l 使用一个backing bean基类:这各方案很简单,但是会让backing beans依赖于这个继承结构。(也就是backing bean都继承这个基类) l 使用一个JSF ViewHandler包装类:这个方案会把安全逻辑紧紧地限制在JSF这个特殊的Web层技术上。 l 使用servlet filter:一个JSF应用和其他的基于Java的Web应用没什么区别,因此一个filter就是处理认证检查的最好地方。这种方案安全逻辑不会绑定到特定Web应用上。 在示例应用中,SecurityFilter类处理用户的认证。目前,受保护的资源只包括三个页面,所以为了简单起见,把它们的位置硬编码到Filter类里面了,作为改进你可以把具体的安全规则和受保护的资源写入配置文件中。
分页 应用中,Catalog页面需要分页。表示层可以处理分页,同时意味着所有的数据必须在表示层取得和存储。分页也可以在业务逻辑层,集成层甚至EIS层处理。JCatalog的一个假定就是只有不超过500个产品在目录中。所有的产品信息都可以放入用户的session中。分页逻辑做在ProductListBean里面。与分页相关的参数product per page通过JSF managed-bean机制可以配置。
Caching Caching是在Web应用中提高性能的最重要的技术之一。在应用框架中,Caching可以在许多层中实现。若结构中一层能够避免对在它之下的层的调用就是最理想的状况(??)。JSF managed-bean机制使得在表示层Caching非常的容易。通过改变一个managed bean的scope,managed bean中的数据就能在不同的scope中被缓存。
示例应用使用了两级的Caching。第一次层的Caching在业务逻辑层里面。CachedCatalogServiceImpl类维护所有产品和目录的读写cache。Spring把这个类作为一个Singleton的service bean管理。所以,第一层的cache是一个application-scopse的读写cache。
为了简化分页逻辑加快应用程序的速度,产品也被缓存在表示层中,作为范围是session scope。每一个用户在session中维护他自己的ProductListBean。这样做的代价是系统内存和陈旧的数据。在用户的session期间,如果administrator更新目录,用户可能会看到陈旧的数据。然而,基于我们前面的假设,目录中不会超过500的商品,目录不会经常的更新,所以我们可以忍受这些代价。
File upload 目前Sun的JSF参考实现并不支持文件上传。Struts有很好的文件上传功能,但是你要使用这个功能的话Struts-Faces包就是必须的了。在JCatalog中,每一个产品都有相对应的一张照片。当用户创建了一个新的产品后,他必须上传与之对应的图片。图片被存储在应用服务器的文件系统中,用productID作为图片名。 示例程序使用<input type=”file”>,Servlet,和Jakarta Commons的file-upload包来实现一个简单的文件上传功能。这个方案需要两个参数:产品图片的目录和图片上传的结果画面。它们可以在ApplicationBean中进行配置。请查看FileUploadServlet类获得详细信息。
Validation JSF自带的标准的validator是很基本的,不能满足很多实际中的需求。开发你自己的JSF validater是很简单的。作者在示例中用自定义标签开发了SelectedItemsRange validater。它用来验证UISelectMany组件选择的条目数: <h:selectManyListbox value="#{productBean.selectedCategoryIds}" id="selectedCategoryIds"> <catalog:validateSelectedItemsRange minNum="1"/> <f:selectItems value="#{applicationBean.categorySelectItems}" id="categories"/> </h:selectManyListbox> 请在示例中获得更多信息。
Error-message customization 在JSF中,你可以为converter和validator创建resource boudles,定制错误信息。一个resource bundle在faces-config.xml中创建: <message-bundle>catalog.view.bundle.Messages</message-bundle> 错误信息的key-value值加到Message.propertyes文件中: #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框架 业务对象和业务服务位于业务逻辑层。一个业务对象不仅仅包含数据,同时也包含与这个对象相关的逻辑。在示例中,有三个业务对象:Product, Category, User。
业务服务与业务对象作用,并且提供高级别的业务逻辑。一个正式的业务接口应该被定义,它包含了能够被客户端直接调用的服务接口。在Spring框架下的POJO实现了JCatalog的业务逻辑层。这里有两个业务services:CatalogService包含目录管理相关的业务逻辑;UserService包含用户管理逻辑。
Spring是基于反转控制(IoC)的概念上建立的。示例程序中使用到的Spring的特性有: l 使用application context管理bean:Spring能够有效地组织管理我们的中间层的对象,为我们处理plumbing。Spring能够消除Singleton模式的泛滥,促进良好的OO编程习惯,比如面向接口编程。 l 声明式的事务管理:Spring使用AOP(aspect-oriented programming)做到不使用EJB容器也能使用声明式事务管理。通过这种方法,事务管理能够施加于任何POJO上。Spring的事务管理并没有依赖于JTA,它使用其他的事务策略工作。在示例中,我们将在Hibernate的事务中使用声明式事务管理。 l 数据访问异常的继承体系:Spring提供了一个有意义的异常体系替代SQLException。要想使用Spring的异常体系,必须在Spring配置文件中定义Spring数据访问的exception translator: <bean id="jdbcExceptionTranslator" class= "org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean> 在示例程序中,如果一个ID重复的产品被插入的时候,DataIntegrityViolationException就会被抛出。这个异常会被捕捉并且作为DuplicateProductIdException再被抛出。这样,DuplicateProductIdException就能与其他数据访问异常区别开来进行处理。 l 整合Hibernate:Spring并强制我们使用它强大的JDBC抽象的特性。它能够很好地同O/R maping框架结合,尤其是Hibernate。Spring提供了有效安全的Hibernate session操作,Spring在application contexts中处理Hibernate SessionFactory的配置以及处理JDBC data sources,Spring还使应用程序很容易测试。
集成层和Hibernate Hibernate是一个开源的O/R mapping框架,O/R mapping可以减少JDBC API的使用。Hibernate对所有的主流SQL数据库管理系统提供了支持。Hibernatede查询语言HQL是对SQL的最小的OO扩展,它为对象和关系世界提供了一个优雅的桥梁。Hibernate提供了实现数据取得和更新,事务管理,数据库连接池,编程式或声明式的查询,声明式的实体关系管理等等的机能。
Hibernate比其他的O/R mapping框架有更少的侵略性。反射,运行时二进制代码生成被使用。 生成SQL发生在系统启动的时候。它允许我们可以按照Java的习惯开发持久对象(PO),包括使用关联,继承,多态,组合以及Java Collections框架。示例应用中的业务对象(BO)就是POJO,不需要实现任何Hibernate相关的接口。 Data Access Object(DAO) JCatalog中使用了DAO模式。这个模式抽象和封装了所有对数据源的访问。应用程序有两个DAO接口:CatalogDao和UserDao。它们的实现类HibernateCatalogDaoImpl和HibernateUserDaoImpl包含Hibernate特定的管理和持久化数据的逻辑。 |