JBI规范1.0 (三)
 

2009-11-30 作者:Juset 来源:Juset的blog

 

[5]——规格化消息路由

1 规格化消息路由(Normalized Message Router)

规格化消息路由(NMR)从JBI组件(服务引擎或绑定组件)接收消息交换并将其路由到适当的组件进行处理。这种中间消息交换处理模型把服务消费者和提供者分离,并允许NMR在消息交换的生存周期中可以执行一些附加的处理。

注意,本章中使用WSDL2.0中的术语而不是WSDL1.1。

1.1 关键概念(Key Concepts)

本节介绍NMR架构中的几个关键概念。NMR的目标是允许组件之间以服务提供者和服务消费者的身份,使用基于WSDL的服务描述彼此交互。这是组件可以进行混合匹配(mix-and-match)装配、构造集成解决方案和服务基础的关键。

WSDL [WSDL 2.0, WSDL 1.1]为JBI组件的交互提供了基本模型和描述机制。WSDL提供了一个基于XML消息交换操作的抽象服务模型,该模型可以通过扩展提供特定的绑定信息:将抽象模型映射到实际的通信协议和端点[WSDL 2.0 bindings] 的特定的协议信息。

JBI扩展应用了WSDL的抽象消息模型,可以把NMR看作是一个抽象的WSDL消息系统基础平台[infrastructure],绑定组件和服务引擎在这个平台上提供和使用WSDL定义的服务。

WSDL定义了一项服务提供者和消费者之间的消息交换操作,消息交换的参与者依照各方都能理解的一种消息交换模型进行操作。一组相关联的操作称作“接口”(WSDL1.1中称为端口类型)。一个“服务”实现这样的一个“接口”。一个服务具有一个或多个端点,每个端点通过一种特定的绑定(消息通信协议)为外部系统访问该服务提供入口。

所有的这些WSDL概念都被JBI用于在APIs和SPIs中构建服务模型。

1.1.1 服务的消费者和提供者 (Service Consumers and Providers)

JBI引擎和绑定组件可以作为服务消费者,服务提供者或两者兼具。服务提供者通过端点(endpoint)提供WSDL描述的服务;服务消费者发送消息交换调用特定的操作来使用服务。服务(service)实现了WSDL接口,WSDL接口是通过交换抽象定义的消息来描述的一组相关的操作。

服务消费者和服务提供者之间通常只共享抽象的服务定义而不是具体的端点信息,因而两者之间是松耦合的。这种结构把服务的消费者同特定的服务提供者实现(如特定协议)分离开来。

一个WSDL接口可以具有多个服务实现,服务消费者在查找实现了某个接口的提供者时,可能会查找到多个实现了该接口的服务提供者(服务和相关联的端点)。

1.1.2 规格化消息(Normalized Message)

JBI使用“规格化(normalized)”消息的概念描述服务消费者和提供者之间的交互。一条规格化消息由以下三个主要部分组成:

· 消息载荷(payload):符合抽象WSDL消息类型,不具有任何协议编码或格式信息的XML文档(这不是一个消息的规范格式)。

· 消息属性(或元数据):包含了在消息处理过程中获得的与消息相关的额外信息。消息属性可以包含消息的安全信息(例如消息接收方的签名信息),事务上下文信息和组件特定的信息。

消息附件:消息载荷(内容)的一部分可以由附件构成,附件由消息载荷来引用,并且附件中包含一个可以操作附件内容的数据处理器。这些附件可以是非XML格式的。

规格化消息通过抽象WSDL消息类型定义为组件间的交互提供了互操作。

1.1.3 传输通道(Delivery Channel)

传输通道(DeliveryChannel)是绑定组件和服务引擎与NMR之间通信使用的双向通信管道。传输通道构成了服务消费者,提供者和NMR之间的交互契约,即API接口。服务消费者使用传输通道开始服务调用,服务提供者使用传输通道从NMR接收服务调用。既作为服务提供者又作为消费者的组件使用同一个传输通道完成这两种功能。

以一个绑定组件接收JBI系统外部的服务消费者的请求为例,当接收这种传入的服务调用请求后,该绑定组件通过其传输通道与NMR交互时必须扮演服务消费者的角色(在此用例中,绑定组件可以看作是一个服务消费者的代理)。

每个组件都具有唯一的一个传输通道,因此传输通道的实现必须支持在多线程环境下的并发使用。

1.1.4 运行时端点激活(Run-time Endpoint Activation)

运行时激活是服务提供者激活其提供的实际的服务的过程。在该过程中,服务提供者将其提供的服务通知NMR,这样NMR可以将服务调用路由到该服务。端点激活分为两步:

· 向NRM声明一个服务端点

· 提供描述该端点定义的元数据信息

向NRM声明一个服务端点是绑定组件或服务引擎向NMR激活一个端点名称的过程。在NMR中声明的端点名称必须有相应的元数据信息详细描述该端点的注册,这部分详细信息通过使用SPI来提供。

1.1.5 服务调用和消息交换模式(Service Invocation and Message Exchange Patterns)

服务调用指服务消费者和服务提供者之间的一个端对端交互的实例。尽管无法在JBI中定义一套完整的服务调用模式,但是JBI实现必须支持以下列表中的交互模式:

· 单向交互(One-Way)模式:服务消费者向提供者发送一个请求,服务提供者不必向消费者返回任何错误(故障)信息。

· 可靠的单向交互(Reliable One-Way)模式:服务消费者向提供者发送一个请求。如果服务提供者处理该请求失败,会向消费者返回出错信息。

· 请求-回复(Request-Response)模式:服务消费者向提供者发送一个请求,并期望服务提供者响应。如果处理请求失败则返回出错信息。

· 请求-选择回复(Request Optional-Response)模式:服务消费者向提供者发送一个请求,并期望服务提供者响应。消费者和提供者接受到一个消息后,都可以选择是否应答。

上述提到的消费者和提供者角色可能是绑定组件也可能是服务引擎。当一个绑定组件作为服务消费者时,说明存在一个外部的服务消费者;同样,当一个绑定组件作为服务提供者时,说明存在一个外部的服务提供者。当服务引擎作为服务提供者或消费者时,说明该服务引擎是一个内部参与者。

WSDL 2.0预定义扩展[WSDL 2.0 Extensions]定义了一个消息交换模式(MEP)作为“一个操作中的所有抽象消息的顺序和基数”。JBI使用该模式定义消费者节点和提供者节点之间的交互。该模式根据消息类型(normal或fault)和消息流向来定义。

MEPs总是以提供者的角度来理解。例如,在请求-回复交互模式中, MEP的定义是in-out,其消息流从提供者的角度来看,请求(Request)是输入(in),回复(Response)是输出(out)。从服务消费者的角度看,消息流的方向正好相反,但是NMR在与服务消费者交互过程中使用的MEP总是从服务提供者的角度来说的。当处理WSDL MEPs时,这是一种惯用的处理方式。In-Out模式示意图如图8所示:

图8 In-Out消息交换模式

如图所示,第一条消息(请求)从消费者发送到提供者,从提供者的角度看其方向是输入(in)。第二条消息(回复)是从提供者到消费者,即输出(out)。这些交换是按给定的顺序执行的,从提供者的角度这种交换首先是输入一条消息(in),跟着是输出(out)一条消息,因此这种交互模式命名为输入输出(in-out)模式。

1.1.6 消息交换(Message Exchange)

消息交换(ME)作为规格化消息的“容器(container)”。ME不仅封装了其实现的消息交换模型中的输入(in)消息和输出(out)消息,它还包含了这些消息的元数据信息以及正在进行的消息交换的状态信息。消息交换代表了JBI本地服务调用的一部分。下表说明了服务调用和消息交换模式之间的关系。

服务调用(Service Invocation)
消息交换模式MEP (提供者角度)
One-Way
In-Only
Reliable One-Way
Robust In-Only
Request-Response
In-Out
Request Optional-Response
In Optional-Out

表 2 服务调用与MEP的映射

图9 两服务引擎之间的单向服务调用

上图描述了两个本地服务引擎间的单向服务调用,包括了一个消息交换实例:SE1作为一个服务消费者,通过创建并初始化一个包含了消息请求的in-only消息交换来调用所需要的服务操作。如图中SE1和NMR之间的对象流所示,SE1将该消息交换实例发送到NMR。

NMR决定由哪一个服务提供者提供所请求的服务操作,并把该消息交换实例传输给选定的提供者。在图中表示为NMR到SE2的对象流。

注意,该图示并不意味着调用者必须在交换完成之前阻塞当前线程的执行。调用方在发送和接收消息交换时,调用者可以是事件驱动的,服务引擎的内部状态的前进不必占用着线程资源不放。

图10 SE调用远程JBI实例提供的服务:Robust In With Fault

下面描述图10所示的处于两个不同的JBI环境中的服务引擎之间的可靠的单向调用。注意,规范中未规定如何“联合(federating)”两个分离的JBI实例,该例中两个JBI实例之间通过BC1和BC2提供的通信协议使得两者作为远程的消费者和提供者而相互可见,并不存在影响两者之间服务调用的特殊关系。

SE1构建并初始化了一个可靠的单向(robust-in)消息实例,并发送到NMR(在构建ME实例的过程中,NMR为该实例设定了一个唯一标识“X”)。NMR选择合适的服务提供者,并将消息交换以可靠的单向消息模式发送给BC1。BC1按照其所实现的协议需求封装消息请求,并将该请求发送给服务提供者,在本例中,恰好是另一个通过BC2发布了一个服务端点的JBI实例JBI2。

当JBI2实例中的BC2组件接收到请求以后,该组件构建一个可靠的单向消息交换实例(“Y”)并发送给NMR。JBI2中的NMR选择合适的服务提供者并将消息交换实例(“Y”)发送给该提供者即SE2。

SE2接收实例“Y”,处理完请求之后,SE2可能选择返回一个故障消息说明发生了一个应用级别的错误。在本例中,SE2可以使用实例“Y”返回故障消息,沿着请求流向的反向路径最后通过消息交换实例“X”到达JBI1中的SE1组件(在此MEP中,存在一个“done”响应信息会沿着相同的路径传递,为了清晰起见未在图中显示)。

图11 SE调用远程服务

下面描述图11所示的JBI服务引擎与JBI系统外部的一个远程服务提供者之间的请求-响应调用。

服务引擎SE创建并初始化一个in-out模式的消息交换实例并发送到NMR,由NMR决定哪一个服务提供者应当处理该调用。在本例中,BC组件被选中,BC接收到该消息交换(in-out模式),将其反规格化(denormalize),然后通过通信协议发送到外部的服务提供者。外部提供者处理消息请求,返回适当的信息(正常的回复或者是应用处理层级的错误消息)。BC组件规格化该回复并封装到消息交换中,然后发送到NMR,最终返回给消费者SE。

1.1.7 端点(Endpoints)

端点,在WSDL2.0中指代一个可以通过特定协议访问的特定的地址,用于访问特定的服务。JBI使用这个概念表示两种不同类型的端点:

  • 外部端点(External):JBI系统外的端点:
    • 外部服务提供者发布的端点。
    • 绑定组件发布的端点,用于代理外部服务消费者。
  • 内部端点(Internal):JBI系统内部服务提供者发布的端点,可以通过NMR的API访问。

绑定组件在内部端点和外部端点之间建立映射,例如,绑定组件提供的内部端点可以映射到外部服务提供者发布的端点。

在JBI中,端点可以通过三种不同的方式进行引用:

  • 隐式引用(Implicitly):JBI根据服务类型选择服务提供者端点
  • 显式引用(Explicitly):服务消费者根据自身的逻辑或配置选择服务提供者端点
  • 动态引用(Dynamically):在消息交换中使用一个端点引用(EPR)来提供“回调(call back)”地址供服务提供者返回应用会话进行过程中其它相关的消息交换信息。EPR在JBI中具有两种不同的使用方式:
    • EPR构建(EPR creation):一个组件希望在消息中提供回调地址时,必须能够构建适当的EPR。
    • EPR解析(EPR resolution):一个接收EPR的组件必须能够解析该EPR(也就是将其转换成可用的端点)以便于将消息交换发送到适当的端点(通常是外部服务提供者)。

1.2 消息交换的构成(Element of a Message Exchange)

  • 模式(Pattern)—每条消息交换都由一个模式描述,该模式描述了组成一个消息交换的方向(derection),顺序(sequence),基数(cardinality)和消息名称(normal或fault)。
  • 起始组件(Initiator)—构建该消息交换的组件。在所有JBI定义的消息交换模式中服务消费者组件担当消息交换的初始组件。
  • 服务组件(Servicer)—处理该消息交换的组件。服务提供者担当服务组件。
  • 角色(Role)—当一个组件“拥有”某个消息交换实例时,该组件所扮演的角色,包括:
    • 提供者(Provider)—提供该消息交换请求的服务。
    • 消费者(Consumer)—向服务提供者请求一个服务。
  • 地址(Address)—一个服务引用,端点引用和NMR用来路由消息的逻辑地址的操作名称。
  • 消息(Message)—携带一条或多条消息的消息交换ME。
  • 故障(Fault)—携带至多一条故障信息的消息交换,故障(fault)是一种特殊的消息。
  • 状态(Status)—描述消息交换的状态:error,done或active中的一种。
  • 错误(Error)—用于描述错误状态特征/原因的Java Exception对象。
  • 属性集(Properties)—消息交换的起始组件和服务组件可以为一个消息交换关联任意的属性信息。NMR可以保留某些属性名称用于声明QoS,安全,事务或其他操作元数据。

消息交换的生命周期很短,不能够在系统停止或崩溃时保留。由消费者和提供者组件提供的错误恢复逻辑必须能够处理这类错误案例。能够使JBI支持可恢复性的可靠的消息交换将在JBI2.0中进行考虑。

1.2.1 交换概要(Exchange Summary)

下表是JBI使用的WSDL2.0中定义的消息交换模式。每种定义包含一个故障处理规则,指定创建消息交换中的哪一种消息来把故障信息从一个参与者传到另一个参与者。与WSDL1.1等同的操作在下表中给出:

消息交换模式名称
消息序列
故障处理规则
WSDL 1.1操作
In-Only
in
none
One-way
Robust In-Only
in
on in
-
In-Out
in, out
replaces out
Request-response
In Optional-Out
in, out
on in or out
-

表 3 JBI标准消息交换模式(来自WSDL 2.0)

1.3 规格化消息(Normalized Message)

规格化消息是JBI消息交换中的消息。本节定义了规格化消息,同时讨论了JBI中处理该类消息的模型。

1.3.1 规格化定义(Normalization Defined)

消息的规格化指的是将协议与业务上下文信息以可传输的方式存储起来的过程。NMR处理的所有消息都是规格化的,规格化是一个双向的过程:

  • 消息的规格化(Normalizing)是将包含了特定上下文数据的消息转换成上下文中立的抽象表示,本质上说就是将由NMR解释的上下文数据转换成一种NMR能够理解的“通用”的表现形式。任何其他的信息(如消息载荷,附加上下文等)都可以由消息承载并由NMR传递,但是,这些信息对NMR来说是不透明的。注意,规格化(normalization)并不意味着要将消息载荷数据规范化(canonicalization)。
  • 反规格化消息(Denormalizing)指的是从NMR接收消息并将其转换为与上下文相关的表现形式。反规格化是规格化的逆向操作,例如,一个SOAP绑定组件反规格化消息时会将消息适当的部分以及消息元数据放入SOAP信封,转化成需要的编码格式。

1.3.2 规格化消息的结构(Structure of a Normalized Message)

规格化消息包含两个不同的部分:上下文和载荷。

  • 消息上下文(Message context)指的是一组消息属性集,可为消息关联一些元数据信息。
  • 消息载荷(Message content)实际上是包含了所有消息载荷的原始数据信息的抽象。此处,术语“包含(contain)”是指包含在描述消息的抽象数据模型中。

1.3.3 处理方法(Processing Methods)

规格化消息提取并未规定消息载荷的处理方式。消费者可以根据特定上下文需求自由选择消息载荷的处理方式。例如,一个高性能的绑定组件会考虑使用“拉(pull)”模式的解析器或原始字节流来序列化消息载荷;另一方面,一个转换引擎可能需要将整个消息载荷加载到内存中表示成XML的DOM树以便于查询。

1.3.4 二进制数据(Binary Inclusion)

规格化消息的消息载荷一定是XML,对于非XML(包括二进制数据)的数据通过使用JAF(Java Activation Framework)将这些数据作为规格化数据的附件。

1.3.5 WSDL1.1的支持(Support for WSDL 1.1)

服务提供者可能使用WSDL1.1描述其自身而不是用WSDL2.0。由于WSDL1.1的消息模型与2.0有很大的区别,JBI定义了一个映射关系用于将WSDL1.1消息映射成为单个XML文档的规格化消息形式。

1.4 NMR设计(NMR Design)

本节给出规格化路由的详细设计信息。详细讲解JBI容器(绑定组件或服务引擎)和NMR之间的契约。

1.4.1 激活(Activation)

如前所述,激活是服务提供者向NMR声明其所提供的服务的过程。每个声明都必须伴有一个描述此声明的相关元数据定义。(这些元数据由组件提供的SPI提供)JBI要求这种声明是用WSDL1.1或WSDL2.0。NMR假设(并不强制)所有的这种声明都符合WSDL规范。然而,如果元数据格式难看,服务的消费者就很有可能无法正确的调用该服务。

1.4.1.1 服务提供者激活(Service Provider Activation)

下图是服务提供者激活的一个领域模型。

图12 服务激活领域模型

每个提供者激活的端点必须使用元数据描述该服务、端点及其绑定细节。(服务引擎对其提供的服务的激活使用一种特定的JBI定义的绑定类型。)

下面用一个逻辑顺序图描述了激活的情景。

图13 BC端点激活消息序列图

此图只显示与激活相关的活动,没有表明端点激活和消息交换之间的联系。

1. 作为预处理,BC提供组件SPI的一个实现,组件SPI的一部分用于提供元数据描述的激活端点。(所有的组件都必须提供这个。)

2. BC激活一个端点的名称。

3. NMR初始化服务端点。

4. SE通过端点名称(或其他标准)查询NMR以获取端点。

5. SE使用端点查询NMR获得服务端点的元数据描述。

6. NMR调用BC的组件SPI获取合适的元数据。

7. SE解析NMR返回的DOM文件。

1.4.2 交换消息(Exchanging Messages)

本节对JBI支持的四种标准MEP进行深入的探讨。值得注意的是NMR提供了一个可扩展的消息交流模型。JBI供应商可以自由地扩展四种MEP的标准目录。

每种模式以一个状态交换结束,用于确定交换已经结束。这种方式对于可选消息(或默认)作为消息交换模式的一部分来说尤其有用,在所有的MEP中支持可靠消息交换。

在标准JBI模式中,以起始组件开始的交换模式一般是服务的消费者,它创建并初始化一个新的交换实例。服务消费者 “拥有”这个消息交换实例直到把它发送出去(使用DeliveryChannel.send())。随后这些参与者交替接收和发送消息交换实例,直到某个参与者把消息交换状态设置为终结状态并把它发送给另一个参与者。当一个组件接收一个消息交换实例(使用DeliveryChannel.accept())时,就说它“拥有”一个消息交换。

下图显示了对于每种标准JBI消息交换模式,服务消费者和提供者之间可能的交互。每种模式都是两个参与者(无论它们的角色是服务提供者还是消费者)之间的契约。注意,当接收一个消息交换实例时,一个参与者可以通过使用MessageExchange.getRole()很容易地决定它的角色是服务提供者或是消费者。这有助于参与者在参与较复杂的模式时与参与较简单模式时一样方便。

1.4.2.1 图例(Diagram Key)

此处的MEP图展示了接下来的图例要使用到的符号。这些图代表了消息交换的时间顺序,时间在图中显示为从上到下。交替过程(故障或正常回复)使用选择符号表示。

图14 MEP图例

1.4.2.2 In-Only消息交换模式(In-Only Message Exchange)

这种模式被用作单向消息交换。

图15 In-Only MEP

注意:

  • 服务消费者使用消息发起请求。
  • 服务提供者回复状态以完成交换。

1.4.2.3 健壮的In-Only消息交换模式(Robust In-Only Message Exchange)

这种模式被用作可靠的单向消息交换。

图16 Robust In-Only MEP

注意:

  • 服务消费者使用消息发起请求。
  • 服务提供者可能回复状态或故障。
  • 如果服务提供者回复状态,那么交换完成。
  • 如果服务提供者回复故障,消费者回复状态以完成交换。

1.4.2.4 In-Out消息交换模式(In-Out Message Exchange)

这种模式被用作双向消息交换。

图17 In-Out MEP

注意:

  • 服务消费者使用消息发起请求。
  • 服务提供者回复消息或故障。
  • 消费者回复状态。

1.4.2.5 In Optional-Out消息交换模式(In Optional-Out Message Exchange)

这种模式被用作服务提供者的回复可选的双向消息交换。

图18 In Optional-Out MEP

注意:

  • 服务消费者使用消息发起一个请求。
  • 服务提供者可能回复消息、故障或状态。
  • 如果服务提供者回复状态,那么交换完成。
  • 如果服务提供者回复故障,消费者必须回复状态。
  • 如果服务提供者回复消息,消费者可能回复故障或状态。
  • 如果服务消费者恢复故障,提供者必须恢复状态以完成交换。

1.4.3 消息交换路由(Routing Message Exchange)

服务消费者使用消息交换工厂(MessageExchangeFactory)创建一个新的消息交换实例,并使用以下提供者可能需要的信息(这些信息为了增加特定性)初始化这个实例:接口名称、服务名称、端点名称。下面给出了每种地址类型的详细信息。如果消息交换实例中有多种地址类型,实现时必须使用最特定化的那个地址类型而忽略其他。

  • 接口名称。如果在一个消息交换中指定了接口名称,实现必须使用隐式端点选择。
  • 服务名称。实现必须选择一个属于给定服务名称的服务提供者端点,使用隐式端点选择。
  • 端点。实现必须使用给定的端点作为服务提供者,可以使用直接或间接(直接路由和间接路由详见5.4.3.3节)

如果消息交换指定的服务可以通过不止一个端点来实现(当使用接口名称或服务名称来查询交换时是有可能的),必须只能选择一个服务提供者端点。这里不再讨论如何选择端点。

当服务提供者发出一个交换消息之后,总是先将它发送到触发交换过程的组件。而随后从服务的消费者返回给提供者的交换消息会被发送到一个在交换过程的初始化阶段就已经确定了的服务提供者端点。

当一个消息交换被发送到一个指定的服务提供者端点,实现必须把消息交换传输到激活给定端点的组件的传输通道(DeliveryChannel)。

当一个消息交换被返回给一个服务消费者组件,实现必须把消息交换传输到消费者组件的传输通道(DeliveryChannel)。

1.4.3.1 实现消息交换地址属性的改变(Implementation Changes to Message Exchange Address Properties)

一个服务消费者可能在初始化消息交换实例的过程中选择性地设置一些消息交换提供者的地址属性。当一个提供者的地址属性被设置之后,实现禁止改变其他任何属性(接口名称、服务名称和服务端点)。

当组件发送一个新的交换实例,实现必须把交换实例的端点属性设置为NMR选择那个的端点(如果组件没有指定该端点),这个端点作为服务提供者。注意,如果组件直接指定了端点,实现禁止改变这个端点值,即使这个端点设计到一个服务连接。(详见5.4.3.3节)

1.4.3.2 隐式端点选择过程(Implicit Endpoint Selection Process)

当服务消费者使用任何形式的地址(而不是一个服务端点)指定了所需的服务提供者,JBI实现必须隐式地选择服务提供者端点。本节给出实现进行隐式端点选择的过程中所必须遵守的几点。当服务消费者调用DeliveryChannel.send或DeliveryChannel.sendSync发送一个消息交换实例时,必须进行隐式端点选择。

如果在选择的过程中没有候选的服务提供者端点匹配给定的服务地址,或者没有合适的候选服务提供者,send或者sendSync方法必须抛出一个MessagingException异常并给出相应的出错信息详细描述路由故障。

JBI实现过程中的每个候选服务提供者端点都必须做到如下几点:

  • 调用服务消费者的Component.isExchangeWithProviderOkay()方法,提供属于这个选择过程的消息交换和候选选端点作为该方法的参数
  • 调用服务提供者的Component.isExchangeWithConsumerOkay()方法,提供属于这个选择过程的消息交换和候选选端点作为该方法的参数。

一个可以使得上述两个调用返回为“真”的候选服务提供者端点被认为是匹配的、适用的隐式选择服务提供者端点。实现必须使用指定的方式选择一个提供者端点,作为从所有匹配的提供者端点中选择的结果。一个实现选择其找到的第一个匹配的服务提供者端点可能会导致整个选择过程“短路”。

1.4.3.3 消息交换路由和服务连接(Message Exchange Routing and Service Connections)

服务集合可能包含提供部署信息的路由信息,称为服务连接和服务链接类型。服务连接提供从消费者给出的地址到实际提供者服务端点之间的显式映射。服务链接类型提供服务消费者组件希望获得的附加信息,JBI实现使用服务连接路由消息交换。

服务链接逻辑上被定义为包含以下属性的结构:

  • 消费者地址。服务消费者提供的地址。有两种形式:
    • 服务类型名称。消费者使用接口名称指定希望获得的服务类型。
    • 服务端点名称。消费者使用限定名(服务QName和端点名称)指定希望获得的服务端点。
  • 提供者端点地址。实际的服务提供者的地址,将交换发送给消费者,可以是一个内部端点或者解析过的端点引用(EPR)。

当一个消费者使用一项特定的服务时,可能在服务单元元数据中声明它对服务连接如何被提供的期望。此处用术语“提供者链接”来表示。共有三种可能的提供者链接类型,当且仅当消费者的地址是一个服务端点名称的时候才提供:

  • 标准链接。NMR执行正常路由,包括使用服务连接。
  • 硬链接。消费者一般直接指定提供者的端点,路由不受服务连接的影响。换句话说,消费者要求给定的端点地址必须是一个内部端点。
  • 软链接。消费者使用一个间接地址指定提供者端点。此时消费者提供一个“假”(并不存在的)端点。这种类型的链接是消费者的一个声明:给定的地址必须使用一个服务连接来解析。如果该端点是一个内部端点的话就会引发错误。

服务单元在它们的部署描述符中声明链接类型。JBI实现必须标记消息交换,当组件发送新的消息交换实例到传输通道时服务调用的链接类型是可用的。

如果一个新的消息交换指定了一个端点,当调用DeliveryChannel.send时,JBI实现必须进行以下几步:

  • 如果端点指定了一个内部端点且链接类型是“标准链接”或“硬链接”,发送交换实例至给定的端点。
  • 否则,如果端点指定了一个服务连接的消费者端点且链接类型是“标准链接”或“软链接”,发送交换实例至服务连接指定的提供者端点。
  • 再则,抛出一个MessagingException异常,在消息中指示错误类型:
    • 软链接时指定内部端点地址。
    • 硬链接时指定服务连接地址。

注意,实现只能使用和当前运行的服务集合相关联的服务连接。

当使用隐式端点选择时,实现必须应用服务连接选择端点。(例如,指定一个服务类型或服务名称而不是一个端点)。

1.4.4 使用动态端点引用 (Use of Dynamic Endpoint References)

动态端点引用(EPR)在5.1.7节给出定义。最终由绑定组件负责解析接收到的EPR,因为EPR包含了特定的绑定信息。由于同样的原因,创建EPR也是绑定组件的职责。

EPR使用专门的XML词汇,这些词汇也是特定的绑定信息所使用的。解析收到的EPR或创建一个新的EPR(服务引擎)的组件不需要“理解”EPR词汇,但并不是禁止这样做。在服务引擎内部创建特定的EPR处理操作一般说来是不明智的举动,因为它在引擎和某种特定的协议之间引进了紧耦合,降低了系统的可配置性。

EPR可以为内部端点创建。有两种类型的内部端点,它们由JBI实现进行不同的处理:

  • 提供服务引擎的端点。这种端点的EPR必须使用JBI定义的EPR词汇来提供引用。(见5.5.4.1)。
  • 提供绑定组件的内部端点。这种端点的EPR如果可能的话应引用外部提供者端点,否则使用JBI定义的EPR词汇。

EPR可以为外部端点创建。如上所述,一个内部(绑定)端点作为一个外部服务的代理,如果可能的话应该直接引用外部提供者的端点。

对被绑定组件暴露在外部的端点(为外部消费者使用),组件需要为这些外部端点创建EPR引用。这些外部端点可以被其他组件通过特定的查询方式发现。由于这个原因,绑定组件创建并注册这些外部端点。

1.4.4.1 实现绑定的端点(Binding-implemented Endpoints)

绑定组件需要提供对ServiceEndpoint接口的实现,两种EPR相关的情况:

· 外部端点。绑定组件为它创建的外部端点(由外部服务消费者访问)注册。这个外部端点作为一个代理允许外部消费者使用一个外部服务。为外部端点提供绑定的ServiceEndpoint实现必须做到以下几点:

  • getInterfaces()返回端点实现的接口QName的一个数组。
  • getServiceName()返回与内部提供者同名的服务。
  • getEndpointName()返回一个合法的端点名称,该名称必须与内部提供者端点名称不同。
  • getAsReference()返回一个端点引用,能与绑定组件交流的外部服务消费者可以使用该饮用发送请求至绑定组件。

外部端点注册的唯一目的是提供到这些端点的引用。它们不能用来处理消息交换实例。如果一个组件处理一个外部端点的消息交换实例,DeliveryChannael.send方法将抛出一个异常。

· 内部端点。绑定组件解析一个端点引用。组件创建的端点解析EPR指示的服务端点。这种类型的端点可以用来处理一个消息交换实例。绑定组件的ServiceEndpoint实现必须做到以下几点:

  • getServiceName()和getEndpointName()返回一个合适的、充分认证的服务名称,这个名称在EPR引用的外部端点中是独一无二的。这里“合适”表明满足以下任一点:
    • 服务名称作为解析过的EPR的一部分。
    • 服务名称在绑定组件中是独一无二的。
  • getAsReference()返回空。如果端点需要一个EPR,使用最初的EPR。

JBI实现必须保证,EPR解析创建的内部端点处理过的消息交换,被传输到创建那个内部端点的组件。组件实现必须区分JBI提供的ServiceEndpoint和组件自己创建的ServiceEndpoint。一个端点实现的例子如下所示:

class MyServiceEndpoint implements ServiceEndpoint {
private DocumentFragment epr_;
private QName serviceName_;
private String endpointName_;
private QName interfaceName_;
MyServiceEndpoint(DocumentFragment epr) 
{
epr_ = epr;
// a real binding component would pick apart the EPR, and
// determine appropriate service description items. This
// binding just makes up a fake description for the
// dynamic endpoint.
serviceName_ = new QName("http://www.example.com/services",
"service1");
endpointName_ = "endpoint1";
interfaceName_ = 
new QName("http://www/example.com/interfaces",
"interface1");
}

public QName getServiceName() {
return serviceName_;
}

public String getEndpointName() {
return endpointName_;
}

public DocumentFragment getAsReference(QName operation) {
return epr_;
}

public QName[] getInterfaces() {
return new QName[] { interfaceName_ };
}

}

1.5 NMR API(NMR API)

NMR为服务引擎和绑定组件提供一个标准的API。

1.5.1 消息API(Message API)

消息API定义了JBI系统中规格化消息的内容和行为。该API的主要功能包括:

  • 使用一个标准数据模型(XML)存储/检索内容
  • 在每个消息的基础上set/get上下文信息(属性)

图19 消息API类图

1.5.1.1 规格化消息(Normalized Message)

一个规格化的消息由以下几部分组成:

  • XML“有效载荷”文件,在抽象消息定义中定义。即规格化消息的内容。
  • 消息属性(元数据),与消息本身相关联。一些属性是JBI定义的,另一些属性可以是组件定义的。
  • 消息的二进制附件。

1.5.1.1.1 规格化消息内容(Normalized Message Content)

规格化的消息内容(有效载荷)是一个XML文件,在服务描述中定义(一般用XML Schema1.0)。为了提供处理XML的灵活度,规格化消息使用JAX-P API中的TraX Sourcej接口。在图19中描述,在包javax.xml.transform中。

规格化消息内容必须和抽象消息定义模型相一致,这个模型在服务提供者提供的服务描述中给定。

规格化消息的生产者必须使用NormalizedMessage.setContent()方法设置合适的XML资源以访问有效载荷文件的内容。规格化消息的消费者应使用NormalizedMessage.getContent()方法,按需求消费有效载荷文件的内容。

1.5.1.1.2 规格化消息附件(Normalized Message Attachments)

消息的附件用一个Java活动框架数据处理器(Java Activation Framework DataHandler)表示。NormalizedMessage接口包含和消息相关的增加、删除和列表方法。一个消息可能包含任意个附件。

带有附件的规格化消息的生产者负责把一个本地消息的内容映射为带附件的规格化消息。规格化消息的消费者负责解析消息的XML内容并处理相关的附件。生产者和消费者依赖消息的WSDL定义确定规格化消息内容的封装结构。

WS-I附件概述1.0(WS-I Attachments Profile1.0(AP)) [WS-I AP]和二进制XML优化包(XML-binary Optimized Packaging(XOP))[W3C XOP]是本领域的两个Web服务标准。下一小节给出在规格化消息内容的XML体中使用这些标准引用附件的处理规则。

1.5.1.1.2.1 WS-I Attachments Profile1.0的处理规则(Processing Rules for WS-I Attachments Profile 1.0)

  • 在消息内容的XML体中,每个附件都必须被一个swaRef元素/属性引用。
  • 在swaRef元素/属性中指定的cid必须与封装在附件内容中的数据处理器的id相匹配。

使用带附件的SOAP详见[WS-I AP]

1.5.1.1.2.2 W3C XML-binary Optimized Packaging (XOP)的处理规则(Processing Rules for W3C XML-binary Optimized Pachaging)

  • 每个附件必须被一个XOP引用:包括消息内容中的XML体元素。
  • Href属性值在一个XOP中指定:包括元素必须与封装在附件内容中的数据管理器id相匹配。

使用XOP详见[W3C XOP]。

1.5.1.1.3 规格化消息属性(Normalized Message Properties)

下表总结了规范定义的规格化消息的标准属性:

名称
类型/值
描述
javax.jbi.security.subject
javax.security.auth.Subject
JAAS安全主题,负责消息的签名信息。
javax.jbi.messaging.protocol.type
java.lang.String
创建消息的绑定组件的协议类型名(使用WSDL2.0绑定类型属性值)
javax.jbi.messaging.protocol.headers
java.util.Map
名称/值,指定绑定组件使用的协议。名称必须为String型;值为绑定组件定义的类型。(对SOAP headers,值为org.w3c.dom.DocumentFragment节点)。

表 4 标准规格化消息属性

注意,javax.jbi.messaging.protocol.type和 javax.jbi.messaging.protocol.headers的属性是由绑定组件设置的。这些属性值一般只有绑定组件自己使用,因为它们包含特定的绑定信息。其他组件可能使用这些属性,不过这是不明智的举动,因为它将导致组件和某种特定的协议绑定。

1.5.1.1.4 WSDL 1.1定义内容的消息(WSDL1.1-Defined Content Messages)

WSDL1.1使用术语消息部件定义抽象消息,消息部件用模型语言描述(一般用XML Schema)。这与WSDL2.0和JBI使用的规格化消息模型截然不同。为了允许提供用WSDL1.1定义服务的组件可以同消费者交互操作,JBI为封装WSDL1.1消息部件定义了一种XML文件格式。使用这种清楚的映射,可以把用来规格化内容的消息,消费者和提供者可以交互。

一个封装的文字化的文件须符合下面所示的模型。

default namespace jbi = "http://java.sun.com/xml/ns/jbi/wsdl-11-wrapper"
start =
element message {
attribute version { xsd:decimal },
attribute type { xsd:QName },
attribute name { xsd:nmtoken }?,
part*
}
part =
element part {
# part value
( (element* { text }) | text)
}

这个封套须在portType(抽象)操作中使用。(wsdl:input,wsdl:output,或wsdl:fault)文件中的元素<jbi:message>是这种消息所有部件的封套。它具有以下几个属性和元素:

  • version属性,必须是1.0。
  • type属性,包含在封套中的WSDL1.1消息定义的限定名。与wsdl:input,wsdl:output,或wsdl:fault消息属性值相关。
  • name属性,与规格化消息的wsdl:input,wsdl:output,或wsdl:fault可选名称属性相关。如果这个属性没有提供,将应用WSDL1.1默认的命名规则。
  • 零或多个<jbi:part>元素,表示WSDL1.1描述的消息的逻辑部件。消息的所有逻辑部件必须在此处显示,与在WSDL1.1消息描述中定义的顺序一致。

每个<jbi:part>元素必须包含WSDL1.1定义的消息的逻辑部分的值。<jbi:part>元素可能包含XML元素或非XML文本。

转换成封装的文字形式,WSDL1.1定义的消息的规格化的消息内容也必须符合基于WSDL2.0的规格化消息内容的要求。(例如被表示为一个javax.xml.Source)。

1.5.1.1.5 消息属性(Message Properties)

消息属性是与消息内容相关的元数据。以下是本规范定义的属性及可能用到的组件:

  • 安全主题。该属性表明JAAS主题的身份,或者为一个消息签名,或者为一个主题签名,由处理的上下文决定。getSecuritySubject()和setSecuritySubject()用来读写该规格化消息的属性。
  • 名称/值属性对。一个可扩展的属性集,组件可以为使用其它组件或其自身设置属性。getProperty()和setProperty()用来读写属性设置;getPropertyNames()可被用来发现所有定义的属性名称。

1.5.1.1.6 消息附件(Message Attachments)

消息的附件可表示为一个具名实体集,由Java活动框架处理器(Java Activation Framework handlers)处理。以下操作可被组件执行:

  • 使用唯一的名称给规格化消息添加附件。
  • 使用唯一的名称给规格化消息移除附件。
  • 列表显示规格化消息的所有附件的名称。
  • 使用唯一的名称从规格化消息处获得一个附件。

附件内容必须表示为javax.activation.DataHandler对象。

1.5.2 服务API(Service API)

此API允许JBI组件与NMR交互。

图20 NMR API类图

1.5.2.1 传输通道(DeliveryChannel)

JBI组件和NMR通过DeliveryChannel接口进行交互。传输通道使得组件可以:

  • 接收和发送消息
  • 创建消息交换工厂。

1.5.2.1.1 消息交换工厂API(Message Exchange Factory API)

所有的消息交换实例使用消息交换工厂的实例来创建。使用组件的传输通道,通过以下三种方式之一可以创建消息交换工厂:

  • createMessageExchangeFactory(ServiceEndpoint)创建一个工厂,设置所有创建的交换实例的endpoint属性为给定的端点。当处理显式选择的端点或动态解析的端点时是很有用的。
  • createMessageExchangeFactory(QName interfaceName)创建一个工厂,设置所有创建的交换实例的interfaceName属性为给定的接口名称。
  • createMessageExchangeFactory()创建一个工厂,设置消息交换的所有属性都为默认值(null或空)。
  • createMessageExchangeFactoryForService(QName serviceName)创建一个工厂,设置工厂创建的所有交换实例的service属性为给定的服务名称。

1.5.2.1.2 创建消息交换(Creating Message Exchanges)

MessageExchangeFactory提供三种不同的方法创建新的消息交换实例,支持三种不同的需要:

  • createExchange(serviceName,operationName)根据服务名称和操作名称创建。允许组件快速装配一个消息交换实例,不用检查服务元数据中的MEP。
  • createExchange(URI)根据MEP URI创建。允许支持MEP的扩展。
  • createInOnlyExchange()等。使用指定MEP的方法创建。在编码时确定了MEP需求时使用此方法便于创建标准消息交换。

1.5.2.1.3 发送消息交换(Sending Message Exchanges)

DeliveryChannel提供两种基本方式发送消息交换:

  • Send(exchange).异步消息传输:此方法不会阻塞。交换可和调用那个send()的线程并发执行。
  • SendSync(exchange).同步消息传输;此方法必须阻塞当前执行的线程直到消息交换返回。

后者用一个变量存放超时参数。JBI组件可以使用简单同步消息交换而不用在消息交换没有按预期返回时管理“粘滞”线程。为支持超时特点,sendSync()方法返回一个布尔值来表示消息交换实例是否成功返回。如果第一个超载,sendSync(exchange)一般返回真。如果第二个超载,sendSync(exchange,long)返回假,消息交换被设置为ExchangeStatus.ERROR状态。如果一个提供者组件试图发送一个这样一个消息交换(例如一个因sendSync()超时而失败的消息交换),实现必须停止调用send()并抛出一个相关的MessagingException异常。

当一个消息交换实例使用一种sendSync方法发送时,JBI实现设置消息交换实例的以下属性:

Javax.jbi.messaging.sendSync.

这个属性

注意,在异步消息交换的情况下,无法保证被接收的消息交换 (通过接收者调用accept(),下节讨论)就是最初发送的同一个对象。相反,唯一的保证是消息交换的ID必须是相同的。

1.5.2.1.4 接收消息交换(Rreceiving Message Exchanges)

DeliveryChannel提供两种方式接收消息交换:

  • accept().此方法在从NMR接收到一个消息交换实例之前是阻塞的。
  • accept(timeoutMS).此方法作用类似accept(),当超过给定的时间之后,方法返回null,表明没有接收到任何消息。

消息接收一般符合“拉”模型:JBI组件用自己的执行线程从传输通道把消息交换实例“拉”出来。组件可能会使用不止一个这样的线程做到并发消息接收。JBI实现可满足一个组件以任意的顺序发出的多个accept()请求。

1.5.2.1.5 关闭传输通道(Closing Down a DeliveryChannel)

组件可调用一个传输通道deactive所有的服务端点:

  • close().此方法关闭所有组件激活的活动端点。
  • deactivateEndpoint().调用此方法作用同上。

一旦传输通道这样关闭就不能再次开放了。相反,要想使组件继续和NMR交互,需要创建一个新的传输通道实例。

当一个传输通道关闭之后,该通道创建的所有消息交换工厂实例都不能正常工作;所有试图使用这样的工厂创建消息交换实例都将导致抛出一个消息异常,表明相关的传输通道已经关闭。

1.5.2.2 服务描述SPI(Service Description SPI)

使用组件接口,组件使用getServiceDescription(ServiceEndpoint)方法提供服务相关的元数据。方法返回一个DOM文件,给出某个端点提供的服务的WSDL描述。如果端点不是激活的或者端点不是由该组件提供的,getServiceDescription(ServiceEndpoint)方法返回null。

该DOM文件可以是WSDL2.0兼容的或者WSDL1.1兼容的,有以下附加属性:

  • 文件不能使用<wsdl:import>或<wsdl:include>元素。(文件必须是单独的)。
  • 服务引擎提供的端点必须使用在“JBI服务引擎WSDL绑定”一节定义的绑定类型。

文件必须提供指定服务的完整定义和端点名称,包括服务接口和操作。(当然也包括使用的消息交换模式)。

1.5.2.3 消息交换模式APIs(Message Exchange Pattern APIs)

下图显示了所有内嵌的MEP的消息交换模式API:

图21 MEP API类图

所有的消息交换实例都必须由消息交换工厂(MessageExchangeFactory)的实例提供的工厂来创建。

消息交换实例的拥有者可以是起始组件、服务或NMR。创建或接收一个消息交换实例就等于把所有权授给创建(或接收)的那个组件。一旦对某个消息交换实例调用send()方法,组件就立即交出了它对这个消息交换实例的所有权。

sendSync()在调用该方法的过程中交出其对消息交换实例的所有权。当调用过程结束(返回真),组件恢复对该消息实例的拥有权。

对一个消息交换实例的拥有权意味着拥有者可以读写这个消息交换实例。如果一个组件(或NMR)对一个消息交换实例不具备拥有权,就不能读写这个实例,但是可以读取消息交换的状态,因为状态的访问可以不必考虑拥有权问题。如果一个组件不是当前消息交换实例的拥有者,而试图对该实例调用任何方法(除了getStatus()),就会抛出一个相应的java.lang.IllegalStateException异常。

1.5.2.3.1 消息交换(MessageExchange)

父接口MessageExchange,提供一般方法给实现某个特定消息交换模式的API。MessageExchange实例为一串规格化消息交换和状态交换提供上下文环境,可被看作一个带状态的规格化消息的容器。

每个消息交换实例都可以通过MessageExchange API访问以下特征:

  • 状态。ExchangeStatus在一个消息交换实例生命周期中提供其状态的可能值。(安全类型枚举(type-safe enumeration))。一个新消息交换实例在其生命周期内是激活的,直到状态转变为DONE或ERROR。可分别通过getStatus()和setStatus()来获得和设置该状态。
  • 出错状态源。实例可保留一个处理错误的根源(Java异常)。见getError()和setError()。
  • 交换ID。每个MessageExchange实例必须拥有一个JBI实现赋予的唯一的标识字符串。JBI组件可以用这个标识把异步的响应和初始的请求关联起来。
  • 交换模式。每个MessageExchange实例拥有一个交换模式标识。标准消息交换模式由WSDL2.0赋值的URI来标识。
  • 交换角色。当一个组件创建或接收某个消息交换实例,它必须认识到自己在交换中的角色。在简单交换模式中可以使用交换的状态、消息和故障等特点很容易地确定组件的角色。在复杂的交换模式下,组件使用getRole()方法可以更容易地确定其在交换中的角色。
  • 服务名称。交换调用的服务的限定名。
  • 操作名称。交换调用的操作的限定名。
  • 服务端点名称。服务提供者端点可选的限定名。(服务名称加端点名称)。
  • 规格化消息。交换实例相关的规格化消息的工厂。提供一些方法来创建、设置和查询这些消息。见createMessage(),setMessage()和getMessage()。
  • 故障信息。交换实例相关的故障消息的工厂。提供一些方法来创建、设置和查询这些消息。见createFault(),setFault()和getFault()。
  • 事务上下文。如果事务的上下文非空(non-null),执行该交换。见getProperty()和setProperty()。事务由常量MessageExchange.JTA_TRANSACTION_PROPERTY_NAME来命名。
  • 属性。一些非JBI的消息交换特点可以作为属性存储:名称-值对,值(对JBI来说)是一个不透明的java.lang.Object。

MessageExchange API提供足够的功能支持所有标准MEP。然而,在API层次上并不区分“in”和“out”消息,使得理解Java编码层的消息交换变得困难。为解决这个问题,提供了一套特定的MEP API,在以下四节给出详细介绍。

1.5.2.3.2 InOnly消息交换(InOnly Message Exchange)

这个子接口为“http://www.w3.org/2004/08/wsdl/in-only”MEP提供API。

提供以下特定的MEP方法:

  • setInMessage(msg).只能调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。
  • getInMessage()。可被多次调用。如果setInMessage()方法(或setMessage(msg,“in”) )没有被调用,返回null.

1.5.2.3.3 InOptionalOut消息交换(InOptionalOut Message Exchange)

这个子接口为“http://www.w3.org/2004/08/wsdl/in-opt-out”MEP提供API。

提供以下特定的MEP方法:

  • setInMessage(msg).只能由服务消费者调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。
  • getInMessage().可被多次调用。如果setInMessage()方法(或setMessage(msg,“in”) )没有被调用,返回null.
  • setOutMessage(msg). 只能由服务提供调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。在此模式中设置“out”消息是可选的。
  • getOutMessage().可被多次调用。如果setOutMessage()方法(或setMessage(msg,“out”) )没有被调用,返回null.

1.5.2.3.4 InOut消息交换(InOut Message Exchange)

这个子接口为“http://www.w3.org/2004/08/wsdl/in-out”MEP提供API。

提供以下特定的MEP方法:

  • setInMessage(msg). 只能由服务消费者调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。
  • getInMessage().可被多次调用。如果setInMessage()方法(或setMessage(msg,“in”) )没有被调用,返回null.
  • setOutMessage(msg). 只能由服务提供调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。
  • getOutMessage().可被多次调用。如果setOutMessage()方法(或setMessage(msg,“out”) )没有被调用,返回null.

1.5.2.3.5 RobustInOnly消息交换(RobustInOnly Message Exchange)

这个子接口为“http://www.w3.org/2004/08/wsdl/robust-in-only”MEP提供API。

提供以下特定的MEP方法:

  • setInMessage(msg). 只能由服务消费者调用一次,否则再次调用会导致实现抛出一个相应的MessagingException异常。
  • getInMessage().可被多次调用。如果setInMessage()方法(或setMessage(msg,“in”) )没有被调用,返回null.

1.5.2.3.6 WSDL1.1消息交换属性(WSDL1.1 Message Exchange Properties)

WSDL1.1允许对操作名称的重载,在一个请求-响应消息交换(in-out消息交换模式)情况下,需要哪个正确的操作不能简单地由检查请求消息的类型来决定。这种情况下,服务消费者应使用以下几个消息交换属性说明需要哪个操作:

属性名(Property Name)
属性值类型(Property Value Type)
描述(Description)
javax.jbi.messaging.wsdl-11.output-type
javax.xml.namespace.QName
期望的操作输出消息类型
javax.jbi.messaging.wsdl-11.output-name
Java.lang.String
期望的操作输出消息名称
javax.jbi.messaging.wsdl-11.fault-types
Javax.xml.namespace.QName[]
操作的故障类型
javax.jbi.messaging.wsdl-11.fault-names
Java.lang.String[]
故障名称,以故障类型顺序显示

表5 WSDL1.1消息交换属性

一个消费者使用上述的属性提供所有可用的信息来解决模糊性。如果消费者无法自己解决模糊性(提供一个或多个属性),消费者发送消息交换并允许服务提供者来解决这种模糊性。

例如,一个作为服务消费者的SOAP绑定组件,可能从一个外部服务消费者处接收到一个请求服务名称为“foo”的操作。WSDL1.1定义的foo包括:

<definitions xmlns ="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns ="http://www.example.com/xmlsvr"
targetNamespace ="http://www.example.com/xslsvr">
<types>...</types>
<message name="DoFooInput">...</message>
<message name="DoFooOutput1">...</message>
<message name="DoFooOutput2">...</message>
<portType name="FooPortType">
<operation name="Foo">
<input name="foo-in" message="tns:DoFooInput"/>
<output name="foo-out1" message="tns:DoFooOutput1"/>
</operation>
<operation name="Foo">
<input name="foo-in" message="tns:DoFooInput"/>
<output name="foo-out2" message="tns:DoFooOutput2"/>
</operation>
</portType>
</definitions>

操作“Foo”具有模糊性。如果SOAP组件想要使用一个上述定义的内部服务,必须使用以下几种策略的一种来解决模糊性。

  • 设置InOut消息交换的"javax.jbi.messaging.wsdl-11.output-type" 属性为以下任一种tns::DoFooOutput1 或 tns:DoFooOutput2, 分别选择第一种或第二种Foo操作。
  • 设置InOut消息交换的"javax.jbi.messaging.wsdl-11.output-name" 属性为以下任一种:“foo-out1”或 “foo-out2”, 分别选择第一种或第二种Foo操作。
  • 设置InOut消息交换的非附加属性。相反,消费者依赖提供者来选择使用哪一种“Foo”操作。

绑定组件根据特定的实现信息选择使用哪一种策略。(如SOAP活动headers或附加配置信息)。

1.5.3 查找激活端点(Querying Activated Endpoints)

服务消费者可使用组件上下文(ComponentContext)查找激活端点。(如何使用此接口详见“框架”一章)。查找可以通过多种方式进行,但大多数返回一个服务端点(ServiceEndpoint)对象数组。这个数组可能包含零个或多个端点;消费者必须做好准备处理没有可用的服务(零个端点)情况和多个端点访问同一类型的服务的情况。查找方法如下:

  • getEndpoint().方法返回一个特定的激活端点,该端点与提供的服务名称或端点名称相匹配,如果没有匹配的则返回null.
  • getEndpoints(QName interfaceName).方法返回所有实现给定的接口/端口类型名称(interface/portType)的激活端点。接口名称参数缺省时方法返回所有的激活端点。
  • getEndpointsForService(QName serviceName).方法返回所有属于给定服务名称的激活端点。

1.5.4 端点引用API(Endpoint Reference API)

端点(内部或外部)在5.1.7节中定义,使用端点引用(EPR)在5.4.4节定义。本节定义了JBI中处理EPR所需的API。

EPR由组件创建和解析。JBI实现使得解析、创建、使用EPR的组件之间可以交互。

组件解析接收到的一个EPR时需要调用ComponentContext.resolveEndpointReference().

组件(一般是绑定组件)解析一个它理解的EPR时需要实现

Component.resolveEndpointReference().

提供可被EPR引用的外部端点的绑定组件使用ComponentContext.registerExternalEndpoint()方法注册该端点,表明此外部端点是激活的。使用deregisterExternalEndpoint()表明外部端点不再是激活的,并在调用ComponentContext.getExternalEndpoints()的结果中不再显示该端点。

用来生成EPR的组件首先选择引用哪一个外部端点。ComponentContext提供两种方法查找JBI系统中可用的外部端点: 

  • 使用接口名称。getExternalEndpoints()返回一个实现了给定接口的所有注册过的外部端点的数组。
  • 使用服务名称。getExternalEndpointsForService()返回一个在名称服务中包含的所有注册过的外部端点的数组。

例如,假定我们有一个服务引擎SE1,通过一个端点EP1提供服务。一个该服务的消费者是绑定组件BC2,发布一个外部端点EP2供EP1的外部服务消费者使用。(此时BC2作为一个外部消费者的代理)。

在一个用例中,SE1发送一个请求消息给外部服务提供者,消息中提供了指向EP2的回调地址(如,一个EP2的EPR)。目的是让外部提供者作为一个消费者使用EPR发送请求给EP2。

在这种情景中,BC2使用JBI在创建时(部署时期)注册外部端点。SE1可发现外部端点(使用以上的查找方法),并使用该外部端点为发送到外部提供者的请求消息创建一个EPR。(创建EPR的任务交给BC2)。外部提供者使用EPR发送所需的消息给外部端点EP2。

1.5.4.1 内部EPR词汇(Internal EPR Vocabulary)

服务引擎提供的内部端点EPR,如在5.1.7中所定义的,组件可以使用XML Schema来引用EPR。这种类型的EPR仅在JBI系统中有用并可以用来内部和外部提供的服务上。

default namespace jbi = "http://java.sun.com/jbi/end-point-reference"
start =
element end-point-reference {
attribute service-name { xsd:QName },
attribute end-point-name { text },
empty
}

服务引擎的端点生成的端点引用必须符合上表所示的schema。

1.5.5 应用举例(Usage Examples)

本节给出一般绑定组件、服务引擎和NMR交互的详细示例。运行时激活和消息交换部分也将详细讲述。

1.5.5.1 服务消费者(Service Consumer)

这是一个单个JBI系统内的例子。一个服务引擎,消费者引擎——外部Web服务的消费者(例如,对外(outbound)服务调用)。

注意:

  • 服务名称:{http://abc.com/services}:service1
  • 端点名称:soap1
  • 方向:Outbound
  • 操作:updateStatus(单向)
  • 为简单起见,服务引擎和绑定组件为单线程,在整个例子中只参与一个交换。
  • 假定以下常量已经声明:
    • QName SERVICE1 = new QName("http://abc.com/services", "service1");
    • QName OP_UPDATE_STATUS = new QName("http://abc.com/services", "updateStatus");
    • String ENDPOINT1 = “soap1”;

1.5.5.1.1 服务引擎初始化(SE Initialization)

在一个组件的第一个“开始”之前,调用“init”方法进行启动前初始化。本例中,服务引擎、传输通道和消息交换工厂已经准备好了。

class ConsumerEngine implements Component, ComponentLifeCycle,
{
DeliveryChannel channel;
MessageExchangeFactory factory;
public void init(ComponentContext context)
throws JBIException
{
// Obtain reference to delivery channel
channel = context.getDeliveryChannel();
// Create our ME factory; we use a single factory for all exchanges
factory = channel.createMessageExchangeFactory();
}

// ...
}

1.5.5.1.2 绑定组件初始化(BC Initialization)

在一个组件的第一个“开始”之前,调用“init”方法进行启动前初始化。本例中,同服务引擎一样,组件创建好一个传输通道和消息交换工厂。

class ConsumerEngine implements Component, ComponentLifeCycle,
{
DeliveryChannel channel;
MessageExchangeFactory factory;
public void init(ComponentContext context)
throws JBIException
{
// Obtain reference to delivery channel
channel = context.getDeliveryChannel();
// Create our ME factory; we use a single factory for all exchanges
factory = channel.createMessageExchangeFactory();
}

// ...
}
class SoapBinding implements Component, ComponentLifeCycle {
DeliveryChannel channel;
ServiceEndpoint serviceEndpoint;
public void init(ComponentContext context)
throws JBIException {
// Obtain reference to delivery channel
channel = context.getDeliveryChannel();
// Activate endpoint names
serviceEndpoint = context.activateEndpoint(SERVICE1, ENDPOINT1);
}

public Document getServiceDescription(ServiceEndpoint ref) {
Document result = 
null;
// return WSDL describing services offered by me through the given
// service reference.
if (ref.getServiceName.equals(SERVICE1) &&
ref.getEndpointName.equals(ENDPOINT1) ) 
{
//... create or find the WSDL
}

return result;
}

// ...
}

1.5.5.1.3 消息交换(Message Exchange)

从服务引擎的角度

void someEngineMethod()
{
InOnly inOnly;
Message message;
// As initator, we get to create the message exchange
inOnly = factory.createInOnlyExchange();
// Create new message for this exhange.
message = inOnly.createMessage();
// Populate message with data (omitted)
// Set message as in reference in exchange
inOnly.setInMessage(message);
// Set operation and service details
// (We could avoid this by using channel.createExchange(SERVICE1,
// OP_UPDATE_STATUS); above)
inOnly.setOperation(OP_UPDATE_STATUS);
inOnly.setService(SERVICE1);
// We will allow the NMR to select the endpoint automatically.
// Alternatively, we could query the available endpoints implementing SERVICE1,
// choose from that list, and use inOnly.setEndpoint() to route the ME ourselves.
// Initiate the message exchange
channel.send(inOnly);
}

从绑定组件的角度

void someBindingMethod()
{
MessageExchange exchange = channel.accept();
if (exchange instanceof InOnly)
process((InOnly) exchange);
else // ...
}

void process(InOnly inOnly)
{
NormalizedMessage inMsg;
// fetch "in" message from exchange
inMsg = inOnly.getInMessage();
// process data
try
{
// commit message to wire (omitted)
// set status appropriately
inOnly.setStatus(ExchangeStatus.DONE);
}

catch (Exception ex)
{
// Implicitly sets inOnly status to ExchangeStatus.ERROR
inOnly.setError(ex);
}

channel.send(inOnly);
}

1.5.5.2 服务提供者(服务者)(Service Provider[Servicer])

这是一个单个JBI系统内的例子。一个服务引擎,提供者引擎——通过一个SOAP绑定组件提供一个面向外部消费者的服务(例如,对内(inbound)服务调用)。
注意:

  • 服务名称:{http://xyz.com/services}:service2
  • 端点名称:engine2
  • 方向:Inbound
  • 操作:getStockQuote(请求-响应)
  • 为简单起见,服务引擎和绑定组件为单线程,在整个例子中只参与一个交换。
  • 假定以下常量已经声明:
    • QName SERVICE2 = new QName("http://xyz.com/services", "service2");
    • String ENDPOINT2 = “engine2”;

1.5.5.2.1 绑定组件初始化(BC Initialization)

class SoapBinding implements Component, ComponentLifeCycle {
DeliveryChannel channel;
MessageExchangeFactory factory;
public void init(ComponentContext context)
throws JBIException {
// Obtain reference to delivery channel
channel = context.getDeliveryChannel();
// Create single ME factory; we will use it for creating all exchanges needed
factory = channel.createMessageExchangeFactory();
// Create listener for inbound SOAP messages
// (omitted)
}

}

1.5.5.2.2 服务引擎初始化(SE Initialization)

class ProviderEngine implements Component, ComponentLifeCycle {
DeliveryChannel channel;
ServiceEndpoint serviceEndpoint;
public void init(ComponentContext context)
throws JBIException
{
// Obtain reference to delivery channel
channel = context.getDeliveryChannel();
// Activate service endpoint (SEVICE2,ENDPOINT2)
serviceEndpoint = context.activateEndpoint(SERVICE2, ENDPOINT2);
}

public Document getServiceDescription(ServiceEndpoint ref{
Document result = 
null;
// return WSDL describing services offered by me through the given
// service reference.
if (ref.getServiceName.equals(SERVICE2) &&
ref.getEndpoint Name.equals(ENDPOINT2) ) {
//... create or find the WSDL
}

return result;
}

// ...
}

1.5.5.2.3 消息传输(Message Delivery)

从绑定组件角度

void someBindingMethod() {
InOut inOut;
NormalizedMessage inMsg;
// Receive message over native protocol (omitted)
// Create InOut exchange
inOut = factory.createInOutExchange();
// Create new message
inMsg = inOut.createMessage();
// Normalize native protocol data (omitted)
// Set message as in reference in exchange
inOut.setInMessage(inMsg);
// Set operation and service details
inOut.setOperation(new QName(SERVICE2.getNamespaceURI(), "getStockQuote"));
inOut.setService(SERVICE2);
// Initiate the message exchange and wait for response
channel.sendSync(inOut);
process((InOut) inOut);
}

void process(InOut inOut) {
NormalizedMessage outMsg = inOut.getOutMessage();
if (inOut.getStatus() != ExchangeStatus.ACTIVE) {
// error occurred. Return error response in native wire protocol (omitted)
}
 else {
// commit response message to native wire protocol (omitted)
// Tell provider that the exchange is now complete.
inOut.setStatus(ExchangeStatus.DONE);
channel.send(inOut);
}

}

从服务引擎角度

void someEngineMethod() {
MessageExchange exchange = channel.accept();
if (exchange instanaceof InOut)
process((InOut) exchange);
else // ...
}

void process(InOut inOut) {
NormalizedMessage inMsg;
NormalizedMessage outMsg;
// fetch in message
inMsg = inOut.getInMessage();
// process data
try {
// perform appropriate processing to inMsg (omitted)
// create response message
outMsg = inOut.createMessage();
// populate message content (omitted);
// attach message to exchange
inOut.setOutMessage(outMsg);
}

catch (Exception ex) {
// Implicitly sets inOut status to ExchangeStatus.ERROR
inOut.setError(ex);
}

channel.send(inOut);
}

1.5.5.3 解析动态端点引用(Resolution of Dynamic Endpoint Reference)

下表显示了一个服务消费者的解析EPR的例子。

MessageExchange createExchangeForEPR(DocumentFragment epr, QName operationName)
throws MessagingException, Exception
{
MessageExchange result;
ServiceEndpoint endpoint = context_.resolveEndpointReference(epr);
if (endpoint != null{
result = channel_.createExchangeFactory(endpoint).
createExchange(endpoint.getServiceName(), operationName);
}

else {
throw new Exception("Cannot resolve EPR");
}

return result;
}

1.5.5.4 创建动态端点引用(Creation of a Dynamic Endpoint Reference)

下表显示了一个服务提供者创建EPR的例子。

private DocumentFragment getEpr(QName interfaceName, QName operationName) {
DocumentFragment result = 
null;
ServiceEndpoint[] endpoints = context_.getExternalEndpoints(interfaceName);
if (endpoints.length > 0) {
ServiceEndpoint endpoint = endpoints[0]; 
// simple choice
result = endpoint.getAsReference(operationName);
}

return result;
}

1.5.6 服务提供者元数据(Service Provider Metadata)

每个服务提供者必须实现ComponentgetServiceDescription()方法。当NMR要检索组件提供的服务的元数据时调用该方法,服务必须已被NMR激活。(使用名称)

JBI通过特定的绑定类型对标准WSDL1.1和WSDL2.0作了补充,服务引擎使用特定的JBI绑定类型其提供的服务声明端点。绑定类型将在下一小节定义。

1.5.6.1 JBI服务引擎WSDL绑定(JBI Service Engine WSDL Binding)

JBI1.0服务引擎绑定通过给WSDL绑定组件的type属性赋以下值来标识:

“http://java.sun.com/xml/ns/jbi/binding/service+engine”。

这种绑定类型用来标识服务引擎(如,JBI系统中的本地服务提供者)。它不能定义WSDL操作或消息的映射。使用所有的抽象WSDL定义时都无需修改。

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。

资源网站: UML软件工程组织