`
hsrong
  • 浏览: 35750 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

JBoss Enterprise SOA Platform 4.3 编程指南翻译之三——服务和消息

    博客分类:
  • ESB
阅读更多
3.服务和消息
    基于面向服务的原则,JBoss ESB里的任何事物都是一种服务或者消息。服务封装了业务逻辑以及和旧有系统进行交互的点。消息是客户端和服务进行通信的方式。下面我们会了解是如何支持服务和消息的。

3.1 服务
         JBoss ESB的一个服务就是一系列动作类,这些动作类以一种有序的方式处理消息。这些有序的动作类也可以被称为动作管道。一个服务也可以定义一些监听器,监听器就像是服务的内置路由一样,将消息路由到动作管道。
         下面这个简单配置定义了一个服务,这个服务只是简单的将消息的内容打印到控制台。

<?xml version = "1.0" encoding = "UTF-8"?>
<jbossesb xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product
/etc/schemas/xml/jbossesb-1.0.1.xsd" invmScope="GLOBAL">
<services>
    <service category="Retail" name="ShoeStore" 
        description="Acme Shoe Store Service">
        <actions>
            <action name="println" 
                class="org.jboss.soa.esb.actions.SystemPrintln" />
            </actions>
        </service>
    </services>
</jbossesb>


    例3.1 将消息内容打印到控制台的简单服务例子
    一个服务有目录和名称属性。当JBoss ESB发布这个服务时会使用这些属性以端点的方式在服务注册处注册服务的监听器。客户端可以使用类ServiceInvoker来调用这些服务。

ServiceInvoker invoker = new ServiceInvoker(“Retail”, “ShoeStore”);
Message message = MessageFactory.getInstance().getMessage();
message.getBody().add(“Hi there!”);
invoker.deliverAsync(message);


   实例3.2客户端调用服务
   类ServiceInvoker在服务注册处查询服务“Retail:ShoeStore”可用的端点地址。它负责处理从客户端到某个可用服务端获取消息的整个细节。消息的传递过程对于客户端来说是透明的。
      ServiceInvoker如何获取端点地址主要依赖于服务上配置的监听器列表,比如JMS,FTP或者HTTP。在上面的例子中,服务上没有配置监听器,但是通过配置参数invmScope等于GLOBAL,会使InVM监听器可用。InVM 传输是SOA平台4.3版本的新特性,这个特性可以使运行在相同JVM上的不同服务进行通信。在4.3.3节
“InVM Transport”包含了这个特性的更多信息。
   为了使其他端点可用,你需要明确的为某个服务添加监听器配置。
      JBoss ESB 支持两种形式的监听配置:



   网关监听


   这些监听器配置提供了一个网关端点。这些端点类型提供了你的JBossESB部署外部的消息的进入点。他们还负责在把消息传送给服务动作管道之前,通过将其包装成ESB 消息来规格化消息负载。


  ESB Aware监听


   这些监听器配置提供一个ESB Aware端点。这些端点类型用于在ESB Aware组件间交换ESB消息。
注意:一个ESB消息就是一个org.jboss.soa.esb.message.Message实现。在3.2节
“消息”进行了详细描述。能够处理ESB消息的组件就是一个ESB Aware组件。

   关于服务的端点的配置和服务其他内容的配置是在同一个文件里的。传输级别的细节是通过增加<providers>部分来定义的。然后会通过添加<listener>配置来引用provider。
   接下来的例子,我们会添加一个<jms-provider>部分,用来为Shoe Store JMS Queue定义一个<jms-bus>。这将会在服务的<jms-listener>定义中引用。

<?xml version = "1.0" encoding = "UTF-8"?>
<jbossesb xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product
    /etc/schemas/xml/jbossesb-1.0.1.xsd" invmScope="GLOBAL">
    <providers>
       <jms-provider name="JBossMQ" connection-factory="ConnectionFactory">
              <jms-bus busid="shoeStoreJMSGateway">
                 <jms-message-filter dest-type="QUEUE" 
                    dest-name="queue/shoeStoreJMSGateway"/>
               </jms-bus>
           </jms-provider>
    </providers>
    <services>
        <service category="Retail" name="ShoeStore" invmScope="GLOBAL"
            description="Acme Shoe Store Service">
            <listeners>
                <jms-listener name="shoeStoreJMSGateway" 
                    busidref="shoeStoreJMSGateway" is-gateway="true"/>
            </listeners>
            <actions>
                <action name="println" 
                    class="org.jboss.soa.esb.actions.SystemPrintln" />
            </actions>
    
        </service>
    </services>
</jbossesb>


    实例3.3 在ShoeStore 服务上添加一个JMS网关监听例子

    现在,ShoeStore服务可以通过两个端点获取,即InVM端点和新的JMS网关端点。因为性能原因,如果一个InVM端点可以获取,ServiceInvoker将会尽量使用服务的本地InVM端点而不是其他端点类型。
      
3.1 消息
    所有在JBossESB里发生的客户端和服务之间的交互都是通过消息交换完成的。推荐使用信息交换方式进行开发,因为这种方式有利于松耦合。请求响应应当是独立的消息,根据基础架构和应用的需求在必要时相关。采用这种方式构建应用能够更具有容错性,并且在部署过程和消息传递需求中给开发者更多的灵活性。

    为了确保服务的松耦合和增强SOA应用的健壮性,参考以下建议。
     1.使用单向消息交换而不是请求响应架构
     2.消息交换过程中要遵守定义约定。你应该避免定义一个暴露后端实现选择的服务接口,因为这将使未来变换实现方式十分非常困难。
     3.为消息负载使用一个可扩展的消息结构,以便对消息的更改能够向后兼容。
     4.不要开发过于细颗粒度的服务,因为这将导致应用极度复杂而不能适应改变环境的改变。SOA的范例是一个服务,而不是分布式对象。

    带有请求响应的单向消息传递方式需要消息里包含有响应发送到什么地方的信息。这个信息可能在消息负载中,并且被应用处理,或者作为原始请求的一部分而被ESB框架处理。
    ESB的核心就是消息,消息的结构与SOAP中消息结构是相似的。

<xs:complexType name="Envelope">
<xs:attribute ref="Header" use="required"/>
<xs:attribute ref="Context" use="required"/>
<xs:attribute ref="Body" use="required"/>
<xs:attribute ref="Attachment" use="optional"/>
<xs:attribute ref="properties" use="optional"/>
<xs:attribute ref="Fault" use="optional"/>
</xs:complexType>


实例3.4
    ESB消息


    下图展示了消息的基本结构。在下面的内容里我们会详细地讲解消息的各个部分。



图3.1 消息的基本结构
        Message的结构的UML表示如下:



图3.2 message结构的UML表示



    每个消息都是结构org.jboss.soa.esb.message.Message的一个实现。这个包里包含消息里变量的接口。

public interface Message
{
public Header getHeader ();
public Context getContext ();
public Body getBody ();
public Fault getFault ();
public Attachment getAttachment ();
public URI getType ();
public Properties getProperties ();
}


实例 3.5. org.jboss.soa.esb.message.Message 接口。
    从应用或者服务的观点,消息负载是由消息体、附件和属性的。
    警告:目前,不建议使用附件或者属性。因为这些概念目前还在重新评估,并且在未来版本中很可能会有明显改变。建议你将属性和附件数据放到消息体中使用。
    消息负载的UML表示如下:




图3.3
信息负载的UML表示


    3.2.1 消息头
    消息头包含了以端点引用(EPRs)形式表示的消息的路由和地址信息,还有唯一标识消息的其他信息。JBossESB使用了W3C的基于Ws-Addressing标准的寻址方案。
    消息头和多个EPRs的关系可以用下面的UML表示:

   

图3.4 消息头和多个EPRs关系的UML表示

    在开发和使用服务的时候必须考虑消息头的作用。例如,如果你需要一个基于请求响应的同步交互方式,你会想设置ReplyTo字段,或者默认的EPR。如果你这样做的话,就算使用请求/响应方式,响应也不需要回到初始的发送者。同样,当发送单向消息的时候(没有响应),你不用设置ReplyTo字段,因为它会被忽略。
    你的ReplyTo或者FauletTo EPR应当使用逻辑EPR,而不是物理EPR(例如JMS-EPR)。一个逻辑EPR就是一个指定了名字和目录的ESB服务或者端点。没有包含物理地址信息。
    逻辑EPR之所以优先选择是因为不用对EPR的使用者(通常是ESB本身,但也不一定)的能力做任何设想。一旦EPR的客户端想要调用服务时,可以使用EPR提供的目录或者名字去寻找服务或者端点的物理端点信息。例如,他们会获得相关的地址信息。客户端也可以选择适合他们的物理端点类型。
    注意:一旦消息头在端点间发送就是不能改变的。尽管消息头能通过接口改变,但是JBossESB会忽略这些变化。未来的版本为了避免混淆很有可能禁止使用这些API。在WS-Addressing标准中有关于这些规则的阐述。

public interface Header
{
public Call getCall ();
public void setCall (Call call);
}


例子 3.6. The org.jboss.soa.esb.message.Header 接口

头信息的内容保存在类org.jboss.soa.esb.addressing.Call的一个实例中。

public class Call
{
public Call ();
public Call (EPR epr);

public void setTo (EPR epr);
public EPR getTo () throws URISyntaxException;

public void setFrom (EPR from);
public EPR getFrom () throws URISyntaxException;

public void setReplyTo (EPR replyTo);
public EPR getReplyTo () throws URISyntaxException;

public void setFaultTo (EPR uri);
public EPR getFaultTo () throws URISyntaxException;

public void setRelatesTo (URI uri);
public URI getRelatesTo () throws URISyntaxException;

public void setAction (URI uri);
public URI getAction () throws URISyntaxException;

public void setMessageID (URI uri);
public URI getMessageID () throws URISyntaxException;

public void copy (Call from);
}


例子 3.7. org.jboss.soa.esb.addressing.Call

        org.jboss.soa.esb.addressing.Call支持单向和请求响应两种交互方式。

   
属性类型必须描述
To EPR消息目标接受者的地址
From EPR消息来源端点的引用
ReplyTo EPR标识响应消息的目标接收者的EPR
FaultTo EPR标识接收消息相关的错误的端点引用
Action URI唯一地潜在的标识消息含义的一个标识
MessageId URI依赖唯一标识消息的URI

   
表格 3.1. org.jboss.soa.esb.addressing.Call 属性
        ReplyTo
        ReplyTo属性是一个标识回应消息的目标接受者的EPR。如果希望产生一个回应,消息头中必须包含一个ReplyTo。JBossESB支持各种传输类型的默认ReplyTo的值。这是在要求响应但是没有设置ReplyTo属性的时候使用的。这些默认值需要系统管理者正确的配置JBossESB。
 
传输类型   
ReplyTo
JMS
是一个队列,这个队列的名字由传送原始请求的队列的名字和后缀_reply组成
JDBC
是一个数据库表。
这个表与传送源请求的表在一个数据库。并且表的名字由传送源请求的表和后缀_reply_table组成。两个表有相同的列定义。 
Files   
对于本地和远程文件,不需要做出管理变化。 
响应被写进和请求相同的目录,但是一个唯一的后缀会确保原始的发送者会获取响应。 
表格3.2 传输的默认ReplyTo

    FaultTo
    FaultTo是一个标识消息相关的错误的目标接受者的EPR。在3.2.3节
“The Fault”会详细阐述。
    JBossESB会将消息的任何失败路由到FaultTo属性表示的EPR。如果没有设置FaultTo属性,JBossESB会依次检查ReplyTo和From属性。如果检查所有属性后还不能获得有效地EPR,控制台将会输出错误信息。
   如果发送者不需要接收操作信息,或者你不想任何响应的话,这个属性可以不设置。在这种场景下推荐使用DeadLetter Queue Service EPR作为你的FaultTo,或者在后续处理中发生的失败将会保存。
    MessageID
    MessageID属性是用于唯一标识一条消息的一个URI。两个不同的消息不能有相同的MessageID,一个重新发送的消息还可能会使用原来的MessageID。
   如果需要回复或者设置了ReplyTo和FaultTo之一的话,MessageID必须设置。
      3.2.2  上下文
    上下文中包含了相关的会话信息,比如交易或者安全内容。JBossESB的这个版本不支持用户增强的上下文,那将会是5.0的特性。
      3.2.3 错误
       Fault用于转换error信息。信息保存在Body里

public interface Fault
{
public URI getCode ();
public void setCode (URI code);

public String getReason ();
public void setReason (String reason);

public Throwable getCause ();
public void setCause (Throwable ex);
}


例子 3.8. org.jboss.soa.esb.message.Fault 接口
     3.2.4 消息体
     Body一般包含了消息负载。你能用Body发送任意种的数据类型。在Body内部,不会限制你发送和接受一种数据类型。对象是如何序列化到消息以及如何从消息反序列化到对象主要依赖特定的对象类型。

public interface Body 
{
    public static final String DEFAULT_LOCATION 
    = "org.jboss.soa.esb.message.defaultEntry";
    public void add (Object value);
    public void add (String name, Object value);
    public Object get ();
    public Object get (String name);
    public String[] getNames()
    public void merge (Body b);
    public Object remove (String name);
    public void replace (Body b);
}


例子 3.9.org.jboss.soa.esb.message.Body 接口
重点
    在JBossESB 4.2.1版本中,Body中的字节数据组件不建议使用。如果你想在消息的Boby中继续连同其他数据使用字节数组的话,只需要简单地添加一个唯一的名字。如果你的客户端和服务字节数组的位置达成一致,可以使用ByteBody.BYTES_LOCATION.
    使用消息Body的最简单的方式是使用命名对象。你可以添加,移除和检查消息负载里的单个数据项,而不用去解码所有数据。还有,你可以将消息负载中的命名对象和字节数组结合起来。
    任何类型的对象都可以添加到Body。如果你添加一个不能序列化的对象的话,你必须使JBossESB能够编排和解编排消息里的对象。更多信息请参考3.2.8节“消息工厂”。
    你必须注意Body里的序列化了的对象,因为对于接受者,并不是所有的序列化对象都是有用或者有意义的。例如,如果一个客户端没有连接到数据库服务器的话,接收到数据库连接对象是没有用的。使用消息里的JAVA序列化对象会导致依赖,这有可能限制服务的实现。
    默认的命名对象(DEFAULT_LOCATION)应当慎重使用,以便多个服务或者动作不会重写各自的数据。
    ESB中所有组件(动作,监听器,网关,路由器,通知等)的默认行为就是使用默认的消息负载位置来在消息上获取或者设置数据。
    所有ESB组件使用MessagePayloadProxy来管理消息负载的获取和设置。就像上面所说的,采用默认的处理方式,但是也可以通过一种统一的囊括所有组件的方式来重写处理方式。允许通过下面的组件特性以一种统一的方式来重写获取和设置消息负载的位置:  
get-payload-location:
指的是获得消息负载的位置
set-payload-location:
指的是设置消息负载的位置
注意
JBossESB 4.2.1GA中没有默认的消息负载交换方式。后续版本是可配置的,配置方式是设置 jbossesb.sar中jbossesb-properties.xml 文件的use.legacy.message.payload.exchange.patterns 属性为true。
    3.2.5 消息体扩展
    除了通过字节和键值对的形式外,还可以通过很多可用的接口来操作消息体的内容。这些接口通过提供预定义的消息结构和方法来操作内容。
    这些接口是基本的Body接口的扩展,他们可以联合已有的客户端和服务使用。消息的使用者不必知晓这些新类型,因为消息的底层数据结构没有改变。
    你可以使用XMLMessageFactory或者SerializedMessageFactory类来创建消息,这些消息的消息体实现是依赖上述某个特定接口的。当使用Message而不是MessageFactory及其相关类时,XMLMessageFactory和SerializedMessageFactory类使用起来更方便。
    对于各个Body类型来说,你都能找到一个适合的创建方法。例如,createTextBody允许你创建和初始化一个特定类型的消息。一旦创建完毕,就可以通过使用接口方法或者Body行来直接操作这个消息了。因为消息体在发送之后仍然是可维护的,所以它能够被消息接收方使用创建该消息的接口的方法来操纵了。
    org.jboss.soa.esb.message.body.content.TextBody
    Body的内容是一个任意的字符串,这个字符串可以通过getText和setText方法操作。
    org.jboss.soa.esb.message.body.content.ObjectBody
    Body的内容是一个序列化的对象,可以通过getObject和setObject方法操作。
    org.jboss.soa.esb.message.body.content.MapBody
    Body的内容是一个Map(String, Serialized),可以通过getMap和setMap方法操作。
    org.jboss.soa.esb.message.body.content.BytesBody
    Body的内容是一个包含任意java数据类型的字节流。可以通过该数据类型的方法来操作它。一旦被创建,BytesMessage被置于只读或者只写的模式,这将依赖于你将怎样操作它。你可以通过使用方法readMode()和writeMode()来改变模式,但是每次改变,缓存指针将会重置。所以为了确保更新能应用到Body,你应当调用flush方法。
    3.2.6 附件
    消息中可以包含附件,比如图片,绘画,二进制文档或者压缩文件。附件是不在主要的消息负载中体现的。附件接口支持命名组件和没有匿名组件。当前版本中,附件只能是序列化对象。在后续版本中,这个限制将会被去除。
    由于多种原因需要使用附件。一般是为了提供一个更合理的消息结构。大消息的性能也可以通过在端点间以流的形式传递附件。
    JBossESB不支持对消息和附件使用其他编码机制。再后续版本中,在适当的情况下将会与SOAP的附件传送机制绑定。目前在消息体中,附件和命名对象被处理的方式是一样的。
警告
    目前建议开发者不要使用属性或者附件。因为他们设计的概念还在重新评估并且在未来版本中很可能会有重大改变。建议将属性或者附件的数据包含在消息体重。

public interface Attachment
{
Object get(String name);
Object put(String name, Object value);

Object remove(String name);

String[] getNames();

Object itemAt (int index) throws IndexOutOfBoundsException;
Object removeItemAt (int index) throws IndexOutOfBoundsException
Object replaceItemAt(int index, Object value)
throws IndexOutOfBoundsException;

void addItem  (Object value);
void addItemAt (int index, Object value)
throws IndexOutOfBoundsException;

public int getNamedCount();
}


例子3.10. org.jboss.soa.esb.message.Attachment 接口
    3.2.7 属性
    消息属性定义了关于消息的其他元数据。JBossESB没有使用ava.util.Properties来实现Properties,因为这可能会对要使用的服务和客户端的类型产生限制。Web 服务栈也是因为同样的原因而没有是用java.util.Properties。如果你需要发送java.util.Properties,你可以把它们嵌入到当前的抽象中。
警告
    目前建议开发者不要使用属性或者附件。因为他们设计的概念还在重新评估并且在未来版本中很可能会有重大改变。建议将属性或者附件的数据包含在消息体中。

public interface Properties
{
public Object getProperty(String name);
public Object getProperty(String name, Object defaultVal);

public Object setProperty(String name, Object value);
public Object remove(String name);

public int size();
public String[] getNames();
}


例子 3.11. org.jboss.soa.esb.message.Properties 接口
   3. 2.8  消息工厂
   尽管每一个ESB组件可以处理java对象集合形式的ESB消息集,将这些消息序列化还是有必要的。这些场景可能是将对象保存到数据库,在不同消息处理器之间发送消息或者调试。
   JBossESB是不会为消息序列化强加一个特定标准格式的,因为格式要求将会被每次ESB部署的唯一特性所影响。接口org.jboss.soa.esb.message.Message所有的实现类都可以通过类org.jboss.soa.esb.message.format.MessageFactory获取。

public abstract class MessageFactory
{
public abstract Message getMessage ();
public abstract Message getMessage (URI type);

public static MessageFactory getInstance ();
}


实例 3.12.org.jboss.soa.esb.message.format.MessageFactory
    消息的序列化实现是通过一个URI唯一指定的。你既可以在创建一个实例时指定一个实现也可以使用默认的配置。
    目前,JBossESB提供了两种实现方式,JBOSS_XML和JBOSS_SERIALIZED。这些实现是在org.jboss.soa.esb.message.format.MessageType类定义的。在运行时还可以通过org.jboss.soa.esb.message.format.MessagePlugin提供其他一些消息实现。

public interface MessagePlugin
{
public static final String MESSAGE_PLUGIN =
"org.jboss.soa.esb.message.format.plugin";

public Message getMessage ();
public URI getType ();
}


例子 3.13. org.jboss.soa.esb.message.format.MessagePlugin
    每个plug-in必须唯一指定消息实现的类型,可以通过使用getType()方法获取该类型。在系统中Plug-in实现必须在jbossesb-properties.xml定义,使用的属性名是org.jboss.soa.esb.message.format.plugin的扩展
    3.2.8.1.  MessageType.JAVA_SERIALIZED
    这种类型的实现要求信息的所有组件都是序列化的。并且要求这种类型消息的接收方可以对信息进行反序列化。换句话说,这就要求必须能对消息里包含的java类进行实例化。
    这种类型的实现要求所有内容都是可以序列化的。任何不能序列化的对象加入到消息里的尝试都会导致抛出IllegalParameterException。
    URI是urn:jboss/esb/message/type/JBOSS_XML
注意:你应该谨慎使用序列化版的消息格式,因为这会使你绑定到某种实现上。
    3.2.8.2.    MessageType.JBOSS_XML
    这是用了消息的XMl表示方式。消息的模式是在schemas目录中的message.xsl定义的。
    URI是urn:jboss/esb/message/type/JBOSS_XML。
    如果你把一个没有序列化的对象添加到消息,你必须提供一种能将对象编组到xml的机制。通过创建org.jboss.soa.esb.message.format.xml.marshal.MarshalUnmarshalPlugin接口的一个plugin可以完成这些。

public interface MarshalUnmarshalPlugin
{
public static final String MARSHAL_UNMARSHAL_PLUGIN =
"org.jboss.soa.esb.message.format.xml.plugin";
  public boolean marshal (Element doc, Object param)
throws MarshalException;

public Object unmarshal (Element doc) throws UnmarshalException;

public URI type ();
}


    必须在系统中的jbossesb-properties.xml配置文件中注册这些编排插件。这些属性名必须以MARSHAL_UNMARSHAL_PLUGIN开头。
    当打包xml里的对象时,JBossESB会遍寻注册的插件列表来查找能够处理对象类型的一个插件。如果找不到一个适合的插件,将会返回一个失败消息,就像3.2.3 节“失败”中描述的一样。打包在对象里的插件的名字也会附加上以方便消息接受者解包。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics