JBoss ESB 4.x 介绍
 

2009-11-23 作者:Jeff Yu (余昌) 来源:apache.org

 
1. 总述
2. JBossESB的概念
2.1. Message (消息)
2.2. Service (服务)
2.3. Listeners, Gateway
2.4. Couriers
2.5. Actions
2.6. Meta-data and Filters
2.7. ServiceInvoker
3. JBoss ESB 例子
3.1. 安装和运行JBossESB sample
3.2. 分析helloworld_action例子
3.3. 客户端调用服务
3.4. 小结
4. JBoss ESB 部署和集成模块
4.1. JBoss ESB Deployer
4.2. JBoss ESB Service 集成
5. 总结
Bibliography

Abstract

这篇文章主要是介绍了JBoss ESB的基础架构,以及通过一个简单的例子来认识JBoss ESB.

1. 总述

JBossESB是JBoss推出的ESB的实现,也是JBoss的SOA产品的基础.首先大家对于ESB的定义有很多的不同,我个人更喜欢把ESB看作是系统集成的一个平台. JBossESB是一个基于消息的中间件(Message Oriented). 在这篇文章中,我们只是看待ESB中的一个很基础部份,也就是怎么从Endpoint A发送信息给ESB的服务S1,然后再有S1发送信息到Endpoint B去调用服务. 至于其他的Router(路由)或者Data Transformation(数据转换),这里赞不介绍.

我们就假设一个简单的系统集成场景来开始阐述JBossESB的设计和概念.

 A系统(Endpoint A) –  Message ->  ESB -> –  Message --> B系统 (Endpoint B)

所以,如果简单的对于JBossESB定义的话,我们可以定义以下三个概念:

  • Message Listener (接收“inbound” message)
  • Message Filter (发送 "outbound” message)
  • Message (消息对象)

JBossESB 是一个面向服务(Service Oriented)的架构,所以在ESB内部的要么是一个Service, 要么是一个Message. 这里的Service就是指具有实现业务逻辑的服务,也可以是一个实现路由(Router),或者数据转化(Transformation)的服务. 就拿上面的这个例子,系统A发送一个Message给 ESB的一个服务,我们假设叫做S1, 那么S1收到Message后,做一些处理,转到S2的服务,S2再把处理后的结果发送给系统B. 这样就实现了A和B之间通过ESB的通信.

System A -> message -> S1 -> S2 ->....  -> message -> System B

那么在ESB内部是怎么去表达一个服务呢?这里引入了EndpointReference的概念,简称EPR. 有了服务之后,服务之间是通过什么样的传输层(比如JMS, FTP, HTTP)来通信呢? 所以ESB的内部也引入了Courier的API, 来统一抽象传输层. 刚我们也看到了,ESB的内部无非就是一系列的服务, 但是我们怎么来保存/注册这些服务的呢? JBossESB是使用jUDDI来注册和保存这些服务元数据的.

2. JBossESB的概念

在要了解和运行JBossESB之前,我们最好了解下JBossESB中比较重要的几个概念

2.1. Message (消息)

ESB内部所交流/传递的都是消息,所以可见这个消息格式的重要性. 在JBossESB中, 定义了一个Message的对象,它是有以下几个部分构成的.

  1. Header (用来存放From, To, Reply-to等Addressing的信息).
  2. Body (存放信息主体)
  3. Attachment (用来存放附件等)
  4. Properties
  5. Context (主要是存放一些类似事务的信息等)

目前在Body里面一般来说存放两种格式的数据,一个是串行化数据(Serialized Object ),另外一个是XML文件,比如常见 的SOAP的payload. 在ESB中,还有两个定义,一个叫ESB-aware Message, 我们上面所讲的Message就是ESB-aware Message, 正如名字说讲的,它是属于ESB内部的Message对象. 还有个叫 ESB unaware Message,也就是说他同样也是一个message,比如SOAP Message,但是如果把soap message直接让ESB来处理,是处理不了的,所以呢? 经常的在Listener 监听的端口会有个Adapter (在JBossESB里叫做Gateway)来负责把ESB-unaware message 转成 ESB-aware message.

2.2. Service (服务)

ESB的内部服务是用EPR来映射的. ESB的内部服务可以是任何的一个服务,比如说一个FTP的服务,一个基于文件系统的服务等等, 那么这个时候我们就需要用EPR来对这个服务进行描述.在EPR这个类里,主要是描述了这个服务的URI,以及所必须的一些元数据. 目前在JBossESB中提供的EPR有: FileEPR,EmailEPR,FTPEPR, HibernateEPR等等. 我们在注册服务的时候,是将EPR的信息注册到UDDI的容器里, 但不仅仅是EPR, 还有一些辅助信息,比如定义服务的category-name, service-name. 这些将在后面继续介绍.

2.3. Listeners, Gateway

Listener的作用是负责监听端口,一般来说,客户端是发送消息到Listener,然后有Listener把消息传递给ESB, 我们可以把Listener看做是inbound router. 在JBossESB中,我们是叫GatewayListener, 它一般来说做两件事情.

  • 监听Message.
  • ESB-unaware message和 ESB-aware message的互转.

目前ESB支持的Gateway有: JMSGatewayListener, JBossRemotingGatewayListener, FileGatewayListener等等. 在MessageComposer这个类里的compose/decompose方法来负责ESB-unaware信息和ESB-aware信息的转化.

public interface MessageComposer<T> {
    /**
     * Set the composer's configuration
     */
    public void setConfiguration(ConfigTree config) throws ConfigurationException;
    /**
     * Compose an ESB "aware" message from the supplied message payload.
     * Implementations need to construct and populate an ESB Message from the
     * messagePayload instance.
     */
    public Message compose(T messagePayload) throws MessageDeliverException;
    /**
     * Decompose an ESB "aware" message, extracting and returning the message payload.
     */
    public Object decompose(Message message, T originalInputMessagePayload) throws MessageDeliverException;
}

2.4. Couriers

Courier的作用就是负责传输,正如以下接口所显示:

public interface Courier extends DeliverOnlyCourier
{
	public boolean deliver(Message message) 	throwsCourierException,MalformedEPRException;
}

目前实现的Transport有:JmsCourier, InVMCourier, HibernateCourier, FileCourier等传输层,在ESB内部是通过EPR来跟Courier进行关联的.

2.5. Actions

在JBossESB中,我们可以把ActionProcessingPipeline类看作是Message Filter, 每个Message都会经过 ActionProcessingPipeline的处理. 里面有这么个方法:

public boolean process(final Message message)

而actionProcessingPipeline又是由Action (ActionPipelineProcessor)来组成的. 我们可以把Action看成是Interceptor, 由它来实现具体的业务逻辑,可以是路由,又或者数据转化功能等等. 如果你用JBossESB的话,那么Action是一个非常重要的部分,我们来看下它所定义的接口.

public interface ActionPipelineProcessor extends ActionLifecycle
{   
	 public Message process(final Message message) throws ActionProcessingException ;
	 public void processException(final Message message, final Throwable th) ;
	 public void processSuccess(final Message message) ;
 }
		

一般来说自定义的Action把具体的业务逻辑放在Process的方法里,当然了,你也可以定义相对应的Exception处理方法,通过实现processException.在ESB代码中,自定义的Action可以用继承AbstractActionPipelineProcessor 或者 AbstractActionLifecycle.

2.6. Meta-data and Filters

在有些情况下,你需要一些全局的Interceptor,我们之前说的Action,可以理解成是每个service的interceptor,但是如果我需要使用log来记录一个消息在各个service之间传输的日志, 又或者想记录消息进入某个service的时间和退出的时间. 那么在JBoss ESB中就有Filter的概念. 如果你要实现自己的Filter,需要继承InputOputFilter类.

public class InputOutputFilter
{   
    /**
     * Called as the message flows towards the transport.
     */
    
    public Message onOutput (Message msg, Map<String, Object> params) throws CourierException
    {
        return msg;
    }
    
    /**
     * Called immediately after the message is received from the transport.
     */
    
    public Message onInput (Message msg, Map<String, Object> params) throws CourierException
    {
        return msg;
    }
}

Tip

写完自己的Filter后,你需要在$JBossESB/server/config (e.g. default)/deploy/jbossesb.sar/jbossesb-properties.xml里面增加filter. 需要注意的是,在这里配置的filter是对所有的esb包都起作用,是个全局的变量.

onInput方法总是在从传输层获取到Message后,第一步所做的工作;类似的, onOutput是给传输层传递前所做的最后一步工作. 你可以在TwoWayCourierImpl中看到这段代码的调用.

2.7. ServiceInvoker

对于客户端调用来说,EPR, Courier等都太底层了.所以如果对此进行了封装. 我们对每个service加以service-category和service-name的属性. 所以如果你想发送一个ESB的内部Message,你只需要知道目标service的service-category和service-name,然后就可以调用ServiceInvoker来调用服务. 不需要去使用Courier等底层的API, 另外用ServiceInvoker还可以支持fail-over等特性.

public class ServiceInvoker {
        public ServiceInvoker(String serviceCategory, String serviceName) throws MessageDeliverException {
                    this(new Service(serviceCategory, serviceName));
       }
            
        public Message deliverSync(Message message, long timeoutMillis) throws MessageDeliverException, RegistryException, FaultMessageException
        public void deliverAsync(Message message) throws MessageDeliverException
}

3. JBoss ESB 例子

为了更好的来解释JBossESB, 最好的一个方法就是试下JBossESB自带的例子,这里我们先以helloworld_action的例子来讲解.

3.1. 安装和运行JBossESB sample

到这里,你已经成功的运行了helloworld_action的例子.
  1. JBossESB网站下载 jbossesb-server-4.4.GA.zip
  2. 解压jbossesb-server-4.4.GA.zip, 假设到/var/local/jbossesb-sever4.4. 下面以$jbossesb来替代.
  3. 在$jbossesb中,运行 bin/run.sh 来启动ESB server
  4. 另外打开一个窗口,到$jbossesb/samples/quickstarts/helloworld_actions, 运行: ant deploy
  5. 再运行: ant runtest
  6. 回到JBoss ESB server的控制台上,应该可以看到以下的输出:
    INFO  [STDOUT] [Hello World Action].
    INFO  [STDOUT] 
    &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
    INFO  [STDOUT] Body: Hello World Action
    INFO  [STDOUT] &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
    INFO  [STDOUT] ConsoleNotifier 2008/09/26 06:35:39.643<
    BEFORE**
    Hello World Action
    AFTER**
    >
    

3.2. 分析helloworld_action例子

下面我们来具体看下helloworld_action这个例子. 在JBoss ESB 4.4的例子中,我们默认以JBoss Messaging来作为JMS的实现.

3.2.1. jboss-esb.xml

在看jboss-esb.xml的配置时候,我们应该分成两个部份. providersservices.

3.2.1.1. providers

首先是<providers>,它是有一系列的<provider>组成, 目前有jms-provider, fs-provider, ftp-provider等等. 然后我们在provider里面定义这个.esb文件里面service所定义的listener所需要的bus, Bus可以简单理解成消息传送所需要的传输层. 正如以下所显示的,我们定义了两个Bus,一个是给Gateway的Listener用,另外一个是给ESB-aware Message传输所需要的传输层.

    <providers>
          <jms-provider name="JBossMQ" connection-factory="ConnectionFactory">
                      
              <jms-bus busid="quickstartGwChannel">
                  <jms-message-filter
                      dest-type="QUEUE"
                      dest-name="queue/quickstart_helloworld_action_Request"
                  />
              </jms-bus>
              <jms-bus busid="quickstartEsbChannel">
                  <jms-message-filter
                      dest-type="QUEUE"
                      dest-name="queue/B"
                  />
              </jms-bus>
          </jms-provider>
      </providers>
               

Tip

虽然在这边写的是JBossMQ, 但是对于JBoss ESB server来说,是默认使用JBoss Messaging的; 如果是把JBoss ESB安装在JBoss AS 4.x的服务器, 那么就是用JBoss MQ, 因为JBoss AS 4.x默认是使用JBoss MQ.

3.2.1.2. services

第二部份就是定义services的部份, 在这里定义了当前这个esb包所提供的services. 每个service又是由 <listener><actions>组成的.

<services>          
        <service category="HelloWorld_ActionESB" 
                 name="SimpleListener" 
                 description="Hello World" >
            <listeners>
                <jms-listener name="JMS-Gateway"
                    busidref="quickstartGwChannel"                         
                    is-gateway="true"
                />
                <jms-listener name="JMS-ESBListener"
                              busidref="quickstartEsbChannel"
                />                
            </listeners>
            <actions mep="OneWay">
            <action name="action2"
                    class="org.jboss.soa.esb.actions.SystemPrintln"
                    />
               <action name="displayAction" 
                    class="org.jboss.soa.esb.samples.quickstart.helloworldaction.MyJMSListenerAction" 
                    process="displayMessage">
                    <property name="exceptionMethod" value="exceptionHandler"/>
               </action>
               <action name="playAction" 
                    class="org.jboss.soa.esb.samples.quickstart.helloworldaction.MyJMSListenerAction" 
                    process="playWithMessage">        
                    <property name="exceptionMethod" value="exceptionHandler"/>
               </action>  
            </actions>
        </service>
      </services>

Warning

在listener里,我们通过 busidref来关联到我们定义在provider里面的bus. 在这里,我们定义了两个listener. 其中一个是做为Gateway,只负责从外界获取到JMS的消息,然后转成ESB内部所需要的Message. 而另外一个listener是用来这个Message在services内部之间通讯的通道. 所以对于每个service来说,一定要至少定义一个listener来作为内部Message传输用.

这里的action是对消息(Message)处理的地方.

3.2.2. MyJMSListenerAction

正如我们在上面看到的,我们在jboss-esb.xml中定义了action,我们看下MyJMSListenerAction.

public class MyJMSListenerAction extends AbstractActionLifecycle
{
   protected ConfigTree _config;
          
   public MyJMSListenerAction(ConfigTree config) { _config = config; }  
          
    public Message playWithMessage(Message message) throws Exception {
       Body msgBody = message.getBody();
       String contents = msgBody.get().toString();
       StringBuffer sb = new StringBuffer();
       sb.append("\nBEFORE**\n");
       sb.append(contents);
       sb.append("\nAFTER**\n");
       msgBody.add(sb.toString());
       return message;
   }
}

Tip

我们只是截取其中的一部分来说明,一般来说每个Action都要继承AbstractActionLifecycle类,然后输入/输出参数都必须是ESB的Message. 方法名可以随便定义. 你只需要在jboss-esb.xmlaction的process属性中写相对应的方法名就可以. 如果不写,默认是process方法.

这里的ConfigTree是个很重要的属性,我们很经常的会在Action配置其他的信息,那么 所有的信息都可以通过ConfigTree来获取到.比如说在某个Action中配置静态路由信息等等.也正是由于Action中你可以随意的配置你自己的信息,增加了很多的灵活性和扩展性.

3.2.3. esb文件目录结构

我们先看下部署在server下的.esb包的文件目录,一般是包括以下些东西.

  • /META-INF/jboss-esb.xml
  • /META-INF/deployment.xml 在这里定义对其他包或者服务的依赖,或者配置classloader.
  • jbm-queue-service.xml (optional) 这里是定义启动所需要的Queue
  • **.jar (optional) 放些所需要的第三方包
  • 所需要的些classes文件

3.3. 客户端调用服务

目前在JBossESB中,一般有两种方式来调用service. 一种是通过Gateway listener, 另外一种是直接通过ServiceInvoker的API来调用.

3.3.1. 通过Gateway来调用服务

回到我们的例子,我们通过JMS Gateway来访问ESB的服务.

public class SendJMSMessage {
              
  public void setupConnection() throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        que = (Queue) iniCtx.lookup("queue/quickstart_helloworld_action_Request");
        session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
        System.out.println("Connection Started");
    }
              
              
    public void sendAMessage(String msg) throws JMSException {  
        QueueSender send = session.createSender(que);        
        ObjectMessage tm = session.createObjectMessage(msg);
         tm.setStringProperty(StoreMessageToFile.PROPERTY_JBESB_FILENAME, "HelloWorldActionTest.log");
        send.send(tm);        
        send.close();
    }
       
    
    public static void main(String args[]) throws Exception
    {                   
        SendJMSMessage sm = new SendJMSMessage();
        sm.setupConnection();
        sm.sendAMessage(args[0]); 
        sm.stop();
    }
}

应该说,这是一个很普通发送JMS消息送到一个指定的Queue. 注意,我这里并没有全部拷贝这个SendJMSMessage类. 只是拷贝出重要的部分.(如果想要看完整的,请参考helloworld_action例子下面的代码)

3.3.2. 利用ServiceInvoker直接发送ESB Message

在helloworld_action例子中,没有直接SendESBMessage的客户端来调用,但是我们可以看下helloworld的sample下面的,因为是一样的.

public class SendEsbMessage 
{
    public static void main(String args[]) throws Exception
    {
//      Setting the ConnectionFactory such that it will use scout
        System.setProperty("javax.xml.registry.ConnectionFactoryClass","org.apache.ws.scout.registry.ConnectionFactoryImpl");
        
        if (args.length < 3)
        {
            System.out.println("Usage SendEsbMessage <category> <name> <text to send>");
        }
        
        Message esbMessage = MessageFactory.getInstance().getMessage();
        esbMessage.getBody().add(args[2]);  
        new ServiceInvoker(args[0], args[1]).deliverAsync(esbMessage);
        
    }   
}

正如我们之前所说的,客户端用ServiceInvokerAPI大大简化了调用服务的过程. 我们在jboss-esb.xml中看到每个service都会有service-category和service-name的属性. 在ServiceInvoker中,用户只需要提供这两个属性,就能调用到ESB的服务,当然了,还需要juddi.properties文件. 这也是为什么我们的 sample下面一般会有这个文件.

ServiceInvoker的使用

ServiceInvoker是个相当重要的API,应该说在ESB service之间服务的互相调用,就是用ServiceInvoker来完成的. 因为ServiceInvoker对Courier等进行了一层的抽象封装. 所以用ServiceInvoker来调用服务,是可以支持fail-over等高级特性的.

3.4. 小结

我们结合之前的概念,来看下这个例子的调用过程. 这里我们假设是通过JMS Gateway来调用ESB服务的.

  1. JMS Gateway listener接收到JMS Message.然后把JMS message 转成 ESB Message.
  2. 使用ServiceInvoker API发送 ESB Message到指定的service.
  3. ESBAwareListener接收到ESB Mesage后,找到对应的service,把Message提交给ActionProcessingPipeline来处理.

我们这里讲述了一个简单的调用oneway服务的一个过程.

4. JBoss ESB 部署和集成模块

通过前两个部分的介绍,应该说JBoss ESB简单的内部框架应该比较清楚了. 但是我们还没有涉及到其他部署和集成的一些模块. 正因为通过比如对Smooks, JBoss Rules, jBPM等的集成,使得JBoss ESB的功能更加强大,好用.

4.1. JBoss ESB Deployer

因为JBoss ESB 4.系列是基于JBoss AS的,所以有个扩展类JBoss4ESBDeployer类是来负责对JBoss ESB包的监听和解析. 我们可以看到在$jbossesb.sar/META-INF/jboss-service.xml里面定义了JBoss4ESBDeployer的服务.

 <mbean code="org.jboss.soa.esb.listeners.config.JBoss4ESBDeployer"
       name="jboss.esb:service=ESBDeployer">
  <depends>jboss.esb:service=ESBRegistry</depends>
  <depends>jboss.esb:service=JuddiRMI</depends>
</mbean>

所以,通过部署了JBoss4ESBDeployer,凡是以.esb结尾的包,都是由JBoss4ESBDeployer来解析, 如果你对这块比较感兴趣,还可以看下以下的几个类.

		   org.jboss.soa.esb.listeners.config.ConfigurationController
		   org.jboss.soa.esb.listeners.config.JBoss4ESBDeployment
           org.jboss.soa.esb.listeners.config.Generator
	   

4.2. JBoss ESB Service 集成

正如我们之前所看到的,因为JBossESB内部本身的架构比较灵活,所以说你只要写个自己的Action,可以很方面的做和其他library的集成. 直到JBoss ESB 4.4.GA版本,有以下一系列的集成.

  • Smooks的集成. (主要是负责数据转化等的服务).
  • JBoss Rules的集成. (可以使用Rules来定义你的路由,这里默认提供了Content-based Router)的功能.
  • jBPM的集成. (可以提供访问jBPM提供的工作流服务)
  • Soap-UI的集成. (负责跟webservice打交道)
  • Spring的集成. (访问Spring提供的服务)
  • slsb (访问无状态SessionBean提供的服务)
  • wise的集成. (另外一种方式通过跟web service打交道)

5. 总结

应该说到目前,我们只是讨论了JBoss ESB一个小的简单的模块,JBoss ESB本身还有更多的功能,比如之前所说的集成模块等等. 所以,如果你想比较完整的了解JBoss ESB,应该是去JBoss ESB的站点,然后去读文档,这里特别推荐<JBoss Programmers Guide >. 除此之外, JBoss ESB本身还带有大量的例子.可以通过运行这些例子,更容易的了解JBoss ESB. 如果在用JBoss ESB遇到问题是,可以上JBoss ESB论坛提问.

Bibliography

  1. JBoss ESB Programmers Guide (强烈推荐)
  2. JBoss ESB website
  3. JBoss ESB Blog
  4. JBoss ESB documentation
  5. JBoss ESB user forum
火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。

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