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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章