其他分享
首页 > 其他分享> > 【中间件技术】第一部分 概述(1) 软件构件与中间件基本概念

【中间件技术】第一部分 概述(1) 软件构件与中间件基本概念

作者:互联网

文章目录

这里介绍分布式软件的基本概念、软件构件的基本概念、中间件的动因与基本概念;利用 JDK 的远程方法调用 Java RMI ,开发了一个简单的分布式应用程序,通过该例子演示「软件构件与中间件技术为软件开发提供的基本支持」。


1.1 分布式软件的基本概念

1.1.1 分布式软件与客户机/服务器模型

在计算机硬件与网络通信技术的支持下,应用需求驱使计算机软件的规模与复杂度不断增长,软件正变得无处不在,软件所面临的挑战也正在日益加剧,软件开发过程中复杂度高、开发周期长、可靠性保证难等问题日益突出。在这种背景下,软件开发人员不得不在软件开发的过程中寻求更多的支持,以帮助其在特定的开发周期内开发出规模更大、更可靠的软件系统

这一系列关注在上述背景下大型分布式软件系统的开发支撑。原因主要有两个:

简单地讲,分布式软件是指运行在网络环境中的软件系统,而网络环境是一群通过网络互相连接的处理系统,每个处理节点由处理机硬件、操作系统及基本通信软件等组成分布式计算有两种典型的应用途径

分布式软件通常基于客户机/服务器 Client/Server 模型。如果一个系统的两个组成部分存在如下关系:其中一方提出对信息或服务的请求(称为客户机),而另一方提供这种信息或服务(称为服务器),那么这种结构即可看作是一种客户机/服务器计算模型。

互联网的许多应用程序都采用客户机/服务器模型,例如 Web 浏览器与 Web 服务器、电子邮件客户程序与服务程序、FTP 客户程序与服务程序等;更一般地,在普通的函数或对象方法调用中,「执行调用语句的子程序」与「实现函数/方法体的子程序或对象」可看作一种客户机/服务器模型,其中实现方是服务器,调用方是客户机

分布式软件与传统的集中式软件的主要区别在于,强调客户端与服务端在地理位置上的分离,这种分离可带来许多好处:

当然,客户端与服务端的分离也使得软件系统更复杂:

1.1.2 分布式软件的三层/多层结构

早期的分布式系统基于图1-1所示的两层结构。在两层结构中,简要将软件系统划分为服务器层和客户层,服务器层又称为数据层。这时的系统结构比较简单,就是多个客户端程序共享一个数据库

两层结构中软件开发的主要工作量在客户层。数据层基本没有什么程序代码,主要就是建好数据库,可能利用存储过程实现一些基本的业务逻辑。开发人员所编写的代码,几乎全部都在客户端,一般可以把客户端的代码分为「用户界面相关的代码」和「业务逻辑相关的代码」,在客户端的代码中要访问数据库中的数据,可以执行一些 SQL 语句或调用存储过程。

两层结构下,客户程序直接访问数据库,并且用户界面代码和业务逻辑代码交织在一起,这些导致两层结构存在以下重要的缺陷:

鉴于以上原因,人们提出了图1-2所示的三层结构:
图 1-2 三层结构的分布式系统
在 3 3 3 层结构下,在数据层和客户层之间再增加一个中间层,将原来放在客户端的业务逻辑代码移到中间层来。客户程序与数据库的连接被中间层屏蔽,客户程序只能通过中间层间接地访问数据库,从而降低了客户端的负担、改善了其可移植性,又提高了系统的数据安全性;同时业务逻辑代码与用户界面代码相对独立,也在很大程度上提高了系统的可维护性,较好地解决了两层结构的上述问题。

三层结构中软件开发的主要工作量在中间层中间层包括除用户界面代码与持久数据之外的几乎所有系统代码,是整个软件系统的核心

在 3 3 3 层结构中,客户层和数据层已被严格定义,但中间层并未明确定义。中间层可以包括所有「与应用程序的界面和持久数据存储无关的处理」,假定将中间层划分成许多服务程序是符合逻辑的,那么将每一主要服务都视为独立的层,则 3 3 3 层结构就成为了 n n n 层结构。典型地,可将业务逻辑层分离出实现数据持久化操作的持久层,用于实现对于持久数据操作的封装,从而形成由客户层、业务逻辑层、持久层与数据层构成的四层结构。


1.2 软件构件的基本概念

本节从分布式对象的角度讨论软件构件的特性。分布式对象是构成分布式系统的软件构件除了具备一般软件构件的特征外,其重要特征就是分布特性

1.2.1 对象到构件的发展

按照面向对象的观点,软件系统由若干对象组装而成,将这一观点延伸至分布式系统,分布式系统由若干分布式对象组装而成。面向对象的精髓之一在于,对象维护自身的状态、并通过消息进行通信,这对于分布式计算环境是非常理想的,但是分布式对象与传统对象相比,有其特殊的特性

首先,分布式系统由于其规模与分布特性等原因,比集中式系统更容易被拆分给不同的人或团队开发,这就很有可能遇到不同的人或团队习惯使用不同的程序设计语言和环境的情况。但是,这些使用不同语言的人(团队)开发的程序要能够方便的交互,换句话说就是,用不同的语言所写的对象要能够方便的交互。而在传统的集中式面向对象系统中,显然对象之间的交互是局限于某种特定的语言的。从语言这个角度我们可以看到,分布式对象比传统对象面临的环境更复杂,这要求分布式对象比传统对象具备更好的封装特性,比如,要把实现所使用的语言屏蔽起来,对象的使用者看到的是一个跨语言的对象。

更进一步讲,分布式系统要求分布式对象可以在任何粒度上透明的使用,也就是说,无需考虑位置与实现,不需要考虑具体在哪——是与使用者在同一个进程内,还是在不同的进程内,如果不同又在哪个进程内,或是在哪个机器上的哪个进程内,或者是运行在什么样的操作系统之上等等。这些具体的位置信息,使用者都不希望去过多的关心,另外也不需要关心对象的具体实现细节——如是用什么语言实现的,是不是用面向对象的语言实现的,还是用一个函数库甚至是一个面向对象数据库实现的,都不用去关心。

传统的面向对象语言中的对象,很难满足上面提到的要求。传统对象的关注点是封装通过继承对实现进行重用——封装提供了一种「将对象实现细节与其他对象屏蔽开的严格方法」,可以大大缓解在面向过程系统中较突出的维护问题;继承提供了一种重用对象实现的简便方法。而分布式系统要求分布式对象要有更好的可插入性,这个要求仅仅依靠传统面向对象的封装和继承是不可能满足的:

一般地讲,构件指系统中可以明确辨识的构成成分,而软件构件指软件系统中具有一定意义的、相对独立的构成成分,是可以被重用的软件实体,构件提供了软件重用的基本支持。

分析传统工业,如机械、建筑等行业,以及计算机硬件产业,其成功的基本模式是符合标准的零部件(构件)生产基于标准零部件的产品生产(组装),其中复用是必需的手段,而构件是核心和基础。这种模式是产业工程化、工业化的必由之路,也是软件产业发展的必然途径,这是软件复用与软件构件技术受到高度重视的根本原因。

由于关注分布式系统,对应软件构件也关注分布式构件,因此以后论述中不区分构件与分布式对象这对概念,一般提到的构件均指分布式构件,即分布式对象,反之亦然。

1.2.2 软件构件的特性与相关概念

与对象相比,构件通常具有如下特性:

既然构件具备极好的封装性和可插入性,那么基于构件的系统从可维护性上来讲,就可以达到一种比较理想的状态。如图1-3所示,一个构件化的软件系统在进行维护或升级时,在保持构件接口不变的前提下,可单独对系统中若干个构件进行修改,而不会影响构成系统的其它构件
图1-3 构件化软件系统的升级方式

下面介绍几个构件相关的概念。


1.3 中间件的基本概念

1.3.1 中间件的动因

尽管有了构件技术的支持,但是随着软件系统规模与复杂度的不断提高,软件开发过程中复杂度高、开发周期长、可靠性保证难等突出问题,并没有得到根本缓解;而分布式软件面临更大的挑战,分布式软件所运行的网络环境具有明显的分布性、开放性、演化性、异构性、并发性等特征,因此分布式软件必须解决互操作、数据交换、分布性、可行性等一系列更复杂的问题

究其本质原因,在于人们控制复杂性的能力相对稳定,但面临的问题却越来越多。在现实生活中,如果遇到一件很复杂的事情要完成,我们往往会寻求工具的支持,很多工具的作用是帮人们完成重复性的、每次手工做起来又很费力费时的工作,在软件开发时解决问题的思路是一致的。

基本的解决思路就是抽取软件的共性成分,抽取的共性成分由系统级软件完成,向开发人员屏蔽系统低层的复杂度,从而在高层保持整体复杂度的相对稳定。在软件领域,这种解决思路往往导致新型系统软件的产生。操作系统与数据库管理系统的产生就是经历了类似这样的过程。

在操作系统出现之前,计算机的初始组成就是“硬件+程序”,即程序直接运行在裸机硬件之上。此时,应用程序直接控制硬件的各种运行细节,应用程序中存在大量的代码用于管理各种物理器件——以访问数据为例,程序必须控制怎样连接磁盘,如何读取其中的数据,如何处理各种异常情况等。这使得程序代码十分庞大,而且正确性难以保证。随着计算机应用的日益广泛,程序的规模不断增大,软件开发变得越来越困难。在这种背景下,人们进行了软件共性的第一次抽取,即抽取出了程序的共性(稳定)成分——计算机资源管理,此次共性的抽取导致了操作系统的产生,分离出了应用程序。初期的操作系统被称为管理程序或监督程序,提供大量的与硬件相关的代码(系统调用)来完成上层应用程序的各种请求,隐藏了与硬件相关的程序执行过程的复杂性,从而简化了应用程序的开发。

操作系统形成之后,计算机的组成变成了“硬件+操作系统+应用程序”。此时,应用程序中访问的数据和应用程序一样以简单文件的方式存储,应用程序的开发人员需要了解数据的具体组织格式,并且需要自己编写程序解决完整性等相关问题。随着应用程序处理的数据规模越来越大,应用程序中数据管理这一共性也越来越明显,即应用程序中普遍存在大量代码实现数据管理功能。于是,人们进行了软件共性的第二次抽取,即抽取出了程序的共性(稳定)成分——数据管理,此次共性的抽取导致了数据库管理系统的产生,分离出了应用软件。数据库管理系统对数据进行统一的管理和控制,并保证数据库的安全性和完整性,为用户屏蔽系统关于数据存储和维护等的细节,从而再次简化了应用程序的开发。

类似的工作仍在继续,在软件系统规模与复杂度不断提高的同时,人们不断从应用软件中提取共性、降低高层复杂性,最终导致了中间件的产生。与操作系统、数据库管理系统类似,中间件是在操作系统(数据库管理系统)与应用系统之间的一层软件,通常为分布式应用的开发、部署、运行与管理提供支持

1.3.2 中间件提供的支撑

在中间件应用的早期,人们依据所抽取出的、应用软件中的不同共性,设计与实现了多种类型的中间件,一般一种类型的中间件实现一种共性功能,为应用软件提供一种开发支撑。由于所属的具体领域不同,面临的问题差异很大,因此不同开发组织分离、开发出的中间件也不尽相同。以下是几种常见的中间件以及其提供的支持:

有了各种中间件的支撑,在应用软件中用到中间件对应的功能时,不需要开发人员自己实现,可直接利用中间件,将其已实现好的功能快速集成到应用软件中。

随着中间件应用越来越广泛,又出现了一个新问题:中间件越来越多,开发时需要安装的支撑环境越来越复杂,开发人员不得不花费越来越多的时间,安装与配置需要的各种中间件。因此自然地出现一种中间件集成的强烈需求,在中间件研究的基础上,人们开始考虑将各种中间件的功能集成在一起,现有中间件多以集成中间件的形式出现,集成中间件也称为应用服务器

现有的集成中间件典型地为三层/多层结构的分布式软件系统提供各种开发支撑,因为三层结构的分布式软件的核心为中间层,因此支撑主要集中在对中间层开发的支撑上。目前应用最广泛的集成中间件有三类:

其中前两种所基于的规范均为工业标准,这两种标准得到了产业界众多厂商的广泛支持,因此可供选择的具体中间件产品较多,也是主要的内容关注点。第三种基于微软公司的私有技术,具体的中间件产品基本局限于微软公司的平台,可参考其它相关书籍或资料。为便于论述,在不引起混淆的情况下,用“中间件”一词代表“集成中间件”

现有中间件为分布式软件系统提供的基本支持,与分布式软件所运行的网络环境密切相关,具体可归为提供构件运行环境、提供互操作机制与提供公共服务三个方面。

1. 提供构件运行环境

现有中间件均提供构件化的基本支持,方便开发与使用符合特定规范的构件(分布式对象)。中间件一般通过构件容器,为构件提供基本的运行环境,具体功能一般包括管理构件的实例及其生命周期、管理构件的元信息等。

2. 提供互操作机制

因为分布式软件跨越了多台计算机,所以需要一种像 TCP/IP 或者 IPX 这样的网络基础设施,来连接应用程序的各节点。现有操作系统(如Unix、Linux、Windows)或高级程序设计语言(如Java、C++),均提供了像套接字 Socket 这样的开发接口,支持编写跨越网络交互的代码。

但是不难发现,基于这些开发接口来实现,需要进行比较复杂的开发与调试,而跨越网络的交互是每个分布式系统必须解决的首要问题。因此,现有的集成中间件,均集成了早期远程过程/方法调用中间件的功能,提供了很强的高层通信协议,以屏蔽节点的物理特性、以及各节点在处理器和操作系统等方面的异构性。基于中间件的互操作支持,开发人员在开发与调用分布式对象时,均不需自己编写处理底层通信的代码。

广泛使用的高层通信协议包括以下几种:

以上各种协议的共同特征就是,帮助应用程序完成编组与解组等跨越网络通信的底层工作,实现远程过程/方法调用中间件的功能

3. 提供公共服务

除了互操作的支持外,现有的集成中间件将「早期各种中间件中针对分布式软件的通用支持」集成于一身,以公共服务的形式提供给应用程序公共服务又称为系统级服务,指由中间件(应用服务器)实现的、应用程序使用的软件系统中共性程度高的功能成分。公共服务有两个基本特征:

与应用程序中开发人员开发的构件实现的功能不同,公共服务通常不实现应用系统中的具体业务逻辑,而是为具体业务逻辑的实现提供共性的支持,而开发人员开发的构件则实现具体的业务逻辑。

显然,一个中间件平台所提供的公共服务越多,开发者就越容易在更短的时间内开发出高质量的分布式系统。有了中间件提供的公共服务,开发者可以将主要精力集中于系统的具体业务逻辑。以下是几种常见的公共服务:


1.4 互操作的基本原理与实例

广义的互操作包括中间层应用构件与数据库、客户层构件与中间层应用构件、中间层应用构件与公共服务构件、中间层应用构件之间的互操作等多个层次,我们主要关注应用构件之间的互操作,即「软件系统开发人员编写的程序」之间的互操作。

1.4.1 互操作的基本原理

上节提到的RPC、IIOP、DCOM通信协议、JRMP、RMI/IIOP等高层通信协议,均可以帮助应用程序完成编组与解组等跨越网络通信的底层工作,实现传统的远程过程/方法调用中间件的功能。

这些高层通信协议尽管具体的实现细节不尽相同,但是在实现方式与开发模式上,均采用了RPC中相同的通信模型与类似的开发模式,它们采用的通信模型称为 Stub/Skeleton 结构,如图1-4所示。
在这里插入图片描述

Stub/Skeleton 结构中,由客户端桩 Stub 替客户端完成「与服务端程序交互的具体底层通信工作」,客户程序中的远程对象引用实际上是对本地桩的引用;而服务端框架 Skeleton 负责替服务端完成「与客户端交互的具体底层通信工作」。由于客户端桩与服务端框架分别位于客户端与服务端程序的进程内,因此开发人员开发客户端与服务端程序时,只需分别与「本进程内的桩与框架构件」交互,即可实现与远端的交互;而负责底层通信的客户端桩与服务端框架,会在开发过程中自动生成、而非由开发人员编写,从而为开发人员省去底层通信相关的开发工作。

Stub/Skeleton 结构的支撑下,客户程序与服务程序按照图中所示的 8 8 8 个步骤完成一服务的调用:

Stub/Skeleton 结构的支持,使得开发人员从繁杂的底层通信工作中解脱出来,开发人员可以不用编写底层通信代码,即可实现客户端与服务端的远程交互,从而将主要精力集中到业务逻辑的实现上。

1.4.2 互操作实例

下面通过一个具体实例来演示互操作的基本原理。该例子实现远程查询通话记录的功能,客户端远程调用分布式对象实现的查询通话记录功能,分布式对象则通过查询数据库完成具体的查询功能。

该例子程序采用纯Java语言编写,利用 JDBC/ODBC 访问关系数据库,并采用远程方法调用 RMI 实现客户程序与服务程序之间的交互。客户程序只能通过服务程序间接地访问关系数据库,因而该例子程序属于一种典型的三层设计模型,该例子程序的三层结构与每一层实现基本功能如图1-5所示。
图 1-5 话费查询实例的系统结构
本例关注的重点是客户端与中间层分布式对象之间的交互,交互基于 Java RMI 实现,Java RMI 采用 JRMP 协议以支持客户程序访问远端分布式对象。

1.4.2.1 数据库设计

数据库中存放本例中使用的持久数据,即电话用户的通话记录,数据库中的表结构如图1-6所示。其中,TelephoneDirectory 记录了所有用户的电话号码,以 number 为主键;表 CallHistory 记录了所有电话的通话历史,其中 number 是外键,建立该属性的允许重复的索引。表 CallHistory 通过外键 number 与表 TelephoneDirectory 相关联。

本例中利用 JDBC/ODBC 访问数据库,因而支持使用多种不同的数据库管理系统,只要这些数据库管理系统提供了ODBC接口,如Microsoft Access、Microsoft SQL Server、Sybase、Oracle等。
在这里插入图片描述
使用ODBC访问数据库之前,必须将数据库配置为ODBC的一个数据源。以采用Microsoft Access数据库为例,主要的步骤为:在Windows的控制面板中打开“数据源(ODBC)”,单击“添加”按钮后选择“Microsoft Access Driver (*.mdb)”驱动程序,然后为数据源命名、并选择相关联的Access数据库文件,如图1-7所示。
图 1-7 配置 ODBC 数据源
为进一步提高程序的数据独立性,还可以利用许多关系DBMS支持的“存储过程”来完成数据的查询与更新操作。数据独立性(逻辑和物理独立性),使得在数据库中表的属性或表之间的关联发生某些变化时,程序员无需对应用程序作任何修改。使用存储过程访问数据库还可带来其他好处,例如提高了SQL语句查询与更新数据库的效率,在网络环境下加强了数据库的安全性等等。在不同的数据库管理系统中,存储过程可能有不同的名字,如保留过程、触发器、查询等。

下面展示了本例中使用的存储过程(即Microsoft Access中的查询),它根据指定电话用户名字的参数(Telephone Subscriber),查询该用户登记的所有电话的通话记录。本例程序中利用该存储过程实现对数据的查询。

// 查询 QueryCallHistoryWithSubscriber
SELECT TelephoneDirectory.number, CallHistory.startTime, CallHistory.endTime 
FROM TelephoneDirectory, CallHistory 
WHERE (TelephoneDirectory.number = CallHistory.number) AND 
	  (TelephoneDirectory.subscriber = [Telephone Subscriber]) 
ORDER BY startTime; 

1.4.2.2 接口定义

除了数据库中的持久数据外,本例中需要编写的程序代码包括以下几个主要部分:

在开发服务端程序与客户程序之前,接口是需要首先考虑的问题,因为接口是客户程序与服务端分布式对象之间的约定。在 Java RMI分布式对象的接口用Java语言的 interface 定义,并且要求所有分布式对象的远程接口必须继承 java.rmi.Remote 接口,还要求其中的每一个方法必须声明抛出 java.rmi.RemoteException 异常,因为网络通信或服务程序等原因均可能导致远程调用失败。

程序1-1给出了例子程序中通话记录管理器的远程接口定义。定义的接口 CallManagerInterface 约定,服务端的分布式对象提供一个可供远程调用的操作 getCallHistory ,该操作需要一个字符串类型的参数(电话用户的姓名),返回一个 DatabaseTableModel 类型的对象(该用户的通话记录)。

// 程序 1-1 CallManagerInterface.java 
// 分布式对象通话记录管理器 CallManager 的远程接口
package Telephone; 
public interface CallManagerInterface extends java.rmi.Remote { 
	// 根据电话用户名字查询通话记录。
	// 参数: subscriber - 电话用户的名字
 	public Database.DatabaseTableModel getCallHistory(String subscriber) 
 throws java.rmi.RemoteException; 
} 

1.4.2.3 服务端程序

程序1-2定义的类 CallManager 就是服务端分布式对象的实现该类实现了远程接口 CallManagerInterface 中约定的 getCallHistory 方法。为防止多个客户程序并发地调用数据库查询操作,方法 getCallHistory() 被定义为同步方法

CallManager 类的实现中,由于 Java RMIStub/Skeleton 结构的支持,因此在实现 getCallHistory 方法时,并没有因为该方法是被远程调用的方法、而添加任何代码处理底层通信;在下面的实现代码中,除了方法接口处声明抛出 RemoteException 外,该方法的实现与一个普通Java类中方法的实现没有区别

// 程序 1-2 CallManager.java 
// 通话记录管理器(即远程接口 CallManagerInterface 的实现)
package Telephone; 
public class CallManager extends java.rmi.server.UnicastRemoteObject implements CallManagerInterface { 
	// 属性定义
	protected Database.DatabaseAccess database; 
	// 缺省构造方法,必须抛出 RemoteException 异常
	public CallManager() throws java.rmi.RemoteException { 
		database = new Database.DatabaseAccess(); 
	} 
	// 根据电话用户名字 subscriber 查询通话记录,实现远程接口指定的方法
	public synchronized Database.DatabaseTableModel getCallHistory(String subscriber) throws java.rmi.RemoteException { 
		String sql = ""; // SQL 查询语句
		Database.DatabaseTableModel table = null; // 返回的二维表模型
		System.out.println("Respond to client request: " + subscriber); 
		try { 
			sql = "QueryCallHistoryWithSubscriber('" + subscriber + "')"; 
			java.sql.ResultSet rs = database.callQuery(sql); 
			table = new Database.DatabaseTableModel(rs); 
			rs.close(); 
		} catch(java.sql.SQLException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} 
		return table; 
	} 
} 

程序1-3所示 ServerApplication 是服务程序的主程序,它的核心功能是:完成「真正提供服务的分布式对象」的创建与注册

// 程序 1-3 ServerApplication.java 
// 服务程序的主程序
public class ServerApplication { 
	final static String JDBC_DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver"; 
	public static void main(String args[]) { 
		// 为 RMI 设置安全管理器
		System.setSecurityManager(new java.rmi.RMISecurityManager()); 
		// 加载 JDBC 驱动程序
		try { 
			Class.forName(JDBC_DRIVER); 
		} catch(ClassNotFoundException exc) { 
			System.out.println(exc.getMessage());  
			System.exit(1); 
		} 
		// 创建并注册伺服对象
		try { 
			// 创建伺服对象
			Telephone.CallManager callManager = new Telephone.CallManager(); 
			// 用名字"CallManagerServant001"注册伺服对象
			java.rmi.Naming.rebind("CallManagerServant001", callManager); 
		} catch(java.rmi.RemoteException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} catch(java.net.MalformedURLException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} 
		// 提示服务程序就绪
		System.out.println("Call manager in the server is ready ..."); 
	} 
} 

程序1-4定义的 DatabaseAccess 类与程序 1-5 定义的 DatabaseTableModel 类均用于抽象JDBC访问数据库的行为,在实际应用中,通常会设计更完善、更个性化的数据库访问程序包,用来包装JDBC的API。由于本例主要关注客户程序与分布式对象之间的交互,如对Java程序中访问数据库不熟悉或不感兴趣,可略过这两个类的具体实现代码。

DatabaseAccess 主要用于管理服务程序与数据库的连接,并完成数据库的查询与更新操
作。DatabaseTableModel 负责将数据库查询结果转换为二维表数据模型的形式,方便利用 swing 的二维表控件显示查询结果。在基于关系数据库的应用中,二维表控件是最常用的数据表达方式;对于某些具有层次结构的数据,则以树控件表达会更加自然

// 程序 1-4 DatabaseAccess.java 
// 实现 JDBC 与数据库的连接
package Database; 
import java.sql.*; 
public class DatabaseAccess { 
	// 常量定义
	protected final String DATABASE_NAME = "jdbc:odbc:Telephone"; 
	// 属性定义
	protected Connection connection; // 为数据库建立的连接
	protected Statement statement; // 将执行的 SQL 语句
	protected CallableStatement callable; // 将调用的 SQL 存储过程语句
	// 行为定义
	// 构造方法,建立与数据库的连接
	public DatabaseAccess() { 
		try { 
			// 建立与指定数据库的连接
			connection = DriverManager.getConnection(DATABASE_NAME); 
			// 如果连接成功则检测是否有警告信息
			SQLWarning warn = connection.getWarnings(); 
			while (warn != null) { 
				System.out.println(warn.getMessage()); 
				warn = warn.getNextWarning(); 
			} 
			// 创建一个用于执行 SQL 的语句
			statement = connection.createStatement(); 
			callable = null; 
		} catch(SQLException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} 
	} 
	// 析构方法,撤销与数据库的连接。
	public synchronized void finalize() { 
		try { 
			connection.close(); 
		} catch(SQLException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 	
		} 
	} 
	// 利用存储过程执行数据库查询操作。
	// 参数: procedure - 存储过程名字
	// 返回: 查询结果集
	public synchronized ResultSet callQuery(String procedure) throws SQLException { 
		callable = connection.prepareCall("{call " + procedure + "}"); 
		ResultSet rs = callable.executeQuery(); 
		return rs; 
	} 
} 
// 程序 1-5 DatabaseTableModel.java 
// 根据数据库查询结果构造供 JTable 控件使用的二维表数据模型
package Database; 
import javax.swing.*; 
import java.sql.*; 
import java.util.Vector; 
public class DatabaseTableModel extends javax.swing.table.AbstractTableModel { 
	// 属性定义
	protected String[] titles; // 列标题
	protected int[] types; // 各列的数据类型
	protected Vector data; // 二维表的数据
	// 构造方法,根据 SQL 查询结果集 rs 构造二维表
	public DatabaseTableModel(ResultSet rs) { 
		try { 
			// 取得结果集的元数据
			ResultSetMetaData meta = rs.getMetaData(); 
			int columnCount = meta.getColumnCount(); 
			// 取所有列标题与类型名字(注意 JDBC 元数据的下标从 1 开始计数)
			titles = new String[columnCount]; 
			types = new int[columnCount]; 
			for (int index = 1; index <= columnCount; index++) {  
				titles[index - 1] = meta.getColumnName(index); 
				types[index - 1] = meta.getColumnType(index); 
			} 
			// 逐行取结果集中的数据
			data = new Vector(1000, 100); 
			while (rs.next()) { 
				Vector row = new Vector(30); 
				for (int index = 1; index <= columnCount; index++) 
					row.addElement(rs.getObject(index)); 
				row.trimToSize(); 
				data.addElement(row); 
			} 
			data.trimToSize(); 
		} catch(SQLException exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} 
	} 
	// 实现 AbstractTableModel 遗留的抽象方法。
	public int getRowCount() { 
		return data.size(); 
	} 
	public int getColumnCount() { 
		return titles.length; 
	} 
	public String getColumnName(int col) { 
		return titles[col]; 
	} 
	public Object getValueAt(int row, int col) { 
		return ((Vector) data.elementAt(row)).elementAt(col); 
	} 
	public void printData() { 
		int i, j; 
		String ss = ""; 
		for(j = 0; j < getColumnCount(); j++) 
			ss = ss + getColumnName(j) + "\t"; 
		System.out.println(ss); 
		for(i = 0; i < getRowCount(); i++) { 
			ss = "";  
			for(j = 0; j<getColumnCount(); j++) 
				ss = ss + getValueAt(i, j).toString() + "\t"; 
			System.out.println(ss); 
		} 
	} 
} 

1.4.2.4 客户端程序

本例中,客户程序完成的功能为调用中间层的分布式对象,并将结果显示在客户端界面上

Java RMI 中,客户程序通过 Java RMI 的命名服务,查找希望使用的分布式对象。程序1-6给出的查询程序 Client.java 中,用于解析远程对象的对象标识为 "CallManagerServant001" ,它必须与「服务程序注册远程对象时采用的名字」完全相同,同时,构造远程对象的 URL 时必须包含主机名

与实现服务端分布式对象时类似,由于 Java RMIStub/Skeleton 结构的支持,因此在实现客户端程序时同样不需编写(底层通信代码)。注意,程序中远程对象 callManager 必须声明为远程对象接口 CallManagerInterface 的实例,而不是远程对象实现类 CallManager 的实例,因为客户程序只能看到远端分布式对象(构件)的接口

// 程序 1-6 Client.java 
// 客户端程序
package Telephone; 
public class Client { 
	// 实现 main 方法,根据参数调用分布式对象的 getCallHistory 完成通话记录的查询
	public static void main(String[] args) { 
		String name = args.length > 0 ? args[0] : "songshengli"; 
		Database.DatabaseTableModel result = null; 
		try { 
			// 从 Applet 取主机名
			String host = "//localhost/"; 
			// 远程对象的标识必须与服务程序注册时使用的对象标识完全相同
			String objectId = "CallManagerServant001"; 
			// 根据主机名与对象标识解析远程对象
			CallManagerInterface callManager = (CallManagerInterface) java.rmi.Naming.lookup(host + objectId); 
			// 调用远程对象方法查询用户的通话记录
			result = callManager.getCallHistory(subscriber); 
			System.out.println(result.toString()); 
		} catch(Exception exc) { 
			System.out.println(exc.getMessage()); 
			System.exit(1); 
		} 
	} 
} 

1.4.2.5 编译并运行应用程序

Java语言要求程序包与子目录相对应(类名与源代码文件名相对应),因而在编写Java 程序之前,就必须决定程序包与子目录的名字。表1-1总结了本小节例子程序的完整目录组织与文件清单。
表 1-1 目录组织与文件清单
表1-1中的Java字节码,由JDK提供的 javac 编译器生成,而远程对象在客户端的桩与服务端的框架,必须另由 rmic 编译器生成,rmic 的输入是 Java 字节码文件、而不是 Java 源程序文件。具体的编译过程如下:

# 编译 DatabaseAccess 类与 DatabaseTableModel 类
D:\TelephoneExample> javac Database\*.java
# 编译 CallManagerInterface 接口与 CallManager 类
D:\TelephoneExample> javac Telephone\*.java
# 生成客户端桩 CallManager_Stub 与服务端框架 CallManager_Skel 
D:\TelephoneExample> rmic Telephone.CallManager
# 编译 ServerApplication 类与 Client 类
D:\TelephoneExample> javac *.java

如果开发者不是直接使用JDK,而是使用某种集成化开发环境(如 NetBean、Borland JBuilder、Eclipse等),则利用项目 Project 机制,可减轻许多编译工作的负担。开发环境会根据项目内容,自动调用相应的编译器生成字节码文件、以及客户程序桩与服务程序框架文件

完成例子程序的编译与布置后,就可以开始运行例子程序了。首先启动 RMI 远程对象注册表,然后启动服务程序。例如在Windows中可在 MS-DOS 中键入以下命令:

# 启动 RMI 远程对象注册表
D:\TelephoneExample> start rmiregistry
# 启动服务程序
D:\TelephoneExample> start java ServerApplication
# 运行客户端程序
D:\TelephoneExample> java Client

客户端与服务端程序的输出界面如图1-9所示:
在这里插入图片描述

1.4.2.6 分布运行

此例子程序可以很容易地将客户端与服务端,分布到两个不同的物理机器上运行,由于Java良好的可移植性,除了Windows 平台,客户端程序还可运行在安装JDK的LINUX、UNIX等其它操作系统之上,由于服务端使用 JDBC/ODBC 访问数据库,因此可让其仍运行在Windows操作系统之上。

跨机器运行时,我们需要修改客户端程序中查找分布式对象的代码,使其查找运行在服务端机器上的分布式对象,而不是本机上的分布式对象。具体的修改就是,在调用命名服务的 lookup 方法中,将对应的主机名改为「服务程序运行的机器的主机名或 IP 地址」,如程序1-7所示中的黑体部分所示:

// 程序 1-7 Client.java 
try { 
	// 从 Applet 取主机名
	String host = "//serverhost/"; // 改为服务程序运行机器的主机名或 IP 地址
	// 远程对象的标识必须与服务程序注册时使用的对象标识完全相同
	String objectId = "CallManagerServant001"; 
	// 根据主机名与对象标识解析远程对象
	CallManagerInterface callManager = (CallManagerInterface) 
	java.rmi.Naming.lookup(host + objectId); 
	// 调用远程对象方法查询用户的通话记录
	result = callManager.getCallHistory(subscriber); 
	System.out.println(result.toString()); 
} catch(Exception exc) { 
	System.out.println(exc.getMessage()); 
	System.exit(1); 
} 

1.4.2.7 小结

在本例中,我们实现了一个简单的三层结构的分布式应用

在上述三层结构中,重点是中间层分布式对象与客户端程序通信的实现,即基于 Java RMIStub/Skeleton 结构实现的远程交互。在上述开发过程中,rmic 命令的执行,生成了负责客户端底层通信的 CallManager_Stub 与负责分布式对象底层通信的 CallManager_Skel 1,因此,尽管没有编写任何代码、处理跨越机器的通信,我们的程序也能很容易分布到两个物理机器上运行。

在实际的复杂分布式系统中,特别是当要解决的问题变得更庞大、更复杂的时候,除了分布式对象与其使用者之间的基本通信问题外,我们往往需要寻求中间件提供的更多支持,典型的主要包括如下两个方面:

后续内容将以 CORBAJava 企业版中间件为核心,重点讨论:

在这里插入图片描述


  1. 在新版本的JDK中,可能会发现 rmic 只会生成一个负责客户端底层通信的 Stub 类,而不会生成负责服务端底层通信的 Skeleton 类,这主要是因为随着研究的进展,技术上已经实现通用的 Skeleton ,即一份代码即可为所有的分布式对象实现底层通信的功能,而由于每个分布式对象对客户端提供的接口不尽相同,为保持客户端编程的简单性,每个分布式对象仍需一个 Stub 类。 ↩︎

标签:对象,数据库,中间件,构件,分布式,基本概念,客户端
来源: https://blog.csdn.net/myRealization/article/details/122645156