WebService与SOA的实现

第14章 WebService与SOA的实现


14.1 Web Services和面向服务的软件架构(Service Oriented Architecture,简称SOA)概述:

 

在最新Java开发世界里,我们经常会遇到这样一个名词:Web Services(Web服务)。同时还会发现,与这个名词同时出现的多是各大主流技术供应商,各大技术供应商无一不在关注这一领域的发展。从Microsoft的.NET架构,到SUN的SUN ONE,以及IBM的Web Services,都体现了这些重量级的技术提供者对Web Services的推崇与重视。

 

电子商务的发展促进了Web Services的发展。Web服务可以使公司降低进行电子商务的成本,更快地部署解决方案以及开拓更多的新机遇。Web服务使应用程序的集成比以前更快、更容易而且更便宜。它更注重服务语义而不那么注重网络协议语义的消息,从而实现了业务功能的松散耦合。这些特性对于在企业之间和企业内部通过web连接业务功能是非常理想的。它提供了一致化(Uniform)的编程模型,从而在企业内外都可以利用通用的基础设施并以一种通用的方法进行应用程序集成。

 

要理解Web Services, 首先需要认识面向服务的软件架构(Service Oriented Architecture,简称SOA),Web Services是SOA架构系统的一个实例。

14.1.1面向服务的软件架构(SOA)

 

1. 面向服务中的基本概念

在面向服务的架构中包含一些基本的概念,透过这些基本概念可以进一步了解面向服务的架构。

(1) 服务的概念

在SOA中的服务是指能够处理一些任务过程的动作的抽象概念,这些服务可以被描述,可以被发现,可以由服务代理负责向请求者提供服务并给出结果。代理是指请求或者提供服务的人所使用的软件工具,人通过代理进行交互操作。

(2) 消息的概念

服务代理之间需要通过消息的传递进行交互操作,消息的内容含有一定的语义和数据,消息传输需要与某个通信协议相绑定才能够实现。

(3) 服务的描述和发现

众多的服务组成一个开放系统,除了需要提供信息交互方式以外,还需要提供相互了解的机制,这就需要提供描述和发现的方式。代理可以通过服务的描述来了解一个服务的内容,包括使用这个服务的技术信息、访问这个服务的地址信息等内容。当新的服务被投入到系统之中后,它需要被注册,并且要能够被发现,使它可以被利用起来。

 

2.为什么需要面向服务的软件

由于软件需求的扩大,软件系统变得越来越复杂。面对复杂的系统资源,我们需要一种更加合理的方式将不同类型、不同位置的子系统有力地结合起来,这种整合并不是将它们之间绑定得更加紧密,而是利用更加松散的方式来建立这个系统。

SOA通过松散的方式将分布式的软件模块结合起来,与旧有系统集成相比有着明显的优势。对于服务的使用者来说,可以简单地通过服务的描述来获取服务,系统各部分之间不必为了某一部分的升级而改变,在服务的过程中不同的软件模块可以充当不同的角色,从而构成整个系统的工作体系。

在SOA当中,一个服务代表的是一个由服务提供者向服务请求者发布的一些处理过程,这个过程在被调用之后,获得服务请求者所需要的一个结果。在这个过程中,服务请求者可以向任何能够提供此项服务的服务提供者来请求服务,服务实现的过程对于服务请求者来说是透明的。

随着系统分布式和多种结构复合程度的提高,SOA的巨大优势将进一步被挖掘。

(1) 建立松散耦合的系统

松散耦合系统的优点已经被业内充分地认可,SOA作为一种分布式的系统,它实现了一种服务和描述等概念相结合的架构。

SOA中的服务在SOA架构中被标准的描述语言所描述,并通过与某种传输协议的绑定来实现相互之间的交互。这种基于服务的架构使整个系统成为一个松散耦合的结构,利用与通信协议的绑定将分布式系统中的所有部分连接起来,利用语义和服务的描述,在代理之间进行交互。

(2) 提高软件的重用性

面向服务的架构还提高了对软件的重用性。与组件方式相比,SOA系统中的单个服务的改变不会对其他部分造成严重的影响,同时利用服务的描述使同一服务可以充分地被其他系统所调用,各个系统之间形成了高度的重用性。

现在正在被广泛使用的一些服务,正在不断地被各个系统所重用,重用的条件十分简单,只要了解服务的描述,或者可以访问到服务的描述地址即可。与组件重用相比,服务的重用还具有与实现语言无关的特点,重用服务的客户端程序不需要使用与服务实现部分同样的开发语言,一切交互的过程都是利用与实现无关的方式进行的。

(3) 提供按需服务的代码

面向服务的架构也使得系统的实现虚拟化,在SOA架构中的请求和提供之间交互或相互代理的过程中,可计算的代码资源分布在松散结构中的各个部分上,当请求发生时才被调用和服务,所有计算过程都是按照请求者的需求进行的。服务的对象分为有状态和无状态两种方式提供服务,按照需求提供服务,也可以利用缓冲机制优化系统的系统。

总之,SOA使代码的开发变得更有服务的目的性,使开发更加有效和合理。

 

14.1.2 SOA与 Web 2.0

另外,我们补充一下SOA与目前同样热门的Web 2.0的关系。

实际上Web 2.0 和SOA的概念在很大程度上是相同的,只是被粉饰成为软件的不同部分(如果的确存在不同的话),也就是说SOA和Web 2.0有很多重叠的东西,例如都是基于调用(invoke)的服务,都能存在于网络的任何位置等等。

SOA和Web 2.0的共性远大于它们之间的区别,而且Web 2.0在推广SOA方面起到了一定作用。到现在为止,SOA和Web 2.0拥有不同的支持者- SOA更多涉及企业结构和商务开拓,而Web 2.0更关注用户。这种差别随着更多企业接纳Web 2.0而在变化,但是这两项技术有着不同的重心: Web 2.0告诉我们数据是软件应用中最重要的部分,而SOA告诉我们服务才是中心。SOA中传输数据的服务也非常重要,但是传统SOA更关注IT系统的接合处而不是那些能使接合处更具价值的东西。也就是说,SOA也许是通畅的管道,但并不是系统中通过的水的价值。许多行业领导者说企业同时需要SOA类方法的结构和Web 2.0方法的创业能力。

SOA和Web 2.0之间有许多共有的要素:

l 软件重组

l 管理

l 软件就是服务

l 应用就是平台

l 无意识的使用

l 开放

l AJAX

l 互操作性

l 货币化

l 安全

l 网络导向架构

特别要说的是,最后一条网络导向架构或者Web Oriented Architecture(WOA)是关键的内容,最终有可能会将SOA和Web 2.0合为一体。

 

了解了SOA后, 我们来介绍什么是Web Services。Web Services是SOA架构系统的一个实例。从技术角度来讲,Web Services是一种新的技术架构、新的软件应用环境。它的系统架构和实现技术完全继承已有的技术,可以认为Web Services是Internet的一种延伸,是现有的Internet面向更好的互操作能力的一个延伸。

14.2. Web Services的概念

Web Services,从字面上理解就是通过Web提供的服务。我们可以理解Web Services是自包含的、模块化的应用程序,它可以在网络(通常为Web)中被描述、发布、查找以及调用;也可以理解Web Services是基于网络的、分布式的模块化组件,它执行特定的任务,遵守具体的技术规范,这些规范使得Web Sevices能与其他兼容的组件进行互操作;也可以这样理解,所谓Web服务,它是指由企业发布的完成其特别商务需求的在线应用服务,其他公司或应用软件能够通过Internet来访问并使用这项应用服务

对于Web Services,很多人会与Web Service混为一谈,认为二者指的是同一个事物。其实不然,前者指的是用于建构Web Service的技术框架,后者指的是使用Web Services技术而创建的应用实例。Web Services是描述了一些操作的接口,基于标准化的XML消息传输机制,我们可以通过网络访问这些操作。Web Services使用规范的、基于XML的WSDL(Web Services Description Language)语言描述的,这称为Web Services的服务描述。这一描述囊括了与服务交互所需要的全部细节,包括消息格式(详细描述操作的输入输出消息格式)、传输协议和位置。该接口隐藏了服务实现的细节,允许通过独立与服务实现、独立于软硬件平台、独立于编写服务所用的编程语言的方式使用该服务。这使得基于Web Services的应用程序具有松散耦合、面向组件和跨技术实现的特点。Web Services都履行一定的特定业务或任务,可以实现同其他Web Services一起用于实现复杂的商业交易。

从外部使用者角度而言,Web Services是一种部署在Web上的对象和组件,具备以下特征:

.完好的封装性:

Web服务既然是一种部署在web上的对象,自然具备对象的良好封装性,对于使用者而言,他能且仅能看到该对象提供的功能列表。

.松散耦合

这一特征也是源于对象/组件技术,当一个Web服务的实现发生变更的时候,调用者是不会感到这一点的,对于调用者来说,只要Web服务的调用界面不变,Web服务实现的任何变更对他们来说都是透明的,甚至是当Web服务的实现平台从J2EE迁移到了.NET或者是相反的迁移流程,用户都可以对此一无所知。对于松散耦合而言,尤其是在Internet环境下的Web服务而言,需要有一种适合Internet环境的消息交换协议,而XML/SOAP正是目前最为适合的消息交换协议。

.使用协议的规范性

这一特征从对象而来,但相比一般对象,它更加规范化和易于理解。首先,作为Web服务,对象界面所提供的功能应当使用标准的描述语言来描述(比如WSDL);其次,由标准描述语言描述的服务界面应当是能够被发现的,因此这一描述文档需要被存储在私有的或公共的注册库里面。同时,使用标准描述语言描述的使用协约将不仅仅是服务界面,它将被延伸到Web服务的聚合、跨Web服务的事务、工作流等,而这些又都需要服务质量(QoS)的保障。其次,我们知道安全机制对于松散耦合的对象环境的重要性,因此我们需要对诸如授权认证、数据完整性(比如签名机制)、消息源认证以及事务的不可否认性等运用规范的方法来描述、传输和交换。最后,在所有层次的处理都应当是可管理的,因此需要对管理协约运用同样的机制。

.高度可集成能力

由于Web服务采取简单的、易理解的标准,Web协议作为组件界面描述和协同描述规范,完全屏蔽了不同软件平台的差异,无论是CORBA、DCOM还是EJB,都可以通过这一种标准的协议进行互操作,实现了在当前环境下最高的可集成性。

 

14.2.1 Web Services的核心技术

 

Web Services 是一种基于组件的软件平台,是面向服务的Internet 应用。Web Services 是应用于Internet 的,而不是限于局域网或试验环境,这就要求Web Services 框架必须适用于现有的Internet 软件和硬件环境,即服务的提供者所提供的服务必须具有跨平台、跨语言的特性。其次,Web Services 所提供的服务不但是面向人,而且需服务于其它应用系统。现有的Web网站也可以认为是面向服务的,但这种服务仅仅可以提供给人使用(只有人类才可以读懂浏览器下载的页面) 。而新一代的Web Services 所提供的服务应能被机器所读懂,例如其它应用程序及移动设备中的软件系统。这样,我们可以看出,Web Services 的发展方向实际上是构造一个基于现有Internet 技术之上的分布计算系统。

Web Services 框架的核心技术包括SOAP(Simple Object Access Protocol,简单对象访问协议) ,WSDL(Web Services Description Lanuage,Web服务描述语言) 和UDDI(Universal Description,Discovery and Integration,通用描述,发现,集成) ,它们都是以标准的XML 文档的形式表述的。

XML是Web Services技术体系中最基础的标准,Web Services的一切都建立在XML技术的基础之上,包括Web Services的消息、描述和服务实现的各个环节。利用XML,Web Services的服务提供者和请求者可以利用不同的开发语言来协作完成服务调用的过程。XML是Web Services技术体系中的很多标准得以建立的基础,在Web Services系统中无处不在。

 

SOAP 是Web services 的通信协议。SOAP是一种简单的、轻量级的基于XML 的机制,用于在网络应用程序之间进行结构化数据交换。SOAP包括三部分:一个定义描述消息内容的框架的信封,一组表示应用程序定义的数据类型实例的编码规则,以及表示远程过程调用和响应的约定。

WSDL表示WEB服务说明语言。WSDL文件是一个XML 文档,用于说明一组SOAP消息以及如何交换这些消息,通过WSDL可以描述一个服务的信息。这些信息使不了解这个服务的开发者可以建立调用这个服务的客户端代码,或者通过WSDL帮助生成实现它的基本代码结构。WSDL在Web Services的实际开发过程中起着重要的作用。

 

  Web Services是基于互联网的应用程序模块,用于在互联网上运行,它采用开放的UDDI(Universal Description,Discovery and Integration,通用描述,发现,集成)标准。UDDI标准先由IBM、微软、Ariba制订,到目前为止获得了130多家公司的支持。UDDI 提供一种发布和查找服务描述的方法。UDDI 数据实体提供对定义业务和服务信息的支持。WSDL 中定义的服务描述信息是UDDI注册中心信息的补充。UDDI提供了一个开放,平台独立的技术框架,来使企业之间能在互联网上找到对方的服务,定义它们在互联网上的交互活动,以及这些信息的共享方式。

 

Web 服务体系结构基于三种角色(服务提供者、服务注册中心和服务请求者)之间的交互。

服务提供者。从企业的角度看,这是服务的所有者。从体系结构的角度看,这是托管访问服务的平台。
服务请求者(用户)。从企业的角度看,这是要求满足特定功能的企业。从体系结构的角度看,这是寻找并调用服务,或启动与服务的交互的应用程序。服务请求者角色可以由浏览器来担当,由人或无用户界面的程序(例如,另外一个 Web 服务)来控制它。
服务注册中心。这是可搜索的服务描述注册中心,服务提供者在此发布他们的服务描述。在静态绑定开发或动态绑定执行期间,服务请求者查找服务并获得服务的绑定信息(在服务描述中)。对于静态绑定的服务请求者,服务注册中心是体系结构中的可选角色,因为服务提供者可以把描述直接发送给服务请求者。同样,服务请求者可以从服务注册中心以外的其它来源得到服务描述,例如本地文件、FTP 站点、Web 站点、广告和服务发现(Advertisement and Discovery of Services,ADS)或发现 Web 服务(Discovery of Web Services,DISCO)。

  Web Services 的体系架构如图1 所示

Web Services 服务提供方通过WSDL(Web Services Description Language) 描述所提供的服务,并将这一描述告知Web Services 注册服务器。注册服务器依据WSDL 的描述,依照UDDI (Universal Description Discovery and Integration) 的协定更新服务目录并在Internet 上发布。用户在使用Web Services 前先向注册服务器发出请求,获得Web Services 提供者的地址和服务接口信息,之后使用SOAP 协议(Simple Object Access Protocol) 与Web Services 提供者建立连接,进行通信。Web Services 的技术主要建立在XML 的规范之上,这保证了这一体系结构的平台无关性、语言无关性和人机交互性能。

 

14.2.2 Web 服务开发生命周期

Web 服务开发生命周期包括了设计和部署以及在运行时对服务注册中心、服务提供者和服务请求者每一个角色的要求。每个角色对开发生命周期的每一元素都有特定要求。

Web 服务开发生命周期有以下四个阶段:

1. 构建
生命周期的构建阶段包括开发和测试 Web 服务实现、定义服务接口描述和定义服务实现描述。我们可以通过创建新的 Web 服务、把现有的应用程序变成 Web 服务和由其它 Web 服务和应用程序组成新的 Web 服务来提供 Web 服务的实现。

2. 部署
部署阶段包括向服务请求者或服务注册中心发布服务接口和服务实现的定义,以及把 Web 服务的可执行文件部署到执行环境(典型情况下,Web 应用程序服务器)中。

3. 运行
在运行阶段,可以调用 Web 服务。在此,Web 服务完成部署,成为可操作的服务。服务请求者可以进行查找和绑定操作。

4. 管理
管理阶段包括持续的管理和经营 Web 服务应用程序。安全性、可用性、性能、服务质量和业务流程问题都必须被解决。

 

接下来我们具体展开Web Services原理。

 

14.3.Web Services原理

 

首先,我们将看看 Web 服务的一个概念性协议栈以及这个协议栈的细节。然后我们将讨论选择网络协议的标准。我们还将回顾一下基本的基于 XML 的消息传递分布式计算。我们将用服务描述扩展基本的 XML 消息传递,而服务描述是根据它的协议栈来解释的。接下来,我们将讨论服务描述在 Web 服务体系结构中的角色,说明支持静态和动态 Web 服务应用程序的服务发布技术的范围。我们还将围绕服务发布讨论服务发现的角色。最后,我们将描述基本 Web 服务体系结构的扩展,电子商务需要这些扩展才能使用 Web 服务。

 

14.3.1 Web 服务协议栈

要以一种可交互操作的方式执行发布、发现和绑定这三个操作,必须有一个包含每一层标准的 Web 服务协议栈。图 2 展示了一个概念性 Web 服务协议栈。上面的几层建立在下面几层提供的功能之上。垂直的条表示在协议栈中每一层必须满足的需求。

图2. Web 服务概念性协议栈

Web 服务协议栈的基础是网络层。Web 服务要被服务请求者调用,就必须是可以通过网络访问的。互联网上可以公用的 Web 服务使用普遍适用的网络协议。HTTP 凭借其普遍性,成为了互联网可用的 Web 服务真正的标准网络协议。Web 服务还可以支持其它互联网协议,包括 SMTP 和 FTP。内部网域可以使用可靠消息传递和调用基础结构,如 MQSeries 和 CORBA 等等。

 

下一层是基于 XML 的消息传递,它表示使用 XML 作为消息传递协议的基础。选择 SOAP 作为 XML 消息传递协议有很多原因:

它是使用 XML 传送以文档为中心的消息以及远程过程调用的标准化封装机制。
SOAP 很简单;它基本上是一个用 XML 信封作为有效负载的 HTTP POST。
SOAP 比对 XML 简单的 HTTP POST 更受青睐,因为它定义了一个标准机制,这个机制将正交扩展(orthogonal extension)合并为使用 SOAP 报头和对操作或函数进行标准编码的消息。
SOAP 消息支持 Web 服务体系结构中的发布、查找和绑定操作。
服务描述层实际上是描述文档的一个协议栈。首先,WSDL 是基于 XML 的服务描述的真正标准。这是支持可交互操作的 Web 服务所需的最小标准服务描述。WSDL 定义了服务交互的接口和结构。要指定业务环境、服务质量和服务之间的关系,我们还需要另外的描述。WSDL 文档可以由其它服务描述文档来补充,从而描述 Web 服务的这些更高级的方面。例如,描述业务环境除了使用 WSDL 文档,还要使用 UDDI 数据结构。Web 服务流程语言(Web Services Flow Language,WSFL)文档中则描述了服务组成和流程。

因为 Web 服务被定义为可以通过 SOAP 从网络进行访问,并由服务描述表示,所以该协议栈中的前三层需要提供或使用 Web 服务。最简单的协议栈将包括网络层的 HTTP、XML 消息传递层的 SOAP 协议以及服务描述层的 WSDL。所有企业间或公用 Web 服务都应该支持这种可交互操作的基础协议栈。Web 服务,特别是企业内部或专用 Web 服务,能够支持其它的网络协议和分布式计算技术。该协议栈提供了互操作性,它使 Web 服务能够利用现有的互联网基础结构。这将使进入普遍存在的环境的成本非常低。另外,灵活性并不会因为互操作性需求而有所降低,因为我们可以为选择性和增值的技术提供另外的支持。例如,我们必须支持 HTTP 上的 SOAP,但也可以同时支持 MQ 上的 SOAP。

协议栈的最下面三层确立了保证一致性和互操作性的技术,而它们上面的两层,即服务发布和服务发现,可以用多种解决方案来实现。

任何能够让服务请求者使用 WSDL 文档的操作,不管它处于服务请求者生命周期的哪个阶段,都符合服务发布的标准。该层中最简单、最静态的实例就是服务提供者直接向服务请求者发送 WSDL 文档。这被称为直接发布。电子邮件是直接发布的载体之一。直接发布对静态绑定的应用程序来说很有用。另外,服务提供者还可以将描述服务的文档发布到主机本地 WSDL 注册中心、专用 UDDI 注册中心或 UDDI 运营商节点。

Web 服务如果没有被发布就不能被发现,所以说服务发现依赖于服务发布。该层的各种发现机制和一组发布机制互相平行。任何允许服务请求者获得对服务描述的访问权,并在运行时使应用程序能够使用该服务描述的机制都必须符合服务发现的标准。最简单、最静态的发现的实例是静态发现,其中服务请求者从本地文件获取 WSDL 文档。这通常都是通过直接发布获取的 WSDL 文档,或者前面查找操作的结果。另外,也可以通过使用本地 WSDL 注册中心、专用 UDDI 注册中心或 UDDI 运营商节点在设计时或运行时发现服务。因为 Web 服务实现是一种软件模块,所以通过组建 Web 服务来产生 Web 服务是很自然的。Web 服务的组合可以扮演很多角色之一。企业内部的 Web 服务可能会相互合作,从而对外显示出一个单独的 Web 服务接口,或者,来自不同企业的 Web 服务可以相互合作,从而执行机器到机器、企业到企业的事务。另外,工作流程管理者还可以在参与业务流程的时侯调用每个 Web 服务。最上面一层,即服务流程,描述了如何执行服务到服务的通讯、合作以及流程。WSFL 用于描述这些交互。要使 Web 服务应用程序满足当今电子商务的迫切需求,就必须提供企业级基础结构,包括安全性、管理和服务质量。这几个垂直条在协议栈的每一层都必须得到解决。每一层的解决方案可以彼此独立。随着 Web 服务范例的采用和发展,将会出现更多此类垂直条。

该协议栈的最下面几层表示基础 Web 服务协议栈,它们相对于协议栈中上面几层来说更成熟,也更标准。Web 服务的成熟和采用将会带动协议栈中上面几层和垂直条的开发和标准化。

网络层

Web 服务协议栈的最底层是网络层。该层可表示任意多个网络协议:HTTP、FTP、SMTP、消息排队(Message Queuing)、互联网 ORB 间协议(Internet Inter ORB Protocol,IIOP)上的远程方法调用(Remote Method Invocation,RMI)、电子邮件等等。在任何给定的情况下使用的网络协议都依赖于应用程序需求。

对于可以从互联网访问的 Web 服务,人们选择网络技术的时侯通常会倾向于选择普遍部署的协议,如 HTTP。对于内部网中提供和使用的 Web 服务,使用另外的网络技术也会被认同。我们可以根据其它需求选择网络技术,包括安全性、可用性、性能以及可靠性。这使得 Web 服务可以利用已有的功能更高级的联网基础结构和面向消息的中间件,如 MQSeries。在有多种网络基础结构的企业中,HTTP 可以用来在这些基础结构之间搭建桥梁。

Web 服务的好处之一在于,它为专用内部网和公用互联网服务的开发和使用提供了统一的编程模型。所以,网络技术的选择对服务开发者来说是透明的。

基于 XML 消息传递的分布式计算

Web 服务体系结构最基础的支柱是 XML 消息传递。当前 XML 消息传递的行业标准是 SOAP。IBM、Microsoft 以及其它企业都向 W3C 建议 SOAP 作为 XML 协议工作组(XML Protocol Working Group)的基础。XML 协议将代替 SOAP 作为行业标准 XML 消息传递协议的位置。当 W3C 发布 XML 协议的草案标准时,Web 服务体系结构就会从 SOAP 迁移到 XML 协议。

SOAP 是一种简单的、轻量级的基于 XML 的机制,用于在网络应用程序之间进行结构化数据交换。SOAP 包括三部分:一个定义描述消息内容的框架的信封、一组表示应用程序定义的数据类型实例的编码规则,以及表示远程过程调用(remote procedure calls,RPC)和响应的约定。SOAP 可以和各种网络协议(如 HTTP、SMTP、FTP 和 IIOP 或 MQ 上的 RMI)相结合使用,或者用这些协议重新封装后使用。

虽然理解这个基础很重要,但多数 Web 服务开发者不必直接处理这个基础结构。大多数 Web 服务都会使用从 WSDL 生成的经过优化的特定于编程语言的绑定。当服务提供者和服务请求者都在类似的环境中执行时,这种优化可能尤为重要。

图 4 展示了 XML 消息传递(即 SOAP)和网络协议如何组成Web 服务体系结构的基础。

图 4. 使用 SOAP 的 XML 消息传递

网络节点在基于 XML 消息传递的分布式计算中扮演提供者和请求者角色的基本要求是构建、解析 SOAP 消息的能力(或两者兼而有之),以及在网络上通信的能力(接收、发送消息,或两者)。

通常,在 Web 应用程序服务器中运行的 SOAP 服务器将执行这些功能。另外,我们也可以使用在 API 中封装这些功能的特定于编程语言的运行库。应用程序与 SOAP 的集成可以通过使用四个基本步骤来实现:

在图 4 中,服务提供者的应用程序在(1)创建一条 SOAP 消息。这条 SOAP 消息是调用由服务提供者提供的 Web 服务操作的请求。消息主体中的 XML 文档可以是一个 SOAP RPC 请求,也可以是一个服务描述中所描述的以文档为中心的消息。服务请求者将此信息和服务提供者的网址一起提供给 SOAP 基础结构(例如一个 SOAP 客户机运行时)。SOAP 客户机运行时与一个底层网络协议(例如 HTTP)交互,然后在网络上将 SOAP 消息发送出去。
网络基础结构在(2)将消息传送到服务提供者的 SOAP 运行时(例如一个 SOAP 服务器)。SOAP 服务器将请求消息路由到服务提供者的 Web 服务。如果应用程序需要,SOAP 运行时负责将 XML 消息转换为特定于编程语言的对象。这个转换由消息中可以找到的编码模式所控制。
Web 服务负责处理请求信息并生成一个响应。该响应也是一条 SOAP 消息。响应的 SOAP 消息在(3)被提供给 SOAP 运行时,其目的地是服务请求者。在 HTTP 上的同步请求/响应的情况中,联网协议的底层请求/响应本质用于实现消息传递的请求/响应本质。SOAP 运行时将 SOAP 消息响应发送到网络上的服务请求者。
响应消息在(4)由服务请求者节点上的联网基础结构接收。消息会经过整个 SOAP 基础结构;可能会将 XML 消息转换为目标编程语言中的对象。然后,响应消息被提供给应用程序。
本示例使用了请求/响应传送基本原理,这种原理在大多数分布式计算环境中都很常见。请求/响应交换可以是同步的,也可以是异步的。其它传送基本原理,如单向消息传递(无响应),通知(推动式响应)以及发布/订阅,也可能用到 SOAP。

那么,服务请求者如何知道请求消息应该使用什么格式呢?这个问题在下面会得到回答。

 

服务描述:从 XML 消息传递到 Web 服务

服务提供者是通过服务描述将所有用于调用 Web 服务的规范传送给服务请求者的。要实现 Web 服务体系结构的松散耦合,并减少服务提供者和服务请求者之间所需的共识的程度和定制编程与集成的程度,服务描述就是关键。例如,不管是请求者还是提供者,都不必了解对方的底层平台、编程语言或分布式对象模型(如果有的话)。服务描述与底层 SOAP 基础结构相结合,足以封装服务请求者的应用程序和服务提供者的 Web 服务之间的这个细节。

基本服务描述

Web 服务体系结构使用 WSDL 作为基本服务描述。WSDL 已经被提交到 W3C 作为标准。WSDL 是一种 XML 文档,它将 Web 服务描述为一组端点,这些端点会处理包含面向文档或面向过程的(RPC)消息的消息。操作和消息都是被抽象描述的,然后它们会被绑定到一个具体的网络协议和消息格式,用来定义端点。相关的具体端点被合并到抽象的端点或服务中。WSDL 可以扩展为允许端点和其消息的描述,不管使用哪种消息格式或网络协议进行通讯都可以。然而,目前经过描述的绑定只能用于 SOAP 1.1、HTTP POST 以及多用途互联网邮件扩展(Multipurpose Internet Mail Extensions,MIME)。

Web 服务体系结构中对 WSDL 的使用按照常规将基本的服务描述分成了两部分:服务接口和服务实现。这使每个部分都可以被分开独立定义,并可以由另一部分重新使用。

服务接口定义是一种抽象或可重用的服务定义,它可以被多个服务实现定义实例化和引用。我们可以将服务接口定义想象成接口定义语言(Interface Definition Language,IDL)、Java 接口或 Web 服务类型。这使常见的行业标准服务类型可以被多个服务实现者定义和实现。这类似于在编程语言中定义抽象接口然后得到多个具体实现。服务接口可以由行业标准组织定义。

服务接口包含 WSDL 元素,它们组成了服务描述中的可重用部分,这些元素有:WSDL: binding、WSDL: portType、WSDL: message 和 WSDL: type 元素,如图 5 中所描述。WSDL: portType 元素中定义了 Web 服务的操作。操作定义了输入和输出数据流中可以出现的 XML 消息。您可以将操作想象成编程语言中的方法说明。WSDL: message 元素指定哪些 XML 数据类型组成消息的各个部分。WSDL: message 元素用于定义操作的输入和输出参数。WSDL: types 元素中描述消息中复杂数据类型的使用。WSDL: binding 元素描述特定服务接口(WSDL: portType)的协议、数据格式、安全性和其它属性。

服务实现定义是一个描述给定服务提供者如何实现特定服务接口的 WSDL 文档。Web 服务被建模成 WSDL: service 元素。服务元素包含一组(通常是一个)WSDL: port 元素。端口将端点(例如网址位置或 URL)与来自服务接口定义的 WSDL: binding 元素关联起来。

为了说明职责的安排,开放应用程序组(Open Applications Group,OAG)为开放应用程序组集成规范(Open Applications Group Integration Specification,OAGIS)购买标准定义了一个服务接口定义。这个服务接口定义会定义 WSDL: type、WSDL: message、WSDL: portType 和 WSDL: binding。

服务提供者可以选择开发实现 OAGIS 购买订单服务接口的 Web 服务。服务提供者会开发一个服务实现定义文档,描述 WSDL 设备、端口和地址位置元素,这些元素描述提供者的 Web 服务的网址及其它特定于实现的细节。

服务接口定义和服务实现定义结合在一起,组成了服务完整的 WSDL 定义。这两个定义包含为服务请求者描述如何调用以及与 Web 服务交互的足够信息。服务请求者可以要求获得其它关于服务提供者端口的信息。此信息由服务完整的 Web 服务描述提供。

 

完整的 Web 服务描述

完整的 Web 服务描述建立在服务基本的 WSDL 定义之上。完整的 Web 服务描述可以解决这样的问题:什么企业在托管这个服务?它是何种类型的企业?与服务相关联的产品有哪些?各种公司和产品类别中与该企业或其 Web 服务相关联的分类有哪些?有没有服务的其它方面(如服务质量)会影响到请求者是否选择调用服务?为了使查找该服务更容易,可以提供哪些关键字?

图 6 中描述了一个完整的 Web 服务描述。

图 6. 完整的 Web 服务描述协议栈

UDDI 提供了一个保存 Web 服务描述的机制。虽然 UDDI 通常会被认为是一种目录机制,但是它也定义了一个用 XML 表示服务描述信息的数据结构标准。UDDI 条目中有四种基本数据结构,如图 7 中所示。

图 7. 基本 UDDI 数据结构

UDDI 条目由 businessEntity 开始。businessEntity 元素对关于企业的信息进行建模,包括基本的企业信息(例如企业名称和联系方式信息是什么?)、分类信息(例如这是何种类型的企业?)以及标识信息(即 Dunn and Bradstreet 编号是什么?)。businessEntity 包含一组 businessService 元素,每个元素对应于企业希望发布的每个 Web 服务。每个 businessService 元素都包含和 businessEntity 元素的 Web 服务有关的技术性和描述性信息。businessService 包含一组 bindingTemplate 元素。bindingTemplate 描述访问信息(例如端点地址),还描述 businessService 如何使用各种不同的技术规范。技术规范在这里的模型是 tModel。tModel 可以为很多不同概念建模,如:一种服务、一个诸如 HTTPS 之类的平台技术或一个类别。与 businessService 相关联的那一组 bindingTemplate 元素代表了 businessService 所使用的技术的印记。

在Web 服务体系结构中,完整的 Web 服务描述包括用于端点描述的一层,这个端点描述使用 UDDI 条目向服务描述添加企业和实现环境。

端点描述遵循结合 WSDL 使用 UDDI 的约定。端点描述使用 UDDI 提供企业信息和类别的标准表示。这个 UDDI-WSDL 约定规定了如何从和 Web 服务相关联的 UDDI 条目中得出服务接口定义和服务实现定义的 WSDL 描述。这个约定对于在Web 服务体系结构中使用 UDDI 作为基于 WSDL 的服务的服务注册中心来说至关重要。

端点描述向应用到服务的特定实现的服务描述添加了另外的语义。安全属性可以定义对 Web 服务的访问进行控制的策略。服务质量属性指定面向性能的能力,例如服务在一定时间内作出响应的能力,或所支持的可靠消息传递的级别。服务开销属性描述服务的资源需求。还可以定义支持哪些对话语义。

服务描述协议栈中的最后一层是协议描述。协议描述反映两个企业伙伴之间为了完成一个多步企业交互而进行的 Web 服务调用的一个简单的编排。例如,“协议定义”定义了购买协议中诸如购买者和出售者之类的角色。协议定义规定了每个角色必须达到的要求。例如,出售者必须有接受报价请求(request for quote,RFQ)消息、购买订单(purchase order,PO)消息和付款消息的 Web 服务。购买者的角色必须有接受报价(RFQ 响应信息)、发票消息和帐户摘要信息的 Web 服务。这个简单的 Web 服务到企业角色的编排对于在企业伙伴之间建立多步的、面向服务的交互来说至关重要。在很多不同的企业协议标准下,一个给定的服务请求者或服务提供者也许能够扮演购买者或出售者的角色。通过显式地建立企业协议和每个节点在企业协议中扮演各种角色的能力,请求者可以选择在面对各种提供者企业伙伴时加入哪种企业协议。

这个领域充满了创新。对于企业协议定义来说,目前还没有一个单独的标准。ebXML 协作-协议概要和协定规范(ebXML Collaboration-Protocol Profile and Agreement Specification)描述了这些概念,但不是根据作为该体系结构的一部分描述的 Web 服务技术而描述的。Web 服务流程描述和 Web 服务端点描述这两层正处于开发中,它们可以提供这个级别的服务描述。

 

服务描述的发布和发现

服务发布

Web 服务的发布包括服务描述的生成和之后的发布。发布可以使用各种不同机制。

生成服务描述
我们可以生成、手工编码服务描述,也可以根据已有的服务接口定义组成服务描述。开发者可以手工编码整个服务描述,包括 UDDI 条目。有些工具可以从编程模型和可执行 Web 服务的部署生成 WSDL,还有可能生成来自元数据构件的部分 UDDI 条目。部分服务描述可能已经存在(例如,Web 服务可以基于一个行业标准服务接口定义),这样就只须进一步生成一小部分就可以了。

发布服务描述
服务描述可以使用各种不同机制来发布。根据应用程序将使用服务的动态程度,这些不同的机制提供不同的能力。服务描述可以使用多种不同机制发布到多个服务注册中心。

最简单的情况是直接发布。直接发布意味着服务提供者直接将服务发布给服务请求者。这可以通过使用电子邮件附件、FTP 站点甚至光盘分发来实现。直接发布可以在企业伙伴双方就在 Web 上使用电子商务的条款达成一致后进行,或在请求访问服务的服务请求者支付了费用之后进行。在这种情况下,服务请求者可以保留服务描述的一份本地副本。

稍微更动态一点的发布使用 DISCO 或 ADS。DISCO 和 ADS 两者都定义了一个从给定 URL 获取 Web 服务描述的简单的 HTTP GET 机制。增强的服务描述资源库会提供服务描述的一个本地高速缓存,不过还提供了附加的搜索能力。对于在企业内部跨越主机的服务描述资源库来说,服务提供者会向专用的 UDDI 节点发布。我们可以根据发布到节点的 Web 服务的域的范围,使用几种专用的 UDDI 节点。

内部企业应用程序 UDDI 节点(Internal Enterprise Application UDDI node)节点:公司内部为了进行内部企业应用程序集成而使用的 Web 服务应该被发布到这一类 UDDI 节点。此类 UDDI 节点的范围可以是部门的或公司的单独的应用程序。这些 UDDI 位于防火墙之后,允许服务发布者对他们的服务注册中心和它的访问权、可用性以及发布要求有更多的控制。
门户网站 UDDI 节点(Portal UDDI node)节点:由公司发布以供外部伙伴查找和使用的 Web 服务可以使用门户网站 UDDI 节点。门户网站节点运行在服务提供者的防火墙之外或之间。这种专用 UDDI 节点只包含公司希望向来自外部伙伴的请求者提供的那些服务描述。这允许公司保留对他们服务描述的控制、UDDI 节点的访问以及 UDDI 节点的服务质量。此外,通过使用门户网站中固有的基于角色的可见性,企业将服务描述的可见性局限在允许看到它们存在的伙伴中。
伙伴目录 UDDI 节点(Partner Catalog UDDI node)节点:由特定公司使用的 Web 服务可以被发布到伙伴目录 UDDI 节点。伙伴目录 UDDI 节点位于防火墙之后。此类专用 UDDI 节点只包含来自合法企业伙伴的经过允许的、测试过的、有效的 Web 服务。此类 Web 服务的业务环境和元数据可以被定位到特定的请求者。
电子市场 UDDI 节点(E-Marketplace UDDI node)节点:对于服务提供者打算用来与其它 Web 服务竞争请求者的业务的 Web 服务来说,服务描述应该被发布到电子市场 UDDI 节点或 UDDI 运营商节点。电子市场 UDDI 节点由一个行业标准组织或社团托管,包含特定行业中的企业的服务描述。我们可以要求这些服务支持特定的标准、可搜索元数据、接口或数据类型。电子市场 UDDI 节点一般会过滤掉某些非法的条目,并提供有保证的服务质量。
UDDI 运营商节点:如果您希望 Web 服务可以被潜在的新的企业伙伴或服务用户发现,还可以将其发布到 UDDI 运营商节点。IBM、Microsoft 和 Ariba 都支持、复制和托管 UDDI 运营商节点。在发布 UDDI 运营商节点的时侯,如果要让潜在的服务请求者发现服务的话,完整的业务环境和经过深思熟虑的分类法是很必要的。

图 8. 服务发现连续体

图 8 展示了从发布和发现中最静态、最简单的技术到最动态、更复杂的技术的连续体。Web 服务的用户或实现者不必严格遵循这个发展顺序。

服务发现

Web 服务的发现包括获取服务描述和使用描述。获取过程可以使用各种不同机制。

获取服务描述

和发布 Web 服务描述一样,根据服务描述如何被发布以及 Web 服务应用程序可能达到的动态程度,获取 Web 服务描述也会有所不同。服务请求者将在应用程序生命周期的两个不同阶段,即设计时和运行时查找 Web 服务。在设计时,服务请求者按照他们支持的接口类型搜索 Web 服务描述。在运行时,服务请求者根据他们通讯的方式或公告的服务质量搜索 Web 服务。

使用直接发布方法时,服务请求者在设计时对服务描述进行高速缓存,以在运行时使用它。服务描述可以被静态地用程序逻辑表示,并存储在文件或简单的本地服务描述资源库中。

服务请求者可以在设计时或运行时在服务描述资源库(简单的服务注册中心或 UDDI 节点)中检索一条服务描述。查找机制需要支持一种查询机制,它提供按接口类型(基于 WSDL 模板)、绑定信息(即协议)、属性(如 QoS 参数)、所需的中介类型、服务分类法、企业信息等等的查找。

不同类型的 UDDI 节点会显示可以选择的运行时绑定 Web 服务的数目、多选一的策略,或者调用服务之前必须由请求者作出预选的量。

内部企业应用程序 UDDI 节点和伙伴目录 UDDI 节点将不需要预选来建立对服务的信任。服务选择可以建立在绑定支持、历史性能、服务质量分类、相似性或负载平衡的基础之上。

电子市场 UDDI 节点将有更多的运行时服务可以选择。必须执行某种预选以保证 Web 服务提供者是有价值的伙伴。我们可以根据价格承诺、开销、经过允许的伙伴列表的出席情况,同样还有绑定支持、历史性能、服务质量分类和相似性来选择服务。

如果服务请求者从 UDDI 运营商节点查询 Web 服务提供者,他们在预选可能的服务提供者时就必须尽可能谨慎和认真。应该有一个有效和准确的机制就位,过滤掉无用的服务描述和没有价值的服务提供者。

使用服务描述
在获取了服务描述之后,服务请求者需要处理它以调用服务。服务请求者使用服务描述生成对 Web 服务的 SOAP 请求或特定于编程语言的代理。该生成可以在设计时或运行时进行,从而对 Web 服务的调用进行格式化。我们在设计时和运行时可以使用各种工具从 WSDL 文档生成编程语言绑定。这些绑定表示应用程序的 API,并封装了来自应用程序的 XML 消息传递的细节。

在下一部分,我们将描述基本 Web 服务体系结构的扩展,电子商务需要这些扩展才能使用 Web 服务。

 

在下一部分,我们将描述基本 Web 服务体系结构的扩展,电子商务需要这些扩展才能使用 Web 服务。

 

14.3.2 真正的电子商务的 Web 服务

虽然对于可互操作的 XML 消息传递来说 SOAP 和 HTTP 就足够了,而且 WSDL 也足可以传达服务请求者和服务提供者之间需要什么样的消息,但是要覆盖电子商务的全部需求还需要更多的技术。为了完全支持电子商务,安全性、可靠的消息传递、服务质量、Web 服务协议栈的每一层的管理都需要扩展。

 

安全性

真的需要 Web 服务安全层吗?对于基于消息的体系结构,业界已经有一套现成的而且广泛接受的传输层安全机制,比如,安全套接字层(Secure Sockets Layer,SSL)和网际协议安全(Internet Protocol Security,IPSec),为什么还要再加别的呢?为了回答这个问题,我们不仅要研究要求,还将探讨一些只依靠现有的几类传输层安全机制并不能在 Web 服务模型内提供足够的安全性的情况。

通常,Web 服务安全层必须提供以下四个基本的安全性要求:

机密性(Confidentiality)是指信息对没有经过授权的个人、实体或进程的不可用性或不公开性,并保证消息内容不对没有经过授权的个人公开。
授权(Authorization)是指权限的授予,包括根据访问权限授予访问权和保证发送方被授权发送消息。
数据完整性(Data integrity)是指数据没有以未经授权的方式或被未经授权的用户不可察觉的改变或者破坏的性质,从而确保消息在传送的过程中不会被偶然或故意修改。
原始性证明(Proof of origin)是对消息或数据的发送者进行标识的证据。断言消息由正确标识的发送者传送,并且不会重新发送以前传送过的消息。这一要求隐含了数据完整性的要求。
由于需要在基于 XML 消息和工作流的动态 Web 服务世界中管理不同风格的资源访问,所以必须重新评估策略、信任和风险评估这三者相互之间的关系。现有的基于个人身份的访问控制模型正在发展成为基于角色的信任域关系,在该种关系中,可信任的权威机构将执行某项任务的权限授予个人,其行为受该权限限制。Web 服务体系结构定义了需要信息的代理(服务请求者)、提供信息的代理(服务提供者),有时还有提供关于信息的信息的代理(服务中介者、元信息提供者或服务注册中心)。服务中介者经常会收到大量信息请求,这样就需要它能够决定谁想要哪些信息以及请求者是不是已经被授予访问权。基础设施和关系变化迅速,因此有关的策略需要能灵活的允许或拒绝访问。

此外,尽管 XML 发誓要为这样的服务提供通用接口,但 XML 不会提供实现这一梦想所需要的整个基础设施。而且,XML 可能不适合构建整个 Web 服务安全层。目标是要确定在哪些场合用 XML 格式提供信息以顾及通用数据交换较为重要,以及在哪些场合利用目前已存在于平台之上的现有安全性机制较为重要。

SOAP 信封是用 XML 定义的,从而使您可以向消息添加种类众多的元信息,比如事务 ID、消息路由信息和消息安全性。SOAP 信封由两个部分组成:头和主体。头是把功能添加到 SOAP 消息中的通用机制。SOAP 头元素下一级的所有子元素都叫做头条目。主体是为最终的消息接收方想要的应用数据(如 RPC)准备的容器。因此,可以把 SOAP 看作是在传输层(例如 HTTP)和应用层(例如,业务数据)之间引入的另外一层,在此可以方便的传送消息元信息。SOAP 头提供可扩展机制以扩展 SOAP 消息使其可以适用于多种用途。虽然 SOAP 头是向消息添加安全性功能最合理的地方,但是 SOAP 规范本身并没有指定这样的头元素。

让我们仔细的分析一下在 Web 服务模型中现有的各种各样的传输层安全机制为什么不够,又为什么会需要 Web 服务安全层,以及这个安全层最初是怎样的。

端对端的消息传递。安全传输协议,如 SSL 和 IPSec,可以在传输过程中提供消息完整性和机密性,但只有在点对点的情况下,它们才会这样做。但是,因为 SOAP 消息是由中介体接收并处理的,所以即便两两之间的通信链路(communication link)是可信任的,只要在所有的中介体间没有信任关联(trust association),那么安全的端对端通信就是不可能的。如果有一条通信链路不安全,那么端对端安全性也会被削弱。就 Web 服务拓扑来看,安全的传输对于 SOAP 消息的端对端安全性是不够的。

中间件的独立性。最终,唯一能提供端对端安全性的方式就在于应用层或中间件层。如果消息在通信方之间的某点是纯文本,那么就有可能在这点受到攻击。但是,既要在新的或现有的应用中集成加密功能,又不能引入额外的安全性弱点或增加风险,这是一项不容易又不受欢迎的任务。因此,在大多数情况下,人们希望安全性功能尽可能靠近应用,但不在应用本身中构建。

传输的独立性。SOAP 中介体的原意是用来把信息转发到不同的网络上去,通常使用的传输协议也会有所不同。虽然所有的通信链路都是安全的,中介体也是值得信赖的,但是,安全信息(如消息发送者的身份验证)需要被转移到消息路径上的下一个传输协议安全性域,这个过程冗长而且复杂,还可能会导致完整性方面的缺陷。

异步多阶消息传递。传输层安全性保证数据在通信链路上传输时的安全。它与存储在任何中介体上的数据都无关。在一次传输被接收并解密后,传输层安全性对保护数据免受没有经过授权的访问和可能的改变就不是很有帮助了。在先存储消息然后转发的情况下(持久的消息队列),消息层保护是有必要的。

因为我们已经看到安全的传输机制不足以满足 Web 服务开发方法和使用场景的要求,所以我们的任务就是要创建一个概念性 Web 安全层,包括下列几个组件:

对于网络安全性:
支持如 SSL 和 HTTPS 等提供机密性和完整性的安全传输机制。
对于 XML 消息:
如果通信没有中间节点,那么发送方可以依靠 SSL 或 HTTPS 来保证用户标识和密码的机密性。
W3C 正在标准化 XML 数字签名工作的支持。它定义了生成消息摘要与利用发送方的私钥来签发消息的标准 SOAP 头和算法。因此,接收方就可以证明消息发送方的身份。
对网络内部的、可信任的第三方验证服务(例如 Kerberos)的支持。
概念性 XML 消息传递模型还必须支持端对端保护消息及其子元素。为了全面的支持,过程和流能力需要被扩展到包括消息交换的安全性特征。应该有一种方式可以定义多段消息和用预期接收方的公钥来保护消息段。需要探讨的一些论题有:

端点负责实现验证及授权。应该支持在企业之间交换信息的合同的任何描述中都要定义哪些雇员可以使用哪些服务。中介体负责审计和服务原始性证明。中介体还可能需要执行验证、授权和数字签名验证以及有效性检查。
在服务端点的服务描述层中需要定义支持上文论述的安全性问题的面向安全性元数据。这些安全性描述将根据主体或角色定义 Web 服务层访问控制。服务描述将会描述是否支持数字签名、加密、验证和授权以及如何支持它们。
请求者将使用服务描述的安全性元素来查找服务端点,该端点应符合政策要求及其安全性方法。
标准组正在调查如下主题和技术。随着这些标准固定下来,它们将会被并入 Web 服务安全性体系结构。

W3C 有一个 XML 加密工作组,帮助提供数据元素的机密性,这样验证交换成为可能。
W3C 已发布了一个 XML 密钥管理服务(XML Key Management Services,XKMS)的备忘录,来帮助分发及管理在端点之间进行安全的通信所需的密钥。
OASIS 已经成立了一个技术委员会来定义授权和验证断言(Authorization and Authentication assertions,SAML)。这将帮助端点接受和决断访问控制权。
OASIS 已经成立了一个技术委员会来标准化访问控制权的表达(XACML)。这将帮助端点能够以一致的方式解析 SAML 断言。
随着我们不断的研究 Web 服务模型中遇到的所有威胁和对策,Web 服务安全性体系结构也在不断发展着。

 

服务质量(QoS)和可靠的消息传递

服务质量垂直塔提供与 Web 服务概念栈每一层有关的信息的规范。对于网络层,这将会暗示能使用各种级别的服务质量的网络。

由于需要通过网络进行可靠的消息传递,所以得根据在这一领域内发送高质量服务的能力来选择网络技术。可靠的消息传递指基础设施把消息一次发送(只发送一次)到预定目标或提供确定的事件(如果发送没能完成,也许会重新发送到源)的能力。结合网络层与 XML 消息传递将需要支持四个等级的消息传递服务质量:

1. 最佳努力:服务请求者发送消息,服务请求者和基础设施不尝试重发。

2. 至少一次:服务请求者提出请求,并一直重试直到它接收到确认为止。服务提供者重复消息处理不是问题,例如简单的查询处理。实现这可能意味着每个消息包含唯一的标识。服务请求者以自己确定的时间间隔重发没有得到确认的消息。服务提供者发出确认消息,为 RPC 响应消息,如果不能处理的话,就发送不能处理的消息异常。

3. 至多一次:这建立在最少一次情况的基础之上。服务请求者试着请求直到它得到回应。象现有的全局唯一标识符(universal unique identifier,UUID)这样的机制允许服务提供者抑制重复多次的请求,以确保请求不会被多次执行。例如,请求根据库存目录中的一个号码拿一件东西。

4. 刚好一次:服务请求者提出请求,请求已经执行的回应使其得到保证。刚好一次交换模式排除了重传请求的需要并且适应失效的情况。

可靠的消息传递通常是通过标准设计模式传送的,在该模式中,一件基础设施,有时叫做端点管理器,将会被用来在通信的每一端协调消息发送。在这种模式中,发送方通过同步请求把消息发给端点管理器。一旦发送到,发送方就可以得到保证,一定会把消息发送出去或引发确定的事件(例如超时)。端点管理器与其它资源管理器参与本地事务,在一次事务中不仅可以由端点管理器对消息进行排队,还有可能在数据库中记录业务过程步骤。应用程序应该指派端点管理器来负责发送消息或者检测发送失败的原因,它在网络传输层或 XML 消息传递层都可以起作用。

可靠的一次性消息发送的技术和目的都不会引起争议。但是,围绕如何在 SOAP 和 XML 的上下文中支持这项技术已经提出了重要的质疑。关键问题是:应不应该在 XML 消息层上定义必要的协议和消息格式,从而允许可靠的消息发送成为两端应用程序的责任,或者能不能在较低的层(如传输层上)定义协议和消息格式?

在没有支持可靠的消息传递的传输方式的情况下(即互联网),XML 消息传递层将需要在不可靠的基础设施之上支持这些服务质量。端点管理器将需要修改消息,而不是修改消息的传输信封,这样才能成功扮演其角色。应用程序和业务过程的定义将必须考虑所有可能的结果,如拒收消息或在可接受的时间长度内发送不出去。但是,这些定义还需要考虑在发送过程中发生的中间状态。向业务过程公开这些状态可能会大大增加其定义的复杂程度,但对于定义过程的业务分析师而言并无太大意义。在一些情况下,使用 XML 消息传递来发送可靠的消息传递格式可能会导致使用现有的这些传输毫无效率。最好能开发一种在互联网上使用的可靠的 HTTP 标准。

在有支持可靠的消息传递的传输方式的情况下(即在企业内部),它可以用于发送可靠的消息,而不是 XML 消息传递层(可能缺省为空实现)。端点管理器将会只修改传输信封,而不会修改 XML 消息。使用可靠的传输使应用程序和业务过程定义不需要知道或处理消息发送的中间状态。

需要在将来进行的几点补充:

互联网的 HTTP 需要加以改进才能提供可以在企业间使用的简单可靠的消息传递。这会带来额外的好处:不止 SOAP,许多种消息类型都可以采用可靠的消息传递。需要 XML 消息传递层处理可靠的消息传递的情况就会随之减少,促进不依赖于网络选择的应用程序开发。
HTTP 上的 XML 消息传递层也需要处理发布和订阅、消息排序、发送时间限制、优先级和多点传送等等问题。
服务提供者对可靠的消息传递的质量和实现的支持情况将会在服务描述的绑定信息中定义。

服务实现层(例如,通过事务的或安全的 SOAP 绑定)的服务描述以及接口层(例如,从请求者开始等待来自提供者的响应之后最长经过多久)的其它服务描述中都会关系到服务质量(Quality of Service)信息。人们期待着开发出 WSDL 扩展或新的服务描述层来允许指定其它服务质量和功能的规范。

Web 服务层上的服务质量可以在服务合成和服务流中使用。在为流选择服务或提示流管理器该开始恢复或其它的流时,预期的执行时间、超时值、历史平均执行时间值都可以作为输入。服务描述栈的端点描述层和工作流描述层必须提供这一信息。

Web 服务的服务质量问题和解决方案仍然很紧迫。

 

系统和应用程序管理(Management)

随着 Web 服务成为商业运作的重要因素,就需要对其进行管理。在这种情况下,所谓管理是指专为应用程序定制的或从厂商那里买来的管理应用程序可以发现 Web 服务的基础设施、Web 服务、服务注册中心和 Web 服务应用程序存在性、可用性以及健康度。最令人满意的结果是管理系统还应当能够控制和配置基础设施及组件。

管理概念性 Web 服务栈各层的 Web 服务和 Web 服务模型组件必定是有可能的。对管理的需求可以分成两个集中的领域。第一个领域是用于实现 Web 服务的基础设施的可管理性。主要的考虑应当是确保可用性和提供服务描述、消息传递和网络的关键元素的性能。Web 服务基础设施提供者应当提供这一层上的系统管理。

企业对其自己的基础设施及管理拥有完全的自主权。但是,当企业在对等基础上相互作用时,就应当提供对网络层、XML 消息传递层、服务注册中心和 Web 服务实现的基本报告和恢复办法。此外,企业向其合伙人提供的管理接口应当是在服务层上操作的,而不是在相对低级的基础设施层上。合伙人应该能够访问到报告操作和请求处理的状态和健康度的接口,但不一定要理解企业如何管理其请求的细节。

对于网络层,现有的网络管理产品几乎支持目前所有的网络基础设施。这些产品应当用于管理企业内部的 Web 服务的网络基础设施。当企业相互作用时,就应该向其合伙人提供有关 Web 服务基础设施可用性的基本报告。影响 Web 服务基础设施可用性的网络可用性应作为因素之一写入报告。

在 XML 消息传递层,协议应该在企业内部由现有的基础设施管理工具来管理。在企业相互作用的情况下,每个站点都有必要提供协议的基本报告和恢复办法。例如,如果站点 A 支持会话,就该向站点 B 提供可用于查询活跃的 IBM Software Group Architecture Overview Web Services Conceptual Architecture 28 会话以及强行回滚的接口。协议层需要正常的频道与协议和类似对等的控制接口。

管理的第二个方面是 Web 服务本身的可管理性。一些主要的考虑是性能、可用性、事件和使用量度,因为它们将为服务提供者市场收取所提供的服务使用费提供必要信息。

服务描述可以用于宣传可管理性特征和管理需求。这方面的约定正在开发之中。

服务注册中心的任何实现,不管是用于私人消费还是公共消费,都要求基础设施是可用的、发送承诺的服务质量并能够报告使用情况。这些系统管理元素对于成功采用 UDDI 是十分重要的。

对于 Web 服务应用程序组件来说,支持管理环境可能会大大增加应用程序的复杂性。由于 Web 服务必须易于开发,所以必须尽可能向开发者隐藏这样的复杂性。Web 服务的管理方式要使基础设施能自动提供量度、审计日志、启动和停止处理过程、事件通知和作为 Web 服务运行时的一部分(也就是说,起码是 SOAP 服务器)的其它管理功能。因为基础设施通过观察它所托管的组件的行为不可能收集到所有的信息,所以 Web 服务实现也许会需要向托管它的服务器提供基本的健康度和监督信息。

Web 服务基础设施应该为服务提供一种简单的方式以参与管理和利用管理基础设施。可管理的服务的 WSDL 文档的定义应当是 Web 服务能实现提供通过管理系统访问 Web 服务的管理信息的功能。这一接口可能包括的能力是获得配置和量度数据、更新配置及接收来自可管理的 Web 服务的事件。

Web 服务体系结构的平台独立性使它不适合套用任何一条 Web 服务管理标准。因此,需要有一种基于 Web 服务而且允许 Web 服务与管理系统通信的方法。为了达到这一目的,还应当定义由 WSDL 文档描述的、可接收来自可管理 Web 服务的事件以及量度更新的管理服务,并使其可用。管理服务的实现技术与 Web 服务无关。但是,对于基于 Java 技术的环境,Java 管理扩展(Java Management Extension,JMX)应该是合乎逻辑的而且厂商不可知的选择。通过使用 JMX 这样的开放标准,对现有的系统管理提供者来说,要把其目前所提供的产品扩展为包括 Web 服务关键元素的管理应该是很容易的。Web 服务的管理体系结构仍在向前发展。

 

14.4 Web Services 项目实战

14.4.1 Web Services实现

 

本书是重点讲解EJB 3.0的。在理解了Web Services原理之后, 接下来我们讲解如何使用J2EE和EJB3.0来实现Web Services

 

Web 服务遵循 Java 2 平台,企业版(Java 2 Platform,Enterprise Edition,J2EE)、通用对象请求代理体系结构(Common Object Request Broker Architecture,CORBA)以及其它针对与耦合较紧的分布式或非分布式应用程序集成的标准。Web 服务是部署并提供通过 Web 访问业务功能的技术;J2EE、CORBA 和其它标准是实现 Web 服务的技术。

 

J2EE 1.4为使用常规Java类或企业级Java Beans来创建和部署web services提供了一个全面的平台。以下表格给出了J2EE 1.4中包括的web service APIs的细节。

定义在Java Community Process的JSR 101之下的JAX-RPC,提供了创建和访问web services的Java API,因此它是使用J2EE平台创建和部署web services的“心脏和灵魂”。通过向应用程序开发者隐藏XML类型和Java类型映射的复杂性,以及处理XML和SOAP消息的底层细节,它提供了一个简单的,健壮的创建web services应用的平台。为了引入一个方法调用范式,它提供了两种编程模式:服务器端模式,使用Java类或无状态EJB开发web service 端点,和客户端模式,创建作为本地对象访问web services的Java客户端。JAX-RPC 1.1要求使用SOAP 1.1,并且实现与使用其他技术创建的web services之间的互操作性,比如微软的.NET。实现了J2EE1.4规范的应用服务器,比如OC4J 10.1.3和SUN的Java System Application Sever,提供了对于JAX-RPC的支持。

JAX-RPC的叫法有点用词不当,因为它既支持RPC类型的web services,也支持文档类型的web services。

Web Services部署模型

在J2EE 1.4之前,所有J2EE商家都使用他们私有的部署模型支持web services。J2EE 1.4为Java Web Services定义了部署模型。它为J2EE平台上的web services制定了开发,部署以及服务发布和使用的标准。

有了J2EE 1.4对web services的支持,让我们学习使用J2EE平台来建造web service的方法。

使用J2EE创建一个Web Service

把web service创建成一个轻便的和可互操作的分布式组件不是一项琐碎的任务。如之前讨论的,你既可以把常规Java类,也可以把无状态EJB部署成web services。常规Java类被打包在一个web模块中,而EJB web services被打包在标准的ejb-jar模块中。

在这两种部署选择中,你会使用哪一个呢?

Java 类对无状态EJB:永无止境的争论

你会选择常规Java类还是EJB作为你创建web service的技术可能是一个长期的争论。Java类比EJB更容易开发,它是纯的Java对象,并且它不具有EJB带来的“额外辎重”。但是,EJB提供了几个很好的特点,比如被声明的事务和安全性,因此它使开发者将精力集中于建立商业逻辑,而不需要担心基础服务架构。EJB 3.0大大简化了设计模型,在它的规范中,EJB看起来就像常规Java类。

使用J2EE 5.0简化SOA的开发

  使用J2EE创建面向服务的应用程序确实很困难,因此通过使用由JSR 181定义的Web Services 元数据注解,J2EE 5.0将使开发更简单。EJB 3.0和Web Services元数据具有相似的目标,就是向开发者提供亲和力。

  为了在J2EE 1.4中开发一个简单的Java web service,你需要几个web service定义文件:WSDL,映射文件和几个冗长的标准以及私有的web services部署描述符。Web Services元数据规范使用一种类似于EJB 3.0的缺省配置方法来使开发更简便。Web Services元数据注解处理器(或web services 装配工具)会为你生成这些文件,因此你只需要关心类的实现。

  当你使用Web Services元数据开发时,这是一个看起来如此简单的Java web service:

package com.ascenttech.ejb30.ws.demo;
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService(name = "HelloWorldService",
targetNamespace = "http://hello/targetNamespace" )
public class HelloWorldService {
@WebMethod public String sayhello(String name ) {
return "Hello” +name+ “ from jws";
}
}

  正如我之前提到的,EJB 3.0使用常规Java类简化了EJB的开发。通过利用EJB 3.0和Web Services元数据,开发基于EJB的web services将会变得越来越简单。当使用EJB 3.0和web services元数据时,这是一个看起来如此简单的HelloWorld EJB web service。你不必担心创建WSDL,部署描述符等等,应用服务器会在部署过程中生成这些定义文件。

package com.ascenttech.ejb30.ws;
import javax.ejb.Remote;
import javax.jws.WebService;
@WebService
public interface HelloServiceInf extends java.rmi.Remote{
@WebMethod java.lang.String sayHello(java.lang.String name)
throws java.rmi.RemoteException;
}

  如下是EJB 3.0中 HelloWorld EJB的实现类:

package com.ascenttech.ejb30.ws;
import java.rmi.RemoteException;
import javax.ejb.Stateless;
@Stateless(name="HelloServiceEJB")
public class HelloServiceBean implements HelloServiceInf {
public String sayHello(String name) {
return("Hello "+name +" from first EJB3.0 Web Service");
}
}

  以上例子清楚的表明了通过使用web services元数据和EJB 3.0,服务开发正在变得越来越简单。现在,你可以在实现了J2EE规范的应用服务中,比如JBoss Application Server等,开始创建和部署你的web services了。

14.4.2 Web Services 项目实战

14.4.2.1 Web Services的实现
本节使用EJB 3.0 实现一个web Services登录的例子。下面我们创建一个工程叫:EmployeeManager。加入用到的EJB 3.0的jar包。

我们先创建服务器端的实体Bean,这和创建普通的Ejb3.0实体bean是一样的:

package com.ascent.ejb.po;

import java.io.Serializable;

 

import javax.ejb.Remote;

import javax.ejb.Stateless;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.Table;

@SuppressWarnings("serial")

@Entity

@Table(name = "usr")

 

public class User implements Serializable

{

private Integer id;

private String name;

private String password;

private String description;

@Id

@GeneratedValue

public Integer getId()

{

return id;

}

public void setId(Integer id)

{

this.id = id;

}

@Column(name = "name", nullable = false)

public String getName()

{

return name;

}

public void setName(String name)

{

this.name = name;

}

@Column(name = "password", nullable = false)

public String getPassword()

{

return password;

}

public void setPassword(String password)

{

this.password = password;

}

@Column(name = "description", nullable = true, length = 100)

public String getDescription()

{

return description;

}

public void setDescription(String description)

{

this.description = description;

}

}

接着创建远程接口:

package com.ascent.webservice.bean;

 

import java.rmi.Remote;

import javax.jws.WebMethod;

import javax.jws.WebService;

import javax.jws.soap.SOAPBinding;

import javax.jws.soap.SOAPBinding.Style;

@WebService

@SOAPBinding(style=Style.RPC)

public interface LoginDao extends Remote {

@WebMethod

public boolean isLogin(String name, String password);

}

在LoginDao类中要注意的是Remote接口是要实现的,@SOAPBinding(style=Style.RPC),Soap的绑定方式也是需要的,不然在客户端是找不到LoginDao 。

下面创建会话bean:

package com.ascent.webservice.bean;

import java.util.List;

import javax.ejb.Stateful;

import javax.ejb.Stateless;

import javax.jws.WebService;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

import javax.persistence.Query;

@Stateless

@WebService(endpointInterface = "com.ascent.webservice.bean.LoginDao")

public class LoginDaoBean

{

@PersistenceContext

protected EntityManager em;// the manager of entity

public boolean isLogin(String name, String password)

{

// define query sentence

StringBuffer hql = new StringBuffer();

hql.append("from User u where u.name='" + name + "'");

hql.append(" and u.password='" + password + "'");

// create the query

Query query = em.createQuery(hql.toString());

List queryList = query.getResultList();

// if the result is null

if (queryList.size() == 0)

{

return false;

}

// if the user's length greater 1

if (queryList.size() > 1)

{

return false;

}

// return single user

return true;

}

}

至此服务器端的类是建好了,这里又两个问题,需要说明一下

A. 两个类的方法中都没有抛出异常,可不可以抛出呢? 可以。但到实现SOA的时候会有一些问题,就是异常在经axis 通过WSDL2Java 生成后,在程序运行时有可能会出现找不到的情形。所以本文为了让大家在仿照这个例子时都能成功,所以也就抛弃了异常的抛出。

B. 两个类的方法的返回值是基本类型,而不是自定义类型,为什么会这样呢,自定义类型可以不可以呢,可以。

现在我们的webservice的服务器端就已经创建好了,我们把服务器端的三个类和persistence.xml文件一起打包部署到jboss服务器里。包名是:empdEjb。

下面我们来创建客户端:

package com.ascent.webservice.client;

import java.net.URL;

import javax.xml.namespace.QName;

import javax.xml.rpc.Service;

import javax.xml.rpc.ServiceFactory;

import com.ascent.webservice.bean.LoginDao;

public class LoginClient

{

public static void main(String[] args) throws Exception

{

String userName ="lxl";

String password = "lxl";

URL url = new URL("http://localhost:8080/empdEjb/LoginDaoBean?wsdl");

QName qname =

new QName("http://bean.webservice.ascent.com/jaws","LoginDaoService");

ServiceFactory factory = ServiceFactory.newInstance();

Service service = factory.createService(url, qname);

LoginDao loginDao = (LoginDao) service.getPort(LoginDao.class);

boolean isExists = loginDao.isLogin(userName, password);

if(isExists)

{

System.out.println("hello " + userName);

}

else

{

System.out.println("sorry " + userName + ", you are not user in the system!");

}

}

}

把服务器端发布出去以后,服务器端会自动发布一个webService,并且生成并发布一个WSDL文件,通过访问http://localhost:8080/empdEjb/LoginDaoBean?wsdl这个网址是可以找到的。http://bean.webservice.ascent.com/jaws 和 LoginDaoService 字符串,在客户端程序当中出现了上面两个字符串,它们在你生成的wsdl 文件中有详细的描述。在地址栏里输入http://localhost:8080/empdEjb/LoginDaoBean?wsdl,就可以看到wsdl文件了。这里你所需要做的是按照你自己的情况编写你自己的客户端程序。启动服务器后,在机子上运行客户端程序就可以了。当然你也可以去编写自己的jsp客户端去调用它。wsdl文件的内容如下:

<definitions name="LoginDaoService"

targetNamespace="http://bean.webservice.ascent.com/jaws">

<types/>

<message name="LoginDao_isLogin">

<part name="String_1" type="xsd:string"/>

<part name="String_2" type="xsd:string"/>

</message>

-

<message name="LoginDao_isLoginResponse">

<part name="result" type="xsd:boolean"/>

</message>

-

<portType name="LoginDao">

-

<operation name="isLogin" parameterOrder="String_1 String_2">

<input message="tns:LoginDao_isLogin"/>

<output message="tns:LoginDao_isLoginResponse"/>

</operation>

</portType>

-

<binding name="LoginDaoBinding" type="tns:LoginDao">

<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

-

<operation name="isLogin">

<soap:operation soapAction=""/>

-

<input>

<soap:body namespace="http://bean.webservice.ascent.com/jaws" use="literal"/>

</input>

-

<output>

<soap:body namespace="http://bean.webservice.ascent.com/jaws" use="literal"/>

</output>

</operation>

</binding>

-

<service name="LoginDaoService">

-

<port binding="tns:LoginDaoBinding" name="LoginDaoPort">

<soap:address location="http://lixinli:8080/empdEjb/LoginDaoBean"/>

</port>

</service>

</definitions>

这样一个WebService+Ejb 3.0的例子就实现了。

14.4.2.2 SOA的实现
本例还是基于前两个例子的基础上的,要保证上面的例子是能正常运行的。

1.WSDL2Java
从名字上可以看出,是把wsdl 转化为java的.在上面的例子中我们在服务器端生成了一个wsdl文件,现在要做的就是你把那个wsdl文件给别人或者别的公司,让他们根据wsdl中所描述的你所提供的服务,去开发一个应用,来访问你所提供的接口。拿到这个WSDL文件后要做什么呢,to java。我们来看看怎么to java。

这里,在原来的EmployeeManager工程下面建一个wsdl文件夹,将它放在下面,然后所要做的准备工作是,把包括axis在内的几个jar包找到,设置在你的classpath里面。然后在命令行下运行WSDL2Java。

哪几个jar包?

C:/axis-1_4/lib/axis.jar;C:/axis-1_4/lib/axis-ant.jar;C:/axis-1_4/lib/commons-discovery-0.2.jar;C:/axis-1_4/lib/commons-logging-1.0.4.jar;C:/axis-1_4/lib/jaxrpc.jar;C:/axis-1_4/lib/log4j-1.2.8.jar;C:/axis-1_4/lib/saaj.jar;C:/axis-1_4/lib/wsdl4j-1.5.1.jar;E:/jboss-4.0.5/server/default/lib/activation.jar;E:/jboss-4.0.5/server/default/lib/mail.jar

这是我classpath 里面所设置的几个jar包,后面两个可以不需要,第二个也可以不需要,后俩个只是保证运行的时候没有警告。

1. 将LoginDaoBean.wsdl 放在wsdl文件夹下面。E:/workspace/EmployeeManager/wsdl

2. 在命令行下进入E:/workspace/EmployeeManager/wsdl

3. 执行 java org.apache.axis.wsdl.WSDL2Java LoginDaoBean.wsdl

这个时候,你会发现在wsdl文件夹下面生成了一个目录,它里面包含了几个java类。

LoginDao.java,LoginDaoBindingStub.java,LoginDaoService.java,LoginDaoServiceLocator.java

2 SOA的实现
本节是一个以SOA+struts实现登录的例子。新建一个web工程,EmployeeWebService,然后将上面生成的几个类放入你的src目录下面,是放整个目录,别只放几个类进去. 构建struts资源。创建struts的过程就不在这里细说了。创建的action内容如下:

package com.ascent.webservice.struts.action;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionForm;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.ActionMapping;

import org.apache.struts.action.ActionMessage;

import org.apache.struts.action.ActionMessages;

import com.ascent.webservice.struts.form.LoginForm;

import com.ascent.webservice.bean.jaws.LoginDao;

import com.ascent.webservice.bean.jaws.LoginDaoServiceLocator;

public class LoginAction extends Action

{

public ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throws Exception {

LoginForm loginForm = (LoginForm) form;

String userName = loginForm.getLoginName();

String password = loginForm.getPassword();

LoginDaoServiceLocator loginDaoServiceLocator = new LoginDaoServiceLocator();

LoginDao loginDao=

loginDaoServiceLocator.getLoginDaoPort();

boolean isExists = loginDao.isLogin(userName, password);

if(isExists)

{

System.out.println("hello " + userName);

HttpSession session = request.getSession();

session.setAttribute("loginName", loginForm.getLoginName());

return mapping.findForward("success");

}

else

{

System.out.println("sorry " + userName + ", you are not user in the system!");

ActionMessages messages = new ActionMessages();

messages.add("login",new ActionMessage("error.login.jsp.loginName.exists"));

this.saveErrors(request, messages);

return mapping.getInputForward();

}

}

}

这里用到的LoginDaoServiceLocator类和getLoginDaoPort()方法就是使用WSDL2Java命令把wsdl文件生成的类。现在就可以打包成叫employee的war文件,运行它。至此,你便可以在浏览器中输入http://localhost:8080/employee/login.jsp,运行你这个SOA的应用了。如果是把服务器端部署到别的机器上,只要把localhost改为相应的ip就可以了。

小结
本章首先介绍了目前一个前沿技术:Web Services和面向服务的软件架构(Service Oriented Architecture,简称SOA)。在理解了Web Services原理之后, 接下来我们讲解了如何使用J2EE和EJB3.0来实现Web Services。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值