.Net中如果需要消息队列功能,可以很方便的使用微软自带的MSMQ,对应到Java中,这个功能就是JMS(Java Message Service). 下面以Jboss EAP 6.2环境,介绍一下基本的用法:
JMS有二种基本的使用模式,
一种是点对点模式(即“一条”消息只能从“一个”发送方传输到“一个”接收方) - 这种模式下,发送方、接收方不必同时在线,消息在未取回走,一直存放在队列中。
另一种是发布/订阅模式,即一条消息(该模式下,称为“主题”),被一个发送方发送后,如果有多个接收方订阅了,这些接收方都能收到消息 - 这种模式下,发布者和订阅都必须同时在线,否则接收不到消息。
本文主要演示“点对点”模式的基本使用
一、jboss上创建相应的用户
JMS要求用户安全认证,即不允许随便谁都向队列里发送/接收,先在Jboss里创建一个用户。
%JBOSS_HOME%/bin/add-user.sh (mac/linux机环境,如果是windows,则是add-user.bat)
执行这个命令后,会出现文字交互界面,参考下图:
将创建一个msgUser的用户,提示:jboss eap 6.2安全性比较高,要求用户的密码满足复杂性要求(比如: Password1! ),如果太简单的密码,将创建失败
输完密码后,会提示该用户是否加入某个组,这里输入组名:guest (后面创建queue时会用到),参考下图:
注:上图中最后一个加密字符串,最好记录一下,某些应用要求安全认证时,需要在配置中配置该串(本文中暂时用不到)
创建完成后,实质上是在%JBOSS_HOME%/standalone/configuration的application-roles.properties、application-users.properties这二个文件中加了一些内容:
msgUser=5f7e011c53bb155cf99b9eeffccdad4d (这是application-users.properties中增加的内容)
msgUser=guest (这是application-roles.properties中增加的内容)
二、在jboss中创建队列
把%JBOSS_HOME%/standalone/configuration下的standalone.xml先改个名,然后把standalone-full.xml改成standalone.xml,启动jboss (即:要以standalone-full.xml中的内容做为配置启动jboss)
a) 图形界面方式
进入jboss的管理控制台,找到Profile -> Subsystems -> Messageing -> Destinations -> 查看 (参考下图)
默认情况,应该没有任何Queue
点击“添加”,Name这里输入myQueue(这个可以随便改),JNDI Names这里输入 java:jboss/exported/queue/mytest (最后面的mytest可以随便改,前面的部分建议不要改)
这样就创建一个Queue,到目前为止,还没看到跟安全认证相关的设置,切换到Seurity Settings面板
可以看到,默认情况下,创建的Queue允许"guest"角色 "发送"消息(Send这里是true)、"接收"消息(Consume这里是true),这就是为什么我们在第一步,要把msgUser这个用户加入guest组的原因
b) standalone.xml 配置方式
其实刚才的操作,最后的结果就是在standalone.xml中生成了如下代码:
<subsystemxmlns="urn:jboss:domain:messaging:1.4">
...
<security-settings>
<security-setting match="#">
<permission type="send" roles="guest"/>
<permission type="consume" roles="guest"/>
</security-setting>
</security-settings>
...
<jms-destinations>
<jms-queue name="myQueue">
//注:这一行建议也手动加上,对于单纯向Queue发送消息而言,加不加效果一样,
//但是对于Message-drive-bean不加,偶尔会发现无法从queue/mytest接收到消息
<entry name="queue/mytest"/>
<entryname="java:jboss/exported/queue/mytest"/>
<durable>true</durable>
</jms-queue>
</jms-destinations>
</hornetq-server>
</subsystem>
熟悉这个结构后,可以直接在standalone.xml中修改
c) 单独在deployments目录下部署 xxx-jms.xml (注:必须是以-jms.xml结尾的文件)
内容如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<messaging-deploymentxmlns="urn:jboss:messaging-deployment:1.0"><hornetq-server><jms-destinations><jms-queuename="fred"><entryname="jms/queue/fred"/><entryname="java:jboss/exported/jms/queue/fred"/></jms-queue></jms-destinations></hornetq-server></messaging-deployment>
注:经实际测试,建议所有内容都写在一行上,不加要其它任何空格或Tab字符,否则部署会失败(jboss EAP 6.1+版本对xml的校验极严格,哪怕是不可见字符,只要有一个字符校验失败,整个部署将失败)
另外:该方式部署的queue,在管理控制界面上看不到,但是代码可访问
三、编写测试代码
3.1 jndi.properties
要连接到队列,必然需要一些相关的参数,比如:队列地址、用户名、密码、连接“字符串”等,如果硬编码在java代码中,显然不好,我们可以在src目录下,新建一个jndi.properties文件,内容参考下图:
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory
java.naming.provider.url=remote\://localhost\:4447
java.naming.security.principal=msgUser
java.naming.security.credentials=Password1!
lookup.connectionfactory.name=jms/RemoteConnectionFactory
lookup.destination.name=queue/mytest
前面java开头是的必须的,后面的二行,是我自己加的(当然大家也可以自行添加任何需要的属性)
3.2 编写测试代码
(以下代码是在jboss-quickstart示例代码的基础上修改而来的)
1 package org.jboss.as.quickstarts.jms; 2 3 import java.util.Hashtable; 4 5 import javax.jms.Connection; 6 import javax.jms.ConnectionFactory; 7 import javax.jms.Destination; 8 import javax.jms.JMSException; 9 import javax.jms.MessageConsumer;10 import javax.jms.MessageProducer;11 import javax.jms.Session;12 import javax.jms.TextMessage;13 import javax.naming.Context;14 import javax.naming.InitialContext;15 import javax.naming.NamingException;16 17 18 19 public class JbossJMSTest {20 21 public static void main(String[] args) throws NamingException, JMSException {22 23 final String lOOKUP_CONNECTION_FACTORY_NAME = "lookup.connectionfactory.name";24 final String lOOKUP_DESTINATION_NAME = "lookup.destination.name";25 26 ConnectionFactory connectionFactory = null;27 Connection connection = null;28 Session session = null;29 MessageProducer producer = null;30 MessageConsumer consumer = null;31 Destination destination = null;32 TextMessage message = null;33 Context context = null;34 35 try {36 // 创建上下文(默认会从应用的classpath下加载jndi.properties做为环境参数)37 context = new InitialContext();38 39 // 把环境参数取出来,后面会用到40 Hashtable<String, String> env = (Hashtable<String, String>) context41 .getEnvironment();42 43 // 查找连接工厂44 connectionFactory = (ConnectionFactory) context.lookup(env45 .get(lOOKUP_CONNECTION_FACTORY_NAME));46 47 // 查找目标队列48 destination = (Destination) context.lookup(env49 .get(lOOKUP_DESTINATION_NAME));50 51 // 创建连接52 connection = connectionFactory.createConnection(53 env.get(Context.SECURITY_PRINCIPAL),54 env.get(Context.SECURITY_CREDENTIALS));55 56 // 创建会话57 session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);58 59 // 创建生产者(即发送者)60 producer = session.createProducer(destination);61 62 // 创建消费者(即接收者)63 consumer = session.createConsumer(destination);64 65 // 开始连接66 connection.start();67 68 // 发送消息69 message = session.createTextMessage("菩提树下的杨过,欢迎使用JMS!");70 producer.send(message);71 72 // 接收消息73 message = (TextMessage) consumer.receive(5000);74 75 // 打印消息76 System.out.println(message.getText());77 78 } catch (NamingException e) {79 e.printStackTrace();80 } catch (JMSException e) {81 e.printStackTrace();82 } finally {83 // 释放资源84 if (context != null) {85 context.close();86 }87 88 if (connection != null) {89 connection.close();90 }91 92 }93 }94 95 }
JbossJMSTest
运行结果:
最后附加上pom.xml内容:
1 using System; 2 using java_ws_client.java.hello; 3 using java_ws_client.java.order; 4 5 namespace java_ws_client 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Console.WriteLine("hello world service is testing..."); 12 13 using (HelloWorldServiceClient client = new HelloWorldServiceClient()) 14 { 15 var msg = client.sayHelloToName("jimmy"); 16 Console.WriteLine(msg); 17 } 18 19 Console.WriteLine("\norder service is testing..."); 20 21 using (OrderServiceClient client = new OrderServiceClient()) 22 { 23 var order = client.orderProduct(new product[] { new product() { productName = "book", amount = 2 }, new product() { productName = "iphone", amount = 3 } }); 24 25 Console.WriteLine(String.Format("orderNo:{0},amount:{1},product:{2}", order.orderNo, order.amount, order.products.Length)); 26 } 27 28 Console.ReadKey(); 29 } 30 } 31 }