星期五, 8月 04, 2006

JBoss EJB3(Message Driven Beans)備忘記

第一編介紹如何安裝 JBoss 及建立第一個 Stateless Session Beans HelloWorld:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_helloworld_%E5%82%99%E5%BF%98%E8%A8%98


第二編介紹 Stateful Session Beans:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_stateful_session_beans


第三編介紹 Entity Beans:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_entity_beans_%E5%82%99%E5%BF%98%E8%A8%98


EJB 提供 messaging 的功能, 這是一種輕量級的傳輸的實作,
解決以下這些 RMI-IIOP (Remote Method Invocation over the Internet Inter-ORB Protocol) 的缺點:
(1) 典型的 RMI-IIOP 客戶端每次請求必須等候系統回應.
(2) RMI-IIOP 客戶端與系統過於偶合, 這使得客戶端難於與系統分離.
(3) 當 RMI-IIOP 客戶端呼叫系統時, 這時系統或網路發生故障, 所有資料可能流失, 客戶沒有得到預期的執行結果.
(4) RMI-IIOP 限制了在一定的時間內每個客戶端只能訪問單一的系統, 並沒有提供多數的客戶廣播事件給多數的系統.


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 放在 D:\jboss\server\default\deploy
在下面的 ANT 己定義一個 Task copy 到 D:\jboss\server\default\deploy
Jboss 就會自動建立 topic/jms/HelloWorldMdbTopic 的 MBean, 可以使用 JNDI 最得.
這裡 JBoss 如發現是 Topic 則會在前面加上 topic, Queue 則加上 queue
如果改成 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.
如果使用的是 Queue, 則最後註冊 Listener 才能收到 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);
    }
}


這次 JBoss EJB3 Message Driven Beans 教學己到了終點.

正籌備 Transactions, Security, Timers, Clustering, Web Services, Interceptors(AOP-like) 教學.


由於 Java Persistence 內容太多了,  所以放在最後.
不過我相信有了上面這個起點, 學下去也很容易.

如果想更了解 EJB3 , 這裡有免費的 Mastering EJB 3.0. 下載.
http://www.theserverside.com/tt/books/wiley/masteringEJB3/index.tss


J2EE tutorial
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/


Sun EJB Documentation page:
http://java.sun.com/products/ejb/docs.html


JSR 220:
http://www.jcp.org/en/jsr/detail?id=220


 

沒有留言: