第一編介紹如何安裝 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
沒有留言:
發佈留言