基于ActiveMQ 的发布/订阅(Pub/Sub) Chat 示例,上传了源码

来源:互联网 发布:淘宝订单要上传身份证 编辑:程序博客网 时间:2024/06/07 23:01

转自:http://www.iteye.com/topic/836509

 

 

环境需求:

1. JDK 1.5 或者以上

2. Apache Ant, 在写本文时,用的是 Ant 1.7.1

3. ActiveMQ, 在写本文时,用的是 Apache ActiveMQ 5.4.1

技术需求:

1. JMS(Java Message Service)

2. JNDI(Java Naming and Directory Interface)

在JMS的“发布/订阅(pub/sub)”模型中,消息的发布者(Publisher)通过主题(Topic)发布消息,订阅者(Subscriber)通过订阅主题获取消息。 一个主题可以同时有多个订阅者. 通过这种方式我们可以实现广播式(broadcast)消息。

为了更好的理解"发布/订阅(Pub/Sub)"模式,我在《Java消息服务器 第二版》上找到了一个很好的例子来说明他的使用。不过书上只提供了相关代码供我们理解,没有讲述整个创建过程,在这里打算记录下整个构建实例的过程:

1.  创建项目目录入下图所示,并将activemq-all-*.jar 复制到项目的classpath中:

2. 编写Chat代码:

Java代码 复制代码 收藏代码
  1. public class Chatimplements MessageListener { 
  2.     private TopicSession pubSession; 
  3.     private TopicPublisher pub; 
  4.     private TopicConnection conn; 
  5.     private String username; 
  6.  
  7.     public Chat(String topicFactory, String topicName, String username) 
  8.             throws NamingException, JMSException { 
  9.  
  10.         // 创建 JNDI context 
  11.         InitialContext ctx = new InitialContext(); 
  12.  
  13.         //1. 创建 TopicConnectionFacotry 
  14.         TopicConnectionFactory factory = (TopicConnectionFactory) ctx 
  15.                 .lookup(topicFactory); 
  16.         //2. 创建 TopicConnection 
  17.         TopicConnection connection = factory.createTopicConnection(); 
  18.  
  19.         //3. 根据 Connection 创建 JMS 会话 
  20.         TopicSession pubSession = (TopicSession) connection.createSession( 
  21.                 false, Session.AUTO_ACKNOWLEDGE); 
  22.         TopicSession subSession = (TopicSession) connection.createSession( 
  23.                 false, Session.AUTO_ACKNOWLEDGE); 
  24.  
  25.         //4. 创建 Topic 
  26.         Topic topic = (Topic) ctx.lookup(topicName); 
  27.  
  28.         //5. 创建 发布者 和 订阅者 
  29.         TopicPublisher pub = pubSession.createPublisher(topic); 
  30.         TopicSubscriber sub = subSession.createSubscriber(topic, null, true); 
  31.  
  32.         //6. 为发布者设置消息监听 
  33.         sub.setMessageListener(this); 
  34.  
  35.         this.conn = connection; 
  36.         this.pub = pub; 
  37.         this.pubSession = pubSession; 
  38.         this.username = username; 
  39.  
  40.         //7. 开启JMS连接 
  41.         connection.start(); 
  42.     } 
  43.  
  44.     protected void writeMessage(String txt) { 
  45.         try
  46.             TextMessage message = pubSession.createTextMessage(); 
  47.             message.setText(username + ": " + txt); 
  48.  
  49.             pub.publish(message); 
  50.         } catch (JMSException e) { 
  51.             e.printStackTrace(); 
  52.         } 
  53.  
  54.     } 
  55.  
  56.     public void onMessage(Message msg) { 
  57.         TextMessage txtMsg = (TextMessage) msg; 
  58.         try
  59.             System.out.println(txtMsg.getText()); 
  60.         } catch (JMSException e) { 
  61.             e.printStackTrace(); 
  62.         } 
  63.     } 
  64.  
  65.     public void close()throws JMSException { 
  66.         this.conn.close(); 
  67.     } 
  68.  
  69.     public staticvoid main(String[] args) throws NamingException, 
  70.             JMSException, IOException { 
  71.         if (args.length != 3) { 
  72.             System.out.println("Factory, Topic, or username missing"); 
  73.         } 
  74.  
  75.         Chat chat = new Chat(args[0], args[1], args[2]); 
  76.  
  77.         BufferedReader cmd = new BufferedReader( 
  78.                 new InputStreamReader(System.in)); 
  79.  
  80.         while (true) { 
  81.             String s = cmd.readLine(); 
  82.  
  83.             if (s.equalsIgnoreCase("exit")) { 
  84.                 chat.close(); 
  85.                 System.exit(0); 
  86.             } else
  87.                 chat.writeMessage(s); 
  88.             } 
  89.         } 
  90.     } 

3.由于里我们使用了JNDI, 所以我们需要编辑jndi.properties。内容如下:

Java代码 复制代码 收藏代码
  1. # START SNIPPET: jndi 
  2. java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory 
  3.  
  4. # use the following property to configure the default connector 
  5. java.naming.provider.url = tcp://localhost:61616 
  6.  
  7. java.naming.security.principal=system 
  8. java.naming.security.credentials=manager 
  9.  
  10. # use the following property to specify the JNDI name the connection factory 
  11. # should appear as.  
  12. #connectionFactoryNames = connectionFactory, queueConnectionFactory, topicConnectionFactry 
  13. connectionFactoryNames = topicConnectionFactry 
  14.  
  15.  
  16. # register some queues in JNDI using the form 
  17. # queue.[jndiName] = [physicalName] 
  18. #queue.MyQueue = example.ChatQue 
  19. topic.chat = example.chat 
  20.  
  21. # register some topics in JNDI using the form 
  22. # topic.[jndiName] = [physicalName] 
  23. #topic.MyTopic = example.ChatTop 
  24.  
  25. # END SNIPPET: jndi 

4. 到这里已经基本完成Chat 编码工作,使用如下指令即可运行这个示例:

《Java 消息服务》原文 写道
java com.dayang.jms.demo.Chat [topicConnectionFactory] [topicName] [userName]

不过如果没有设置相关classpath,是不可能通过这个指令来成功运行这个Demo,在这里我打算使用Ant来帮我完成这个工作

5. 编写build.xml脚本如下:

Xml代码 复制代码 收藏代码
  1. <?xml version="1.0"encoding="utf-8"?> 
  2. <project name="chat"default="run"basedir="."> 
  3.     <propertyname="src.dir"value="src"/> 
  4.     <propertyname="build.dir"value="build"/> 
  5.     <propertyname="classes.dir"value="${build.dir}/classes"/> 
  6.     <propertyname="jar.dir"value="${build.dir}/jar"/> 
  7.     <propertyname="lib.dir"value="libs"/>        
  8.      
  9.     <!-- 设置main函数所在类 --> 
  10.     <propertyname="main-class"value="com.dayang.jms.chat.Chat"/> 
  11.      
  12.     <!-- 定义classpath --> 
  13.     <path id="classpath"> 
  14.         <filesetdir="${lib.dir}"includes="**/*.jar"/> 
  15.     </path> 
  16.      
  17.     <!-- 创建构建目录,用于存放构建生成的文件 --> 
  18.     <targetname="init"> 
  19.         <mkdirdir="${build.dir}"/> 
  20.     </target> 
  21.      
  22.     <!-- 编译 --> 
  23.     <targetname="compile"depends="init"> 
  24.         <mkdirdir="${classes.dir}"/> 
  25.         <javacsrcdir="${src.dir}"destdir="${classes.dir}"  
  26.             classpathref="classpath"/> 
  27.         <!-- copy properties file to classpath --> 
  28.         <copytodir="${classes.dir}"> 
  29.             <filesetdir="${src.dir}"excludes="**.*.jar"/> 
  30.         </copy> 
  31.     </target> 
  32.      
  33.     <!-- 打包 --> 
  34.     <targetname="jar"depends="compile"> 
  35.         <mkdirdir="${jar.dir}"/> 
  36.         <jardestfile="${jar.dir}/${ant.project.name}.jar"  
  37.                 basedir="${classes.dir}"> 
  38.             <manifest> 
  39.                 <attributename="Main-Class"value="${main-class}"/> 
  40.             </manifest> 
  41.         </jar> 
  42.     </target> 
  43.      
  44.     <!-- 运行client1 --> 
  45.     <targetname="run1"depends="jar"> 
  46.         <javafork="true"classname="${main-class}"> 
  47.             <argvalue="topicConnectionFactry"/> 
  48.             <argvalue="chat"/> 
  49.             <argvalue="client1"/> 
  50.             <classpath> 
  51.                 <pathrefid="classpath"/> 
  52.                 <pathlocation="${jar.dir}/${ant.project.name}.jar"/> 
  53.             </classpath> 
  54.         </java> 
  55.     </target> 
  56.      
  57.     <!-- 运行client2 --> 
  58.     <targetname="run2"depends="jar"> 
  59.         <javafork="true"classname="${main-class}"> 
  60.             <argvalue="topicConnectionFactry"/> 
  61.             <argvalue="chat"/> 
  62.             <argvalue="client2"/> 
  63.             <classpath> 
  64.                 <pathrefid="classpath"/> 
  65.                 <pathlocation="${jar.dir}/${ant.project.name}.jar"/> 
  66.             </classpath> 
  67.         </java> 
  68.     </target> 
  69.      
  70.     <targetname="clean"> 
  71.         <deletedir="${build.dir}"/> 
  72.     </target> 
  73.      
  74.     <targetname="clean-build"depends="clean,jar"/> 
  75.      
  76. </project> 

6. 打开两个控制台窗口,分别使用ant run1 和 ant run2 指令来运行程序, 如果成功我们将看到如下结果:


写在最后:

这个示例仅仅简单的说了JMS 发布/订阅 API的基本使用,更多特性需要在以后的使用中进行摸索。

发布/订阅 除了能够提供“1对多”的消息专递方式之外,还提供了消息持久化的特性。他允许订阅者在上线后接收离线时的消息,关于这部分特性,以及“发布/订阅”的应用场景打算在以后的文章中慢慢阐述。

参考资料:

1. JMS: http://baike.baidu.com/view/157103.htm

2. ActiveMQ: http://baike.baidu.com/view/433374.htm

3. JNDI http://baike.baidu.com/view/209575.htm

4. 《Java消息服务器 第二版》

5. Ant Manual http://ant.apache.org/manual/index.html

2011-05-18: 新增加了Demo的源代码, 需要的可以下载附件JSMDemo.rar

JMSDemo工程目录结构如下: