2008年9月28日星期日

Java 设计架构(二)


第三章 J2EE系统的架构设计

第一节              J2EE中常用的概念

在软件业迈向组件装配工业(software component industry) 的过程中﹐不断发现组件设计者对其组件应用场合的预想环境与应用软件师的软件体系结构常常无法很好地整合起来﹐导致应用软件开发人员难以灵活地复用他人设计好的组件,造成软件组件工业发展上的瓶颈。OOP软件专家也逐渐认识到其问题是来自于软件主架构上的不兼容。软件主架构的重要性并非今天才呈现出来﹐20多年前软件大师Fred.P.Brooks 就提到:软件开发者之间﹐他们设计的理念必须一致才能共同创造出简单易用的软件,同时他也强调软件主架构在达到概念一致的过程中,合作居于核心角色。现在,开发者们在项目开始时不是讨论要不要使用架构,而是讨论究竟该使用什么样的架构。

(1) 体系结构(Architecture)

体系结构也可称为架构,所谓软件架构﹐根据Perry 和Wolfe之定义:Software Architecture = {Elements,Forms, Rationale / Constraint },也就是软件主架构 = {组件元素,元素互助合作之模式,基础要求与限制}。Philippe Kruchten采用上面的定义﹐并说明主架构之设计就是:将各组件元素以某些理想的合作模式组织起来﹐以达成系统的基本功能和限制。

(2) 架构(Framework)

  框架也可称为应用架构,框架的一般定义就是:在特定领域基于体系结构的可重用的设计。也可以认为框架是体系结构在特定领域下的应用。框架比较出名的例子就是MVC(模型-视图-控制)。

(3) 库(Library)

  库应该是可重用的、相互协作的资源的集合,供开发人员进行重复调用。它与框架的主要区别在于运行时与程序的调用关系。库是被程序调用,而框架则调用程序。常见的库有Java API,Apache组织提供的Java开发包。

(4) 设计模式(Design Pattern)

设计模式应该很熟悉,尤其四人帮所写的书更是家喻户晓。"四人帮"将模式描述为"在一定的环境中解决某一问题的方案"。这三个事物 — 问题、解决方案和环境 — 是模式的基本要素。

(5) 平台(Platform)

由多种系统构成,其中也可以包含硬件部分。

在J2EE系统开发过程中,大致可以分为五大步骤:需求、分析、设计、编码、测试。而体系结构是软件的骨架,是最重要的基础。体系结构是涉及到每一步骤中。一般在获取需要的同时,就应该开始分析软件的体系结构。体系结构现在一般是各个大的功能模块组合成,然后描述各个部分的关系,J2EE平台已经为我们提供了整个软件系统的体系结构。

架构是体系结构中每个模块中相对细小的结构。如需要表示Web技术,就会用到MVC架构,而Web功能只是整个软件体系中的一个功能模块。每个架构可以有许多个实例,如用Java实现的MVC架构Struts。

而在架构之下就是设计模式,设计模式一般是应用于架构之中,也可以说是对架构的补充。架构只是提供了一个环境,需要我们填入东西。无论是否应用了设计模式,都可以实现软件的功能,而正确应用设计模式,是对前人软件设计思想或实现方法的一种继承。

第二节   MVC架构

上一节中提到基于Web开发的MVC架构目前在J2EE的世界内空前繁荣。在这些架构中,老牌的有Struts、Web work。新兴的有Spring MVC、Tapestry、JSF等。这些大多是著名团队的作品,都提供了较好的层次分隔能力,在实现良好的MVC 分隔的基础上,通过提供一些现成的辅助类库,促进了生产效率的提高。

在这么多J2EE架构中如何选择一个适合自己项目的架构呢?什么是衡量一个架构设计是否优秀的标准?从实际Web产品研发的角度而言(而非纯粹设计上,扩展性上,以及支持特性上的比较),目前Struts 也许是第一选择。它拥有成熟的设计,同时,也拥有最丰富的信息资源和开发群体。从较偏向设计的角度出发,WebWork2 的设计理念更加先进,其代码与Servlet API 相分离,这使得单元测试更加便利,同时系统从B/S结构转向C/S接口也较为简单。另外,对基于模板的表现层技术(Velocity、Free Maker和XSLT)的支持,也为程序员提供了除JSP之外的更多的选择(Struts也支持基于模板的表现层技术,只是实际中不太常用)。而对于Spring而言,首先,它提供了一个相当灵活和可扩展的MVC实现,与WebWork2相比,它在依赖注入方面、AOP 等方面更加优秀,但在MVC 框架与底层构架的分离上又与WebWork2 存在着一定差距,Spring 的MVC 与Servlet API 相耦合,难于脱离Servlet容器独立运行,在这点的扩展性上,比Webwork2稍逊一筹。Spring对于Web应用开发的支持,并非只限于框架中的MVC部分。即使不使用其中的MVC实现,我们也可以从其他组件,如事务控制、ORM模板中得益。同时,Spring也为其他框架提供了良好的支持,很容易就可以将Struts与Spring搭配使用。因此,对于Spring在Web应用中的作用,应该从一个更全面的角度出发。

J2EE系统采用三层的MVC架构之后,其解决的主要问题无外乎以下几部分:

(1)   将Web页面中的输入元素封装为一个(请求)数据对象。

(2)   根据请求的不同,调度相应的逻辑处理单元,并将(请求)数据对象作为参数传入。

(3)   逻辑处理单元完成运算后,返回一个结果数据对象。

(4)   将结果数据对象中的数据与预先设计的表现层相融合并展现给用户或将其持久化。

这样的J2EE系统将具有下以几个优点:

(1)   多个视图能共享一个模型。在MVC架构中,模型响应用户请求并返回响应数据,视图负责格式化数据并把它们呈现给用户,业务逻辑和表示层分离,同一个模型可以被不同的视图重用,所以大大提高了代码的可重用性。

(2)   模型是自包含的,与控制器和视图保持相对独立,所以可以方便地改变应用程序的数据层和业务规则。由于MVC的三个模块相互独立,改变其中一个不会影响其它的两个,所以依据这种设计思想能构造良好的松耦合的构件。

(3)   控制器提高了应用程序的灵活性和可配置性。

    使用MVC需要精心的计划,由于它的内部原理比较复杂,所以需要花费一些时间去理解它。将MVC运用到J2EE应用程序中,会带来额外的工作量,增加应用的复杂性,所以MVC不适合小型应用程序。

面对大量用户界面,业务逻辑复杂的大型应用程序,MVC将会使软件在健壮性,代码重用和结构方面上一个新的台阶,尤其是商业软件的高度可变性。在我实际开发过的项目(一个电子商务网站与一个搜索引擎,和正在开发的一个进销存管理系统)中,我都应用到了Struts,项目相对来说比较成功,通过实践认识到并不是系统中加入了Struts的类库和标签就说明使用了MVC Struts,Struts仅仅是一种思想,你也可以不用它的类库模拟MVC环境,软件的开应该要做到灵活多变。

第三节 分布式架构

J2EE的两大特征就是分层与分布。据统计,大多数中小企业级应用都用不上分布式,因此本文侧重介绍多层,简要的介绍一下分布式的适用范围。

"记住分布式计算机的第一法则:不要分布你的对象!"—(Martin Fowler Patterns of Enterprise Application Architecture)。我们真正需要分布式应用吗?其实我们早已意识到大部分J2EE应用程序,特别是WEB应用程序,并不能从分布式体系架构中受益。甚至相反,由于前期的过渡设计,在根本无需分布式的应用中大量使用分布式技术,不但没有享受到分布式的优点,而且还带来了不同应用层之间昂贵的远程调用,引入了复杂的远程访问期间基础架构和分布式编程。同时,我们也必须明白逻辑层的分层远比物理层的分隔重要。选择分布式也就是选择EJB,选择EJB也就是选择了重量级组件。

也就是说,一些应用选择分布式架构应该有足够的理由。如果真正是明确的业务需求,这属于应有的分布式应用(internal distribution),你需要根据特定的情形选择适当的分布式机制,如果是为了灵活性,在项目的未来某个时期也许要远程输出某些功能,应用程序的架构应该是允许引入选择性的分布式应用(selective distribution),而不是在项目早期就进行过渡设计,直接使用分布式架构。

第四章 数据持久层的设计

第一节 业务对象的持久化

业务对象(Business Object),是对真实世界的实体的软件抽象,它可以代表业务领域中的人,地点,事物或概念。业务对象包括状态和行为,判断一个类是否成为业务对象的一个重要标准,是看这个类是否同是拥有状态和行为。业务对象具有的特征:包含状态和行为,代表业务领域的人,地点,事物或概念。可以重用。通常分为三类业务对象:

(1)      实体业务对象,代表人,地点,事物或概念的组件,在分布式应用中可以作为实体EJB,在更一般的Web应用中可以作为包含状态和行为的Java Bean

(2)   过程业务对象,代表应用中的业务过程或流程,通常依赖于业务对象。在J2EE应用中,它们通常作为会话EJB或是消息驱动EJB。在非分布式应用中,我们可以使用具有管理和控制应用行为的常规Java Bean

(3)   事件业务对象,代表应用中的一些事件,如异常,警告或是超时。

业务对象提供了通用的术语概念,不管是技术人员还是非技术人员都可以共享并理解他们,并且它可以隐藏实现细节,对外只暴露接口。

业务对象在Java编程中可以分两类,分别是普通纯Java对象(pure old java object or plain ordinary java object or what ever POJO)和持久对象(Persistent Object PO)。

    持久对象实际上必须对应数据库中的entity,所以和POJO有所区别。比如说POJO是由new创建,由GC(垃圾收集器)回收。但是持久对象是insert数据库创建,由数据库delete删除的。持久对象的生命周期和数据库密切相关。另外持久对象往往只能存在一个数据库Connection之中,Connection关闭以后,持久对象就不存在了,而POJO只要不被GC回收,总是存在的。由于存在诸多差别,因此持久对象PO(Persistent Object)在代码上肯定和POJO不同,起码PO相对于POJO会增加一些用来管理数据库entity状态的属性和方法。

持久化意味着通过手工或其化方式输入到应用中的数据,能够在应用结束运行后依然存在,即使应用运行结束或者计算机关闭后,这些信息依然存在,不管什么样的系统都需要数据的持久化。我们将需要持久化处理的BO称之为PO,当应用中的业务对象在内存中创建后,它们不可能永远存在,在从内存中清除之前,要被持久化到关系数据库中。

第二节 数据访问对象设计模式

上一节中提到PO在从内存中清除之前要被持久化到关系数据库中,如何做到这一点呢?OOP是当今的主流,但是不得不使用关系型数据库,因此在企业级开发中,对象-关系的映射(Object-Relation Mapping,简称ORM)就是很关键的一部分了。围绕ORM和数据持久化的方式,软件领域通常采用一种数据访问对象(Data Access Object,简称DAO)设计模式。DAO模式提供了访问关系型数据库系统所需要的所有操作接口,其中包括创建数据库,定义表,字段和索引,建立表间的关系,更新和查询数据库等。DAO模式将底层数据库访问操作与高层业务逻辑分离开,对上层提供面向对象的数据访问接口(接口的实现通常使用抽象工厂设计模式来实现)。在DAO的实现中,可以采用XML语言来配置对象和关系型数据之间的映射。在映射数据库表时,值对象类及其子类所构成的树形结构被用来映射一个数据库表,该继承树通过XML 配置文件对应数据库中的单个表,这使得底层的关系型的数据库表结构能够面向对象模型所隐藏,另外,由于面向对象设计方法中类的可继承性,采用继承树对应一个表的策略使得该映射策略极易扩展,并且能够将一个复杂的数据表转化成若干简单的值对象来表示,提高了系统的可维护性和可修改性。

对于一般的J2EE应用,可以直接通过JDBC编程来访问数据库。JDBC可以说是访问持久层最原始,最直接的方法。在企业级应用开发中,可以通过JDBC编程,来开发自己的DAO API,将数据库访问期间操作封装起来,供业务层统一调用。因此DAO所封装的接口在团队开发中显得非常的重要,几乎是项目成败的关键。

DAO模式在系统中所处的位置

DAO的实现通常使用工厂方法模式,工厂方法模式将创建实例的工作与使用实例的工作分开,也就是说,让创建实例所需要的大量初始化工作从简单的构造函数中分离出去。只需要调用一个统一的方法,即可根据需要创建出各种对象的实例,对象的创建方法不再用编码到程序模块中,而是统一编写在工厂类中。这样在系统进行扩充修改时,系统的变化仅存在于工厂类内部,而绝对不会对其他对象造成影响。

第三节 ORM框架的原理和重要性

如果数据模型非常复杂,那么直接通过JDBC编程来实现持久化框架需要有专业的知识,这样可以直接使用采用第三方提供的持久化框架。如Hibernate,Ibatis,JDO。

在使用ORM框架时也需要遵守OOP设计原则和MVC框架的基本原则,都应该确保框架没有渗透到应用中,应用的上层组件应该和ORM框架保持独立。ORM追求的目标就是要 PO在使用上尽量和POJO一致,对于程序员来说,他们可以把PO当作POJO来用,而感觉不到PO的存在。ORM框架就是要做到维护数据库表记录的PO完全是一个符合Java Bean规范的POJO,没有增加别的属性和方法。

    同是ORM框架,但不同的ORM工具实现的方式有所不同,目前主要有两种ORM工具竞争,JDO与Hibernate,以下介绍一下它们的工作机理。

    JDO的实现ORM方法如下:

(1)   编写POJO。

(2)   编译POJO。

(3)   使用JDO的一个专门工具,叫做Enhancer,一般是一个命令行程序,手工运行,或者在ant脚本里面运行,对POJO的class文件处理一下,把POJO替换成同名的PO。

(4)   在运行时运行的实际上是PO,而不是POJO。

该方法有点类似于JSP,JSP也是在编译期被转换成Servlet来运行的,在运行期实际上运行的是Servlet,而不是JSP。

    Hibernate的实现方法如下:

(1)   编写POJO,通常我们可以通过Hibernate官方提供的MiddleGen for Hibernate 和Hibernate Extension工具包,很方便的根据现有数据库,导出数据库表结构,生成XML格式的ORM配置文件和POJO。

(2)   编译POJO。

(3)   直接运行,在运行时,由Hibernate的cglib库利用Java的反射技术动态把POJO转换为PO。

由此可以看出Hibernate是在运行时把POJO的字节码转换为PO的,而JDO是在编译期转换的。一般认为JDO的方式效率会稍高,毕竟是编译期转换嘛。但是Hibernate的作者Gavin King说cglib(Hibernate类库中的一个必需使用的jar包)的效率非常之高,运行期的PO的字节码生成速度非常之快,效率损失几乎可以忽略不计。实际上运行时生成PO的好处非常大,这样对于程序员来说,是无法接触到PO的,PO对他们来说完全透明。可以更加自由的以POJO的概念操纵PO。Hibernate查询语言(Query Language),即HQL,HQL是一种面向对象的查询语言,不同于SQL(结构化查询语言),它具备继承、多态和关联等特性。

Hibernate是从PO实例中取values的,所以即使Session关闭,也一样可以get/set,可以进行跨Session的状态管理。

    在N层的J2EE系统中,由于持久层和业务层和Web层都是分开的,此时Hibernate的PO完全可以当作一个POJO来用,在各层间自由传递,而不用去管Session是开还是关。如果你把这个POJO序列化的话,甚至可以用在分布式环境中。

    因此,在较为常用的数据持久方案中,Hibernate的ORM框架是最优秀的,下面是对各种持久方案的比较:

l        流行的数据持久层架构:

Business Layer <-> Session Bean <-> Entity Bean <-> DB

l        为了解决性能障碍的替代架构:

Business Layer <-> DAO <-> JDBC <-> DB

l        使用Hibernate来提高上面架构的开发效率的架构:

Business Layer <-> DAO <-> Hibernate <-> DB

  通过实际开发和测试,分析出以上三个架构的优缺点:

(1)   内存消耗:采用JDBC的架构无疑是最省内存的,Hibernate的架构次之,EJB的架构最差。

(2)   运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EJB的架构效率会差的很远。

(3)   开发效率:在有Eclipse、JBuilder等开发工具的支持下,对于简单的项目,EJB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EJB架构很可能会失败。

在我工作时所做过的一些项目的过程中,例如电子商务网站 (通常是在写数据库查询和存储过程接口时),面对大量的数据反复存储调用,工作量之大无法忍受,这个很烦恼的持久层开发的问题一直在困扰我。持久层的开发现在一般来说要么用CMP,要么用JDBCDAO CMP需要的成本太高,对于这种无需对象分布的系统并不实用,而JDBC+DAO也存在很多的困难,尤其是数据库表很庞大关系很复杂(通常的商业软件的数据库都非常的庞大)的时候,我很难做到把关系表记录完整的映射到PO的关系上来,这主要体现在多表的关系无法直接映射到对持久对象的映射上来,可能是一个表映射多个持久对象,有可能是多个表映射一个PO,更有可能的是表的某些字段映射到一个持久对象,但是另外一些字段映射到别的持久对象上。而且即使这些问题都处理好了,也不能直接按照对象的方式来对持久对象(PO)编程,因为存在1N关系的持久对象的查询其实就是1+n次对数据库的SQL,等于是完全抛弃了对象设计,完全是按照表字段进行操作,但在这种情况下重复编程量简直无法想象。更重要的是这样做会为系统的成败留下非常大的隐患,因为一般系统是从需求设计,系统设计这样自顶而下的,如果都到了详细设计阶段被持久层映射问题限制,不得不自底向上修改设计方案,又回到了过程化设计上来,严重了违背了依赖倒转原则(DIP) 很明显我并不是第一个遇到这种问题的人,其实这是一个经典的问题:对象和关系的映射问题。自从OOP流行以来,就一直存在这个难题,所以才有人提出将关系数据库进行重新设计,也会有对象型数据库的出现,但实际上关系数据库并没有被淘汰,于是就只能在上层的应用层找解决方案。ORM产品正是为这种解决方案而设计的。 一个好的ORM应该有如下的特点:

(1)      开源和免费的License,任何人都可以在需要的时候研究源代码,改写源代码,进行功能的定制。

(2)      轻量级封装,避免引入过多复杂的问题,调试容易,也减轻程序员的负担。

(3)      具有可扩展性,API开放,当功能不够用的时候,用户可以自行编码进行扩展。

(4)      开发者活跃,产品有稳定的发展保障。

Hibernate符合以上ORM的标准,它的文档也是非常有特色的地方(而且提供了简体中文版),它不仅仅是 Hibernate的功能介绍那么简单,它实际上是一个持久层设计的最佳实践的经验总结,文档里面的例子和总结全部都是最佳设计的结晶。SUN公司最近发布的EJB3.0中的实体EJB仿效了Hibernate这种轻量级组件技术。

没有评论: