提要:我们可以使用messagedrivenbean(消息驱动组件),在企业级的应用程序中进行异步的消息传送。
引言:enterprise javabeans(ejb)1.1版本中定义了两种组件类型―session组件和entity组件。客户端对象可以同步调用ejb1.1的这两种组件的方法,然而,为了继承message oriented middleware(mom,面向对象的中间件)和java message service(jms,java消息服务)的优点的需要,ejb框架中也相应的应当加入异步的消息通讯机制,所以,在ejb2.0中就定义了第三种组件类型----messagedrivenbean(消息驱动组件)
messagedrivenbean兼备ejb和jms的功能,当然,如果您想要消息操作技术,那么您大可只使用jms就行了,但是新的消息驱动组件messagedrivenbean提供了消息通讯的新的可能性。那么,这些组件如何整合到一个应用程序服务器框架中?他们的功能又如何扩大了过去jms服务器的使用范围?让我们看下文吧!
ejb和jms
前面我们已经说过了,ejb1.1种为开发者定义了两个企业级组件类型-----session和entity组件。session组件通常实现一些商业逻辑并且不能在多客户端共用。entity组件则描述一个实体的面向对象的概念,而这个实体往往存在于像数据库那样固定的存储容器中。在这两种组件模型中,使用本地的或远程的接口来简化客户端的交互作用。按照定义,这种交互作用是严格的同步的。举例来说,通过一个方法调用把一个请求发送给组件,然后服务器对象返回一个响应。
然而,在企业版应用程序的范围中,也经常需要异步的消息传递,比方说,一个客户可能想发给服务器一条信息,但是并不需要或者不想要服务器做出应答,这时,客户端就没有必要等待服务器对象处理请求。对于客户端应用程序来说,在确保消息最终能够到达服务器并被正常处理的前提下,提交一条消息然后继续处理本身的事务,将会在很大的程度上提高效率。
能够处理异步消息的能力的java技术可以在java message service(jms)中找到,jms原本就是被开发来提供传统的message oriented middleware(mom)产品的一个标准java接口。
现在,一些公司开发出了一整套新一代轻量级高效的纯java的jms产品,这些产品是开发者能够建立jms连接来发布或从其它应用程序组件中接受消息。下面的例程给出了与一个jms提供者接口的必要步骤:
代码段一:准备客户端
客户端应用程序使用了带有jms 主题的messagelistener来接受和处理消息。
| import javax.jms.*; /** *一个例程,演示如何取得一个jms *连接并取得一个消息监听者。在本例中 *我们将获取一个与一个jms主题的连接 */ public class jmssample { public static void main (string args[]) { initialcontext context = new initialcontext(); // 查找主题 topic topic (topic)context.lookup("mytopic"); file://取得我们创建jms连接时所要用到的连接创建器 topicconnectionfactory tcf = (topicconnectionfactory)context.lookup( "topicconnectionfactory"); // 创建jms连接 topicconnection conn = tcf.createtopicconnection(); // 从连接中创建jms session。 // 这样我们就可以创建一个非事务处理、auto_acknowledge的连接 topicsession session = conn.createtopicsession( false, session.auto_acknowledge); // 创建主题订阅者 topicsubscriber subscriber = session.createsubscriber(topic); // 监听者 subscriber.setmessagelistener(mylistener); // 指出我们将要接受信息的连接 conn.start(); } } |
除了采用上面的步骤取得jms messageconsumer(消息消费者)的连接之外,开发者还可创建并注册一个或多个使用message consumer的jms message listener(消息监听者)接口。message listener总是在一个单独的控制线程中执行,这就意味着在编写消息监听者时,开发者不需要担心并发性问题的出现,
下面我给出了一个典型的jms 消息监听者实现的代码。
代码段2:
| /** * 这个类是jms messagelistener的一个实现 * 用来处理包含股票报价的消息 */ class mylistener implements messagelistener { /** * 从收到的信息中取出股票报价 * 并且把它放入标准输出流中并显示。 */ public void onmessage(message message) { // 从消息对象中取出报价 // 我们知道消息产生者发送textmessages try { string quote = ((textmessage)message).gettext(); system.out.println("股票报价: " + quote); } catch(jmsexception e) { system.out.println( "错误处理消息: "+message); } } } |
在这个实现中,messagelistener接收到的消息中包含了股票报价,消息监听者只是简单的从消息体中取得股票报价并把它输出到标准输出流中。
开发一个健壮的jms客户端程序可能是非常困难的,程序员必须要考虑可能会同时接受多个消息,此外还有交易安全性、并发性消息处理、对象生命周期、容错性和可扩展性,这些都是开发者急切地想从ejb服务器中找到的功能。不过直到现在,程序员们还不得不自己动手把这些技术结合在一起应用。
为了整合ejb1.1和jms,jms监听者必须要使用我们在代码段2中描述的方法来建立。jms客户端程序必须参考一个stateless(无状态)的用于响应处理jms消息的session组件,然后,jms消息要传递给ejb。然而,jms消息并不要求被序列化,这就意味着这条消息在传递到远程的ejb实例之前必须被转换成为有序的消息类或在消息监听者中部分地解构。而且,应用程序开发者还有责任管理jms服务器之间的事务联系,以及处理ejb、消息和并发性,这些都是非常复杂的事情。
即使一个应用程序开发者能把上面的这些都完成,并且也有能力访问jms提供者并取得消息,但显然,他需要编写一大堆的代码,这对于我们这些常人往往是不大可能实现的。ejb2.0解决了这个问题,它通过扩展ejb组件类型,为需要异步消息支持的组件开发者提供简化的解决方案――新的messagedriven组件类型。
messagedrivenbean组件
messagedrivenbean被部署成为总是扮演信息消费者角色的客户端。messagedrivenbean没有客户端视图,这就意味着其本地和远程调用接口都是不可用的。一个消息产生者发信息给一个主题或队列并且没有认识到一个事实--messagedrivenbean正扮演着消息消费者的身份。这就导致了在基于系统的 jms之间的宽松联结,并更多的考虑到了在集合一个分布式计算环境时应有的灵活性。
messagedriven 组件没有对话状态,其实所有的组件实例当它们在没有处理消息时都是等价的。这有点和无状态的session组件的状态特征有些类似。把组件实例集中起来是管理messagedriven组件实例的普遍而又有效的办法。
messagedriven 组件必须以直接或间接的方法从接口javax.ejb.messagedrivenbean中取得,而这个接口类则是从javax.jms.messagelistener接口得来并添加了两个方法。onmessage()方法是从javax.jms.messagelistener接口中继承来的,这个方法有唯一的参数,就是javax.jms.message,可以是任何有效的jms消息类型。这个方法显然不包括throw(抛出)子句,所以在处理消息时不会抛出任何应用程序异常。
当这个容器接收到消息,它首先从一个可用实例池中取得一个messagedriven组件然后把部署描述器中制定的任务与执行线程联系起来,使其能够传播安全上下文。此外,如果部署描述器需要事务上下文的话,容器也会设置与之的关联。
一旦完成了管理任务,接收到的消息酒杯传送到messagedrivenbean实例的onmessage()方法中,而一旦这个方法完成后,消息所载的事务就会被执行或返回,然后组件重新返回可用实例池中。
当messagedrivenbean实例被从容器中(通常从实例池中)的任何强的参考中逐出,都会调用ejbremove()方法。ejbremove()方法将释放任何被组件实例占用的资源。setmessagedrivencontext()方法有一个参数--javax.ejb.messagedrivencontext类的一个实例。messagedrivencontext类与定义在ejb1.1中的entity和session类有点类似,当一个组件实例被创建,容器就把它传递进一个实例占用的上下文中,这个类有取得环境信息的方法也有相应的方法取得jta usertransaction类(用于管理事务定界的组件)。
此外,组件提供者还应当在ejb2.0服务器中可摄制的组件提供一个没有参数的ejb.create()方法。这个组件实例可以获得任何在ejb.create()用于进行处理的所需要的资源,比如说,在这一点上,messagedrivenbean实例可以取得一个数据库连接,如果ejb.remove()方法被调用的话,它将关闭或释放。
值得注意的是,messagedrivenbean现在已经大大的简化了创建jms消息消费者的过程,下面的代码段3就创建并配置了一个ejb容器所委托创建的jms消息消费者。开发者现在可以很容易的实现messagedrivenbean接口,并可以把它配置在ejb服务器中且可以用来创建一个可收集消息的商业组件。
代码段3:
| /** *messagedrivenbean接口由每一个消息驱动企业级组件类实现。 *这个容器使用messagedrivenbean 方法来通知 *企业级bean实例的实例生命周期事件 */ public interface javax.ejb.messagedrivenbean extends javax.jms.messagelistener { /** * 传送一个消息给监听者 * * 参数 message :message对象。 */ public void onmessage(javax.jms.message message); /** *容器在结束消息驱动对象的生命周期之前,调用这个方法。 */ public void ejbremove(); /** *设置相关联的消息驱动上下文。 *容器在创建了实例后调用这个方法。 * 企业版 bean 实例将保存context对象的参考到一个实例对象中 */ public void setmessagedrivencontext( javax.ejb.messagedrivencontext context); } |
在代码段4中给出了一个messagedrivenbean实例的实现,在个组件从一个jms textmessage中取得一条字符串,并输出,它是根据代码段2种的jms消息监听者程序改编的。
代码段: 4
| /** * 这个类是 messagedrivenbean的一个实现。 */ public class mylistenermdb implements messagedrivenbean { /** * 这是一个无参数构造器,这样 ejb容器可以使用class.newinstance()方法来创建组件实例 */ public mylistenermdb() { } /** *这个方法接受消息实例并执行消息处理过程。 * * 参数:message 。message对象 */ public void onmessage(message message) { // onmessage 实现仍然未变: // 从message对象中取出股票报价。 // stockquoteproducer 发送 textmessages // 并在适合的时候放出该对象。 try { string quote = ((textmessage)message).gettext(); system.out.println("股票报价: " + quote); } catch(jmsexception e) { system.out.println( "不能处理消息: " + message); } } /** * 当messagedrivenbean实例被从容器中抛出,该方法就被调用。 */ public void ejbremove() throws javax.ejb.ejbexception { system.out.println( "stocklistenermdb: ejbremove被调用。"); } /** * 设置messagedrivencontext实例。本方法将在组件实例化时被调用 *消息驱动上下文允许组件开发者访问ejb容器的工具 * * 参数ctx : 消息驱动上下文 */ public void setmessagedrivencontext( messagedrivencontext ctx) throws javax.ejb.ejbexception { system.out.println( "stocklistenermdb: setmessagedrivencontext 被调用。"); } /** * ejbcreate with no args required by spec, though not * enforced by interface */ public void ejbcreate() { system.out.println( "stocklistenermdb: ejbcreate called."); } } |
部署描述器
messagedrivenbean可以使用xml部署描述器来指出受ejb服务器信息控制的运行时间动作,下面是部署描述器中定义messagedrivenbean的有效的dtd元素。
| <!element message-driven (description?, display-name?, small-icon?, large-icon?, ejb-name?, ejb-class, transaction-type, transaction-scope?, jms-message-selector?, jms-acknowledge- mode?, message-driven-destination?, env-entry*, ejb-ref*, security- identity?, resource-ref*, resource- env-ref*)> |
需要注意的是,部署描述器包含所有除用来部署messagedrivenbean组件的目标名以外的所有信息,目标名被设置在一个应用程序服务器提供商指定的配置文件中或作为一个系统属性。
在部署描述器中,配置器可以指定组件是倾向于用于主题还是用于队列,并且,如果倾向于用于主题那么组件是否应该担当持久的签署者( durable subscriber)的身份。像队列一样,持久的主题保证监听者将接收到所有发布到这个主题的消息,即使监听者可能一段时间都不可用。 持久的主题对应用程序的可靠性很重要。
我们的给出的messagedrivenbean的部署描述器(见代码段5)告诉容器这个组件是特意侦听一个不持久主题。这个组件有使用 notsupported方法事务属性的容器管理事务限定。
代码段5:
| <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>messagelistenermdb</ejb-name> <ejb-class>messagelistenermdb</ejb-class> <transaction-type>container</transaction-type> <transaction-scope>local</transaction-scope> <jms-acknowledge-mode>auto-acknowledge</jms- acknowledge-mode> <message-driven-destination> <jms-destination-type>javax.jms.topic</jms- destination-type> <jms-subscription-durability>nondurable</jms- subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>messagelistenermdb</ejb-name> <method-name>onmessage</method-name> <method-params> <method-param>javax.jms.message</method-param> </method-params> </method> <trans-attribute>notsupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> |
使用messagedrivenbean组件的另一个好处就是它的部署的简洁性。 典型情况下,一个应用服务器供应商将提供定义组件部署描述器的工具,产生 ejb jar文件,并且部署组件。 一旦组件部署好了, ejb服务器将处理 ejb容器类的注册并且开始 jms连接。 因为 jms定义了一个标准,一个基于 jndi的机制,用于获得 jms主题,队列,连接以及一个能够使用任何jms供应商实现的高端的应用程序服务器的引用。 这允许设计者在开发商业程序时最充分的利用应用程序服务器和jms服务器。
messagedrivenbean容器服务
ejb容器提供了下列服务,它们都是低级的ejb支持的服务,不能直接被使用ejb1.1的jms开发者使用。
1.管理生命周期:
messagedrivenbean的生命周期与配置它的ejb服务器的寿命是一致的,因为messagedrivenbean是无状态的,所以组件实例通常被ejb服务器集中起来,并且当一条消息变得对于作为消息消费者的主题或队列可用的时候,会被容器取回。
2、处理异常:
messagedrivenbean组件当处理消息的时候,可能不会抛出应用程序异常,这就意味着可能被messagedrivenbean抛出的唯一的异常指明一个系统错误的运行时间异常。容器将会处理这个异常,方法是删除这个组件实例,并返回任何组件实例或容器启动的事务处理。
3、线程或并发性:
messagedrivenbean实例是在一个单独的控制线程中执行,这将大大的简化开发者的任务。ejb服务器将确保这一特性,另外,ejb服务器可能提供一种操作模式允许多消息被单独的组件实例同时处理,这个配置选项实用定义在jms规范中的“expert level”类。jms提供者并不一定带有这些类,所以ejb服务器可能不能利用每一种jms实现的这种功能。
4、事务处理:
就像使用entity或session组件一样,messagedrivenbean组件也可以有“contain-or-bean-managed”(容器或组件管理)事务处理。一个事务属性可以设置为messagedrivenbean组件的onmessage()方法。因为没有客户端事务处理,所以只有一个事务处理属性的子集考虑到entity和session组件与messagedrivenbean组件有关系。使用了容器管理事务处理。容器将能支持messagedrivenbean的required和notsupport事务处理属性。一个带有组件管理事务处理的messagedrivenbean组件可以使用jta usertransaction对象。这个messagedrivenbean组件在从onmessage()方法返回之前,必须先结束事务处理。onmessage()方法非常简单,它带有容器管理事务界限和notsupported事务属性。容器将不会创建事务并且组件开发者被禁止访问 usertransaction对象。
更有趣的事,当onmessage()方法被指定了required事务属性,这时容器将创建一个全局事务处理收集任何可参考的资源并被传递到任何其他的正在处理消息的ejb服务器上。这是唯一一种jms主题或队列接口包含于ejb事务处理中的情景。
对于使用required事务属性的组件,jms服务器将成为xaresource,如果jms服务器提供者不支持xa事务处理,jms session通常将与容器的全局事务处理的结果同步。这样,容器将不能包括用于管理分布事务的两方面的委托处理的jms服务器。jms session的一个 rollback(反转)将警告 jms服务器,消息应当被重新发送。
5、消息确认:
容器总是处理messagedrivenbean组件的消息收到的确认,对于组件来说,使用定义在 jms规范中的客户端消息收到确认方法是非法的。消息收到确认可以被设置为 dups_ok_acknowledge或 auto_acknowledge,前者允许在一次失败之后投递消息的副本,而后者提供一个严格的保证机制,确保消息只能被投递一次。
6、安全性
因为messagedrivenbean组件没有客户端,所以在接受消息的时候容器基本上不会产生安全问题。ejb2.0规范中为组件方法执行一个声明指定的功能提供了方便。因此,messagedrivenbean组件可以被设置来确保用于传送到其他正在处理消息的ejb服务器的安全。这就可以使messagedrivenbean组件维护方法级安全性了。
应用程序服务器框架
当 messagedrivenbean提供的功能在应用程序服务器内被部署好后,它就会以指数形式膨胀开来。一个高端的应用程序服务器提供可扩展性,负载平衡,动态应用程序启动,附加的 ejb服务器实例的动态配置以及容错性,这些都是企业级应用程序的核心要素。
1、可扩展性: 企业级应用程序服务器的一个关键元素就在它的提供一个结构来适应不断增加的处理负载的能力。 随着被 messagedrivenbean处理的消息的数量的增加,应用程序服务器的 ejb服务器必须使适应这些增加而不会明显的增加处理时间。一般是提供给多应用程序服务器用于协调处理受到的消息的增多,这样应用程序服务器就有了可扩展性了。
2、负载平衡:当一个请求发送到应用程序服务器,一个负载平衡元素可以在实例之间平均分配负载。举例来说,应用程序服务器每个实例都可能包含一个ejb服务器的实例。使用应用程序服务器的负载平衡特性,受到的消息就可以平均分配到所有的ejb服务器实例上了。
3、动态应用程序启动:应用程序服务器应当可以在负载增多时启动附加的预先设置的实例。
4、动态设置额外的ejb服务器实例:即使一个应用程序被设定了固定的用于处理应用程序负载的实例数,但是也有可能信息流的激增造成系统处理速度变慢,为了处理这种情况,应用程序服务器必须具有配置附加应用程序实例的特性。比如说,应用程序服务器可以允许系统管理员动态定义附加的ejb服务器实例并把这些附加实例部署到ejb服务器中。负载平衡元素将利用这些补充的实例来处理消息。
5、容错性:一个企业级的应用程序服务器必须也具有能够适应出错的情况,像网络或硬件上的问题,但是前提是不能明显降低系统性能或是丢失数据,应用程序服务器使用的一种方法就是通过设置多应用程序服务器实例来分配状态信息,收到的消息通过智能化的负载平衡元素被分配到实例上。如果有一个或多个实例不能到达,负载平衡元素只要简单的把消息重新分配给各个能用的实例就可以了。此外,负载平衡元素还可在管理组件的参与下重启那些因为某些原因而不能使用的实例。
超越jms
ejb的messagedrivenbean组件部分给企业级服务器领域又添了一员猛将,它允许消息经由jms接受在被一个简单但又强有力的组件处理。然而,messagedrivenbean的ejb组件事实上有能力变成能够处理任何消息的组件模型,而不单是jms服务器发来的消息。
jms消息通过jms实现被变成可交互操作,这就意味着jms消息私地下是某一种特定的的实现,但是内容可以完全透明的被转换成另一种特定的jms实现而不会在任何方面影响消息消费者。因此任何消息格式都可以转换成为jms消息并被传递到messagedrivenbean组件中。换句话说,一个messagedrivenbean组件可以处理电子邮件、http、ftp或其他任何协议发来的消息,以及又具有把这些协议转化成为jms消息的应用程序服务器提供的消息。这就开启了一个标准、简单、轻便且能够处理任何协议发过来的任何消息的能力的组件模型之门。如果消息被定义成为可开发的、可扩展的语言向xml这样的及其强大的可交互操作在宽松连接系统以一种每个人都能理解的模式完成。下面我想简单以一个独立于协议以外的消息处理的例子来验证一下这种技术的强大威力。
一个b2b的例子
一个典型的b2b情景就像一个“中枢-轮辐”形,多个宽松连接的商家(即“轮幅”)与一个大型企业(即“中枢”)交互连接,在某种情况之下,当执行交互操作(像发送大的订单这样的事件)可以经由jms应用程序在大企业与商家之间正常相互作用。对于一些小型商家来说,采用纯jms可能在成本上太高而负担不起,它可能会采用通过web浏览器中的http或发送电子邮件的形式向大企业发送订单。为了向客户开放b2b系统,大型企业级服务器就必须能够处理这一系列不同的协议。
而且,作为中枢的大型企业级服务器虽然接受来自email或来自jms等不同类型格式的订单,但是只有必要为每一种方式分别编写处理程序。使用messagedrivenbean组件,任何协议都可以发送以xml格式描述的订单,如下面的例子。messagedrivenbean组件只需要把注意力集中在处理来自应用程序服务器发送的jms textmessage中的xml就可以了,而不要去关心到底所处理的消息时直接来自jms服务器还是作为一条来自邮件服务器的smtp消息。
代码段六:
| <purchase_order> <client_number>101</client_number> <date>august 8, 2001</date> <billing_address> <name>wayne zheng</name> <address>xyz street</address> <city>hefei</city> <province>anhui</province> <zip>230027</zip> </billing_address> <shipping_address> <name>zhang tao</name> <address>huangshan road</address> <city>hefei</city> <state>anhui</state> <zip>230026</zip> </shipping_address> <items> <item> <quantity>1</quantity> <product_number>324</product_number> <description>java book</description> <unitcost>19</unitcost> </item> <item> <quantity>1</quantity> <product_number>532</product_number> <description>java vm</description> <unitcost>56</unitcost> </item> </items> </purchase_order> |
小结
ejb2.0提供了一种新的组件模型-messagedrivenbean组件-用来处理异步消息,messagedrivenbean组件可以简单的开发和利用与其他ejb组件模型相同的容器服务,这种新的组件模型允许整合面向对象的中间件(mom)的、优秀的以及新一代纯java的jms。并且,messagedrivenbean组件还有可能成为处理任何异步消息,特别是基于xml消息的工业标准。因此,messagedrivenbean组件是ejb2.0中的非常重要的一员,本文只是初探其面目,以后我还会更加深入的探讨其的功用。
闽公网安备 35060202000074号