站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题JBoss EJB3(Message Driven Beans)備忘記

JBoss EJB3(Message Driven Beans)備忘記

摘要: 從前由於不同廠家的 MOM(Message-oriented middleware) 系統有自己一套的 API, 這阻礙了不同的系統間 messaging system 不能跨平台, JMS (Java Message Service)的出現就是解決這個問題.

原本來自: http://blog.matrix.org.cn/page/joeyta?entry=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 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 定義檔:

[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 程式:








































執行 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


↑返回目录
前一篇: JBoss jBPM(Workflow Management Engine)備忘記
后一篇: JAVAEE 常见性能问题解决手册