JBoss EJB3(Message Driven Beans)备忘记

前面 EJB remote interfaces 的例子使用了 RMI-IIOP 远端协定.
RMI-IIOP (Remote Method Invocation over the Internet Inter-ORB Protocol) 有下列缺点:
(1) 典型的 RMI-IIOP 客户端每次请求必须等候系统回应.
(2) RMI-IIOP 客户端与系统过于耦合, 这使得客户端难于与系统分离.
(3) 当 RMI-IIOP 客户端呼叫系统时, 这时系统或网路发生故障, 所有资料可能流失, 客户得不到预期的执行结果.
(4) RMI-IIOP 限制了在一定的时间内每个客户端只能访问单一的系统, 并没有提供多数的客户广播事件给多数的系统.

EJB 的 messaging 解决以上所有问题, 这是一种轻量级传输讯息的实作,
保证了接收者能够接收到发送者发送的讯息.
           
从前由于不同厂家的 MOM(Message-oriented middleware) 系统有自己一套的 API,
这阻碍了不同的系统间 messaging system 不能跨平台,
JMS (Java Message Service)的出现就是解决这个问题.
JMS 为 messaging 的标准, JMS 分为两部份, 第一部份为传送及接收讯息的 API,
第二部份则为 SPI (Service Provider Interface), 这嵌入于 JMS providers,
JMS providers 知道怎样与 MOM 系统沟通, JMS 确保了只需要学习一次便能应用于各种不同的 MOM 系统.

Messaging 可分为两类:
发布 / 订购 [Publish / Subscribe]: 多个发送者将不同的 messages 发送到 middleware,
middleware 将这些 messages 发送到不同的订阅者, 当全部发送完成后删除这些 messages.
这种形式为可 多发送 及 每个讯息可有多位接收者.

点对点 [Point-to-point]:发送者将 message 发送给 middleware,
middleware 将这 message 发送给接收者, 然后取消这 message.
这种形式为可 多发送, 但每个讯息只能有一个接收者.

EJB 的 message driven bean 可以接收 JMS messages 及其他种类的 messages.
这里并没有对 Message driven beans 作太多的说明, 有兴趣的可参阅 Mastering EJB3.
下面的例子实作了 Publish / Subscribe 的 internal 及 external 的 message driven beans.


开始备忘记:
[1] Eclipse 启动 JBoss Server
[2] Eclipse 建立 HelloWorldMdbEJB3 Project
[3] 建立 JBoss MBean 定义档
[4] 建立 Message Driven Beans [即 Server 端 Consumer]
[5] 建立 Client 端 Consumer
[6] 建立 Client 端 Producer
[7] 使用 ANT 建立 EJB-JAR 并执行 Client 程式

[1] Eclipse 启动 JBoss Server:
Eclipse: Windows -> Show View -> Other
  -->> JBoss-IDE -> Server Configuration 就会显示 JBoss Server Configuration console
  然后 right client 它按 start , JBoss 就会启动
 
[2] Eclipse 建立 HelloWorldMdbEJB3 Project:
Eclipse: File -> New -> Other -> EJB 3.0 -> EJB 3.0 Project
Project Name: HelloWorldMdbEJB3 -> Next
选择上一编已建立的 JBoss 4.0.x: jboss_configuration [default](not running)
打开后右键点选 JBoss 4.0.x -> new
然后按 Finish. HelloWorldMdbEJB3 project 就建立了 

[3] 建立 JBoss MBean 定义档:
<!----------- jbossmq-HelloWorldMdb-service.xml ----------->
<server>
 <mbean code="org.jboss.mq.server.jmx.Topic"
  name="jboss.mq.destination:service=Topic,name=jms/HelloWorldMdbTopic">
  <depends optional-attribute-name="DestinationManager">
   jboss.mq:service=DestinationManager
  </depends>
 </mbean>
</server>
<!----------- jbossmq-HelloWorldMdb-service.xml ----------->
档案命名为 xxxxxxx-service.xml, 必须有 -service.xml 结尾.
这备忘记将档案命名为 jbossmq-HelloWorldMdb-service.xml
将 jbossmq-HelloWorldMdb-service.xml 放在 D:/jboss/server/default/deploy
在下面的 ANT 已定义一个 task, 功能是将这个档案复制到 D:/jboss/server/default/deploy
Jboss 就会自动建立 topic/jms/HelloWorldMdbTopic 的 MBean,
Message Driven Beans 可以根据 JNDI 取得.
这里 JBoss 如发现是 Topic 则会在前面加上 topic, Queue 则加上 queue
这备忘记实作 Topic 的例子, 如想实作 Queue, 则只需将 Topic 改成 Queue
其实这个档案可以省略, 因为 Message Driven Beans 里己设定 destination property
当 JBoss 找不到就会尝试自动建立

[4] 建立 Message Driven Beans [即 Server 端 Consumer]:
/*------------------- HelloWorldMdbBean.java -----------------*/
package ejb3.joeyta.mdb;

import javax.annotation.PreDestroy;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(activationConfig = {
 @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
 @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/jms/HelloWorldMdbTopic") 
})
// MessageDriven 定义这 class 为 Mesasge Driven Beans, 必须继承 MessageListener
// destinationType 定义使用 javax.jms.Topic, 如果是 queue 则使用 javax.jms.Queue
// destination 定义 目的地 是 topic/jms/HelloWorldMdbTopic
public class HelloWorldMdbBean implements MessageListener {

 public HelloWorldMdbBean(){
  System.out.println("Local Server initialized on HelloWorldMdbBean..."); 
 }

 public void onMessage(Message msg) {  // 这是 MessageListener 里必须实作的 method,
  if (msg instanceof TextMessage) {
   TextMessage tm = (TextMessage) msg;   // 这里将 Message 转换成 TextMessage
   try {
    String text = tm.getText();
    System.out.println("Local Server HelloWorldMdbBean received message : " + text);
   } catch (JMSException e) {
    e.printStackTrace();
   }
  }
 }

 @PreDestroy   // 为 callback method, 当 instance 消除前呼叫这函数
 public void remove() {
  System.out.println("Local Server HelloWorldMdbBean destroyed.");
 }
}
/*------------------- HelloWorldMdbBean.java -----------------*/


[5] 建立 Client 端 Consumer:
/*------------------- HelloWorldConsumerClient.java -----------------*/
package ejb3.joeyta.clients;

import java.util.Properties;

import javax.annotation.PreDestroy;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;

public class HelloWorldConsumerClient implements MessageListener {

 public static void main(String[] args) throws Exception {
  new HelloWorldConsumerClient();
 }

 public static InitialContext getInitialContext()
   throws javax.naming.NamingException {

  Properties p = new Properties();
  p.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "org.jnp.interfaces.NamingContextFactory");
  p.put(InitialContext.URL_PKG_PREFIXES,
    " org.jboss.naming:org.jnp.interfaces");
  p.put(InitialContext.PROVIDER_URL, "jnp://localhost:1099");
  return new javax.naming.InitialContext(p);
 }

 public HelloWorldConsumerClient() throws Exception {
  InitialContext jndiContext = getInitialContext();  // 初始化 JNDI

  ConnectionFactory factory = (ConnectionFactory) jndiContext.lookup("ConnectionFactory");
  // 1: 寻找 connection factory

  Connection connect = factory.createConnection();
  // 2: 以 connection factory 建立 JMD connection 
 
  Session session = connect.createSession(false, Session.AUTO_ACKNOWLEDGE);
  // 3: 以 connection 建立 session, false 表示不使用 transaction,
  //     Session.AUTO_ACKNOWLEDGE 为设定怎样确定接收 message,
   
  Topic topic = (Topic) jndiContext.lookup("topic/jms/HelloWorldMdbTopic");   
  // 4: 寻找 destination, 如果是 queue, 这里只需将 topic 改成 queue, Topic 改成 Queue. 
   
  MessageConsumer consumer = session.createConsumer(topic);
  // 5: 建立 message consumer 
 
  consumer.setMessageListener(this);
  // 将这个 Class 加入到 MessageListener 里

  System.out.println("Remote Client listening for messages on HelloWorldConsumerClient...");
  connect.start();   // 开始连结
 }

 public void onMessage(Message msg) {
  if (msg instanceof TextMessage) {
   TextMessage tm = (TextMessage) msg;   // 这里将 Message 转换成 TextMessage
   try {
    String text = tm.getText();
    System.out.println("Remote Client HelloWorldConsumerClient received message : " + text);
   } catch (JMSException e) {
    e.printStackTrace();
   }
  }
 }

 @PreDestroy    // 为 callback method, 当 instance 消除前呼叫这函数
 public void remove() {
  System.out.println("Remote Client HelloWorldConsumerClient destroyed.");
 }
}
/*------------------- HelloWorldConsumerClient.java -----------------*/
由于这备忘记实作 Topic 的例子,
故 Remote Client 及 Local server 的 Message Driven Beans 均会接收到 message.
如果使用的是 Queue 的例子, 则只有最后注册 Listener 的 Consumer 才能接收到 message.


[6] 建立 Client 端 Producer:
/*------------------- HelloWorldProducerClient.java -----------------*/
package ejb3.joeyta.clients;

import java.util.Properties;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;

public class HelloWorldProducerClient {
 public static InitialContext getInitialContext()
   throws javax.naming.NamingException {

  Properties p = new Properties();
  p.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "org.jnp.interfaces.NamingContextFactory");
  p.put(InitialContext.URL_PKG_PREFIXES,
    " org.jboss.naming:org.jnp.interfaces");
  p.put(InitialContext.PROVIDER_URL, "jnp://localhost:1099");
  return new javax.naming.InitialContext(p);
 }

 public static void main(String[] args) throws Exception {
  InitialContext ctx = getInitialContext();  // 初始化 JNDI
 
  ConnectionFactory factory = (ConnectionFactory) ctx.lookup("ConnectionFactory");
  // 1: 寻找 connection factory

  Connection connection = factory.createConnection();
  // 2: 以 connection factory 建立 JMD connection

  Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
  // 3: 以 connection 建立 session, false 表示不使用 transaction,
  //     Session.AUTO_ACKNOWLEDGE 为设定怎样确定接收 message, 这 client 为发送, 故不重要

  Topic topic = (Topic) ctx.lookup("topic/jms/HelloWorldMdbTopic");
  // 4: 寻找 destination, 如果是 queue, 这里只需将 topic 改成 queue, Topic 改成 Queue.
  //    Message Driven Bean 当然也要修改.

  MessageProducer producer = session.createProducer(topic);
  // 5: 建立 message producer


  TextMessage msg = session.createTextMessage();
  msg.setText("Joeyta try HelloWorld Message Driven Beans.");
  producer.send(msg);
  // 6: 上面三句建立及发送 message

  producer.close();   // 关闭 producer
  System.out.println("Message produced.");
 }
}
/*------------------- HelloWorldProducerClient.java -----------------*/

项目结构如下图所示:

[7] 使用 ANT 建立 EJB-JAR 并执行 Client 程式:
<!-------------------------- build.xml ------------------------>
<?xml version="1.0"?>
<project name="JBoss" default="ejbjar" basedir=".">
 <property environment="env" />
 <property name="src.dir" value="${basedir}/src" />
 <property name="resources" value="${basedir}/META-INF" />
 <property name="jboss.home" value="${env.JBOSS_HOME}" />
 <property name="classes.dir" value="bin" />

 <path id="classpath">
  <fileset dir="${jboss.home}/client">
   <include name="**/*.jar" />
  </fileset>
  <pathelement location="${classes.dir}" />
  <pathelement location="${basedir}/client-config" />
 </path>

 <target name="clean">
  <delete file="${basedir}/HelloWorldMdb.jar" />
  <delete file="${jboss.home}/server/default/deploy/HelloWorldMdb.jar" />
 </target>

 <target name="ejbjar" depends="clean">
  <jar jarfile="HelloWorldMdb.jar">
   <fileset dir="${classes.dir}">
    <include name="ejb3/joeyta/mdb/*.class" />   
    <include name="META-INF/*.xml" />
   </fileset>
  </jar>
  <copy file="HelloWorldMdb.jar" todir="${jboss.home}/server/default/deploy" />
 </target>
 
 <target name="run.mdb.producer.client">
  <java classname="ejb3.joeyta.clients.HelloWorldProducerClient" fork="yes" dir=".">
   <classpath refid="classpath" />
  </java>
 </target>
 
 <target name="run.mdb.consumer.client">
  <java classname="ejb3.joeyta.clients.HelloWorldConsumerClient" fork="yes" dir=".">
   <classpath refid="classpath" />
  </java>
 </target>
 
 <target name="copy.mq.service.xml">
  <copy file="jbossmq-HelloWorldMdb-service.xml" todir="${jboss.home}/server/default/deploy" />
 </target>
 
 <target name="clean.mq.service.xml">
  <delete file="${jboss.home}/server/default/deploy/jbossmq-HelloWorldMdb-service.xml" /> 
 </target>
</project>
<!-------------------------- build.xml ------------------------>

执行 ANT Task:
点选 build -> Run As -> 3. Ant Build ->> copy.mq.service.xml
点选 build -> Run As -> 3. Ant Build ->> ejbjar
点选 build -> Run As -> 3. Ant Build ->> run.mdb.consumer.client
点选 build -> Run As -> 3. Ant Build ->> run.mdb.producer.client
这里必须顺序执行 ANT 里的Task.

JBoss Console 的输出结果为:
05:04:19,242 INFO  [jms/HelloWorldMdbTopic] Bound to JNDI name: topic/jms/HelloWorldMdbTopic
05:05:15,256 INFO  [Ejb3Deployment] EJB3 deployment time took: 297
05:05:15,443 INFO  [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=HelloWorldMdb.jar,name=HelloWorldMdbBean,service=EJB3 with dependencies:
05:05:15,912 INFO  [EJBContainer] STARTED EJB: ejb3.joeyta.mdb.HelloWorldMdbBean ejbName: HelloWorldMdbBean
05:05:16,224 INFO  [EJB3Deployer] Deployed: file:/D:/jboss/server/default/deploy/HelloWorldMdb.jar
05:06:16,987 INFO  [STDOUT] Local Server initialized on HelloWorldMdbBean...
05:06:17,190 INFO  [STDOUT] Local Server HelloWorldMdbBean received message : Joeyta try HelloWorld Message Driven Beans.

如下图所示:

run.mdb.consumer.client Console 的输出结果为:
Buildfile: D:/eclipse_wtp/workspace/HelloWorldMdbEJB3/build.xml
run.mdb.consumer.client:
     [java] log4j:WARN No appenders could be found for logger (org.jboss.mq.referenceable.SpyConnectionFactoryObjectFactory).
     [java] log4j:WARN Please initialize the log4j system properly.
     [java] Remote Client listening for messages on HelloWorldConsumerClient...
     [java] Remote Client HelloWorldConsumerClient received message : Joeyta try HelloWorld Message Driven Beans.
    
如下图所示:

run.mdb.producer.client Console 的输出结果为:
Buildfile: D:/eclipse_wtp/workspace/HelloWorldMdbEJB3/build.xml
run.mdb.consumer.client:
     [java] log4j:WARN No appenders could be found for logger (org.jboss.mq.referenceable.SpyConnectionFactoryObjectFactory).
     [java] log4j:WARN Please initialize the log4j system properly.
     [java] Remote Client listening for messages on HelloWorldConsumerClient...
     [java] Remote Client HelloWorldConsumerClient received message : Joeyta try HelloWorld Message Driven Beans.

如下图所示:

这里为了简化 Message Driven Beans 备忘记,
故没有延续前编的 Shopping Cart Entity Beans 备忘记,
才把它独立建立 HelloWorld Message Driven Beans Project,
如需与 Shopping Cart 合备,
只需在 Entity Beans 备忘记 ShoppingCartBean.java 里加入
@Resource(mappedName="ConnectionFactory")
private ConnectionFactory connectionFactory;

@Resource(mappedName="jms/HelloWorldMdbTopic")
private Topic topic;

@Remove
public void checkout() throws IncompleteConversationalState {
    try {
        Connection connect = topicFactory.createConnection( );
        Session session = connect.createSession(true,0);
        MessageProducer producer = session.createProducer(topic);

    TextMessage msg = session.createTextMessage();
    msg.setText("Joeyta try HelloWorld Message Driven Beans.");
        producer.send(msg);
        connect.close( );
    } catch(Exception e) {
        throw new EJBException(e);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章