activeMQ学习之一(简单例子介绍,含spring结合)

1. activeMQ简单介绍

ActiveMQ 中间件用Java语言编写,为JAVA、C/C++、.NET、Perl、PHP、Python、Ruby 和一些其它语言提供客户端。在你考虑如何集成不同平台不同语言编写应用的时候,ActiveMQ 拥有巨大优势。多种客户端API通过ActiveMQ 发送和接受消息成为可能,无论使用的是什么语言。ActiveMQ 还提供交叉语言功能,该功能整合这种功能,无需使用远程过程调用(RPC)确实是个优势,因为消息协助应用解耦。

提供标准的产生、发送、接收消息的接口简化企业应用的开发。
    它支持两种消息通信模型:点到点(point-to-point)(P2P)模型和发布/订阅(Pub/Sub)模型。
    P2P 模型规定了一个消息只能有一个接收者;Pub/Sub 模型允许一个消息可以有多个接收者。
    对于点到点模型,消息生产者产生一个消息后,把这个消息发送到一个Queue(队列)中,然后消息接收者再从这个Queue中读取,一旦这个消息被一个接收者读取之后,它就在这个Queue中消失了,所以一个消息只能被一个接收者消费。
    与点到点模型不同,发布/订阅模型中,消息生产者产生一个消息后,把这个消息发送到一个Topic中,这个Topic可以同时有多个接收者在监听,当一个消息到达这个Topic之后,所有消息接收者都会收到这个消息。

2.使用场景

2.1不同语言应用集成

ActiveMQ使用java编写,并且提供一个java客户端API。但ActiveMQ也为C/C++,.NET,Perl,PHP,Python,Ruby等提供客户端。当你考虑在不同平台不同语言下的各个应用进行集成时,这将是一个巨大的优势。不同语言的客户端API使各种不同的语言能够通过ActiveMQ发送和接收消息。对于ActiveMQ提供的多语言兼容,还有一个好处是相对于RPC调用,它能帮助系统各应用间的解耦。

2.2.RPC的替代者----应用广泛的使用RPC模式的同步调用。

现在大量使用RPC调用的客户端服务器模式的应用,它们包括ATMs,大多数web应用,信用卡系统,销售点(point-of-sale)系统等。尽管它们大多数是成功的,但是转换到异步消息模式能够在保证正确响应的情况下带来一些好处。使用同步请求的系统在规模上有较大的限制,因为请求会被阻塞,从而导致整个系统变慢。如果使用异步消息替代,可以很容易增加额外的消息接收者,使得消息能被并发消耗,从而加快请求处理。当然,必须你系统应用间是解耦的。

 2.3应用间解耦

紧耦合系统能带来很多问题,特别是在应用是分布式的情况下。松耦合系统,也就是依赖性小的系统,可以更好地适应未知变化。不只是系统某部分的改变不会影响整个系统,而且部件间的交互也更简单。相比使用同步的系统(调用者必须等待被调用者返回信息),异步系统(调用方发送消息后就不管,即fire-and-forget)能够给我们带来事件驱动架构(event-driven architecture EDA).

 2.4作为事件驱动架构的骨架----解耦

异步架构的系统允许通过代理器自己配置更多的客户端,内存等(即vertical scalability)来扩大系统,而不是增加更多的代理器(即horizontal scalability)。考虑如亚马逊这样繁忙的电子商务系统。当用户购买物品,事实上系统需要很多步骤去处理,包括下单,创建发票,付款,执行订单,运输等。但是用户下单后,会立即返回“谢谢你下单”的界面。不只是没有延迟,而且用户还会受到一封邮件表明订单已经收到。在亚马逊下单的例子就是一个多步处理的例子。每一步都由单独的服务去处理。当用户下单是,有一个同步的体积表单动作,但整个处理流程并不通过浏览器同步处理。相反地,订单马上被接受和反馈。而剩下的步骤就通过异步处理。如果在处理过程中出错,用户会通过邮件收到通知。这样的异步处理能提供高负载和高可用性。

2.5 提高系统扩展性

很多使用事件驱动设计的系统是为了获得高可扩展性,例如电子商务,政府,制造业,线上游戏等。通过异步消息分开商业处理步骤给各个应用,能够带来很多可能性。考虑设计一个应用来完成一项特殊的任务。这就是面向服务的架构(service-oriented architecture SOA)。每一个服务完成一个功能并且只有一个功能。应用就通过服务组合起来,服务间使用异步消息和最终一致性。这样的设计便可以引入一个复杂事件处理概念(complex event processing CEP)。使用CEP,部件间的交互可以被记录追踪。在异步消息系统中,可以很容易在部件间增加一层处理。

3. 第一个简单例子helloWorld

3.1 下载安装

可以官方网站下载安装二进制包,   http://activemq.apache.org/download-archives.html,
上面罗列出各个版本,我使用的是这个版本:
http://activemq.apache.org/activemq-590-release.html ,分别下载对应windows和Unix/Linux/Cygwin的版本,其中mac版本也在第二个中

如何启动在 apache-activemq-5.9.0/docs/user-guide.html 写得很清楚
简单描述下:
在windows上解包,可以到apache-activemq-5.9.0\bin\activemq.bat运行ActiveMQ程序, 而在Linux上的apache-activemq-5.10.0-bin.tar.gz,解压命令: tar zxvf apache-activemq-5.9.0.tar.gz,解包到一个目录就可以使用了.

linux 64位运行: apache-activemq-5.9.0/bin/linux-x86-64/activemq  start   (后台进程,启动)
linux 64位运行: apache-activemq-5.9.0/bin/linux-x86-64/activemq  stop   (停止)
mac启动: pache-activemq-5.9.0/bin/macosx/activemq  start
mac停止: pache-activemq-5.9.0/bin/macosx/activemq  stop


ActiveMQ可能需要以下端口1099(JMX),61616(默认的TransportConnector)

windows查看监听端口:
netstat -an|find "61616"

Linux系统:
netstat -an|grep 61616

运行后可以打开web控制台,查看消息的数量、发送情况、多少个客户端等信息:
http://localhost:8161/admin
默认用户名密码:admin  admin


3.2 helloWorld代码

第一个简单的例子需要5个步骤:

1.启动activeMQ的服务

2.程序添加依赖

3.编写消息生产者

4.编写消息接受者(消费者)

5.main方法调试

3.2.1.启动activeMQ的服务

访问:http://localhost:8161/admin
默认用户名密码:admin  admin 登录成功看到如下图,代表消息服务启动成功了。



3.2.2 程序添加依赖

在java工程中,添加依赖,如果是maven项目直接用如下:

<dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.9.0</version>
        </dependency>

如果是普通java工程或web工程,依赖包在:下载的解压文件目录下:apache-activemq-5.9.0/activemq-all-5.9.0.jar

3.2.3.编写消息生产者

import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.util.Date;

/**
 * 消息发送器
 */
public class MessageSender implements Runnable {

	private String url;
	private String user;
	private String password;
	private final String QUEUE;

	public MessageSender(String queue, String url, String user, String password) {
		this.url = url;
		this.user = user;
		this.password = password;
		this.QUEUE = queue;
	}

	@Override
	public void run() {
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);
		Session session = null;
		Destination sendQueue;
		Connection connection = null;

		int messageCount = 0;
		try {
			connection = connectionFactory.createConnection();

			connection.start();
			System.out.println(Thread.currentThread().getName() + " start");

			while (true) {
				session = connection.createSession(true, Session.SESSION_TRANSACTED);

				sendQueue = session.createQueue(QUEUE);
				MessageProducer sender = session.createProducer(sendQueue);
				
				/*
				 * map类型的消息写法
				MapMessage mapMessage = session.createMapMessage();
				mapMessage.setLong("shopId", 123);
				mapMessage.setLong("userId", 456);
				mapMessage.setLong("qrcodeConfigId", 889);
				sender.send(mapMessage);
				*/
				
				// 文本类型的消息写法
				TextMessage outMessage = session.createTextMessage();
				outMessage.setText(new Date() + "现在发送是第" + messageCount + "条消息");
				sender.send(outMessage);
				
				session.commit();
				sender.close();

				if ((++messageCount) == 10) {
					// 发够十条消息退出
					break;
				}
				Thread.sleep(1000);
			}

			connection.close();
			System.out.println(Thread.currentThread().getName() + " close");
		} catch (JMSException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}



3.2.4.编写消息接受者(消费者)

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * 消息接受者
 */
public class MessageReceiver implements Runnable {
	private String url;
	private String user;
	private String password;
	private final String QUEUE;
	

	public MessageReceiver(String queue, String url, String user, String password) {
		this.url = url;
		this.user = user;
		this.password = password;
		this.QUEUE = queue;
	}

	@Override
	public void run() {
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);
		Session session = null;
		Destination receiveQueue;
		try {
			Connection connection = connectionFactory.createConnection();

			session = connection.createSession(true, Session.SESSION_TRANSACTED);
			receiveQueue = session.createQueue(QUEUE);
			MessageConsumer consumer = session.createConsumer(receiveQueue);

			connection.start();
			System.out.println(Thread.currentThread().getName() + " start");

			while (true) {
				Message message = consumer.receive();
				// 处理文本类型消息
				if (message instanceof TextMessage) {
					TextMessage receiveMessage = (TextMessage) message;
					System.out.println(Thread.currentThread().getName() + ",收到消息如下:" + receiveMessage.getText());
				} 
				// 处理map类型消息
				else if (message instanceof MapMessage) {
					MapMessage receiveMessage = (MapMessage) message;
					long shopId = receiveMessage.getLong("shopId");
					long userId = receiveMessage.getLong("userId");
					long qrcodeConfigId = receiveMessage.getLong("qrcodeConfigId");
					System.out.println(Thread.currentThread().getName() + ",收到消息如下:shopId =" + shopId + ",userId=" + userId + ",qrcodeId=" + qrcodeConfigId);
				} else {
					break;
				}
				session.commit();
//				SystemClock.sleepRandom(2000, 3000);

			}
			connection.close();
			System.out.println(Thread.currentThread().getName() + " close");
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}


3.2.5.main方法调试

public class MyActiveMQDemo {  
  
	public static void main(String[] args) throws InterruptedException {
		String url = "tcp://127.0.0.1:61616";
		String user = "system";
		String password = "manager";
		String query = "MyQueue";
		new Thread(new MessageSender(query, url, user, password), "Name-Sender").start();
		new Thread(new MessageReceiver(query, url, user, password), "ReceiveThread1").start();
		new Thread(new MessageReceiver(query, url, user, password), "ReceiveThread2").start();
	}
}  

main运行结果:

Name-Sender start
ReceiveThread2 start
ReceiveThread1 start
ReceiveThread2,收到消息如下:Wed Nov 23 16:33:30 CST 2016现在发送是第0条消息
ReceiveThread1,收到消息如下:Wed Nov 23 16:33:31 CST 2016现在发送是第1条消息
ReceiveThread2,收到消息如下:Wed Nov 23 16:33:32 CST 2016现在发送是第2条消息
ReceiveThread1,收到消息如下:Wed Nov 23 16:33:33 CST 2016现在发送是第3条消息
ReceiveThread2,收到消息如下:Wed Nov 23 16:33:34 CST 2016现在发送是第4条消息
ReceiveThread1,收到消息如下:Wed Nov 23 16:33:35 CST 2016现在发送是第5条消息
ReceiveThread2,收到消息如下:Wed Nov 23 16:33:36 CST 2016现在发送是第6条消息
ReceiveThread1,收到消息如下:Wed Nov 23 16:33:37 CST 2016现在发送是第7条消息
ReceiveThread2,收到消息如下:Wed Nov 23 16:33:38 CST 2016现在发送是第8条消息
ReceiveThread1,收到消息如下:Wed Nov 23 16:33:39 CST 2016现在发送是第9条消息
Name-Sender close

这里有2个线程来处理消息队列的任务,当然也可以一个。。


另外说明:

			session = connection.createSession(true, Session.SESSION_TRANSACTED);
这里定义了消息类型和确认方式。。 如果是其他类型的和消息确认方式可以修改。

查看更多的消息类型和确认机制,http://blog.csdn.net/lsblsb/article/details/53335401

4.spring+activemq+maven集成

共分为5个步骤:

1.工程添加依赖

2.添加配置文件

3.编写消息的发送者

4.编写消息的接受者(消费者)

5.使用


4.1.工程添加依赖

 <!--    如果自己有spring的依赖版本的,添加自己的
          <dependency>  
             <groupId>org.springframework</groupId>  
             <artifactId>spring-jms</artifactId>  
             <version>4.1.6.RELEASE</version>  
             <scope>compile</scope>  
         </dependency>  
           -->
         <dependency>
            <groupId>javax.jms</groupId>
            <artifactId>jms</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.9.0</version>
        </dependency>

好像在maven中央仓库中jms1.1有时候下载不下来,估计资源已经没有了,http://download.csdn.net/detail/zhao_sh/1384138 随便找了个地方下

4.2.添加配置文件,biz-activemq.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.8.0.xsd">
 
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="1" />
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="location">
            <value>classpath:activemq.properties</value>
        </property>
    </bean>

    <!-- Activemq connection factory -->
    <bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <constructor-arg index="0" value="${jms.broker.url}"/>
        <property name="useAsyncSend" value="true"/>
        <property name="userName" value="${jms.broker.username}"/>
        <property name="password" value="${jms.broker.password}"/>
    </bean>

    <!-- ConnectionFactory Definition -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
        <constructor-arg ref="amqConnectionFactory"/>
    </bean>

 
    <bean id="defaultDestination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="${jms.poster.queue.name}"/>
    </bean>


    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="defaultDestination" ref="defaultDestination"/>
        <!-- 
        	发送模式  
        		DeliveryMode.NON_PERSISTENT=1:非持久化 ;
        		DeliveryMode.PERSISTENT=2:持久化
          --> 
         <property name="deliveryMode" value="2" /> 
         <!-- 
        	 带事务的,那么配置(如果session带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。):
         		javax.jms.Session.SESSION_TRANSACTED = 0
         	不带事务,几种配置:(不带事务的session的签收方式,取决于session的配置)
	            javax.jms.Session.AUTO_ACKNOWLEDGE = 1  消息自动签收
	  		    javax.jms.Session.CLIENT_ACKNOWLEDGE = 2  客户端调用acknowledge方法手动签收
	  			javax.jms.Session.DUPS_OK_ACKNOWLEDGE = 3  不必必须签收
	  			 <property name="sessionAcknowledgeMode" value="1" /> 
          -->
    </bean>
	
	<!-- 定义发送消息对象 -->
    <bean id="messageSender" class="com.hsmonkey.weijifen.common.activemq.MessageSender">
        <constructor-arg index="0" ref="jmsTemplate"/>
    </bean>
    
    <!-- 定义接受消息对象 -->
    <bean id="messageReceiver" class="com.hsmonkey.weijifen.common.activemq.MessageReceiver">
    </bean>
    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationName" value="${jms.poster.queue.name}"/>
        <property name="messageListener" ref="messageReceiver"/>
        <!-- 如果我们要进行本地的事务管理,只需要在定义对应的消息监听容器时指定其sessionTransacted属性为true
         -->
        <property name="sessionTransacted" value="true"/>
    </bean>
    
</beans>


activemq.properties  文件内容:

# activeMQ server
jms.broker.url=tcp://127.0.0.1:61616
# connection activeMQ server username
jms.broker.username=system
# connection activeMQ server password
jms.broker.password=manager
# queue name
jms.poster.queue.name=poster


4.3.编写消息的发送者

package com.hsmonkey.weijifen.common.activemq;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;



@Component
public class MessageSender {

	protected final Logger log = LoggerFactory.getLogger(getClass());

	private final JmsTemplate jmsTemplate;

	/**
	 * 发送map格式消息
	 */
	public void send(final long userId, long shopId, long qrcodeConfigId) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("userId", userId);
		map.put("shopId", shopId);
		map.put("qrcodConfigDOId", qrcodeConfigId);
		// log.error("sendMessage:userId=" + userId + ",shopId=" + shopId + ",qrId=" + qrcodeConfigId);
		jmsTemplate.convertAndSend(map);
	}
	
	/**
	 * 发送文本消息
	 */
	public void send(String text) {
		jmsTemplate.convertAndSend(text);
	}
	
	public MessageSender(final JmsTemplate jmsTemplate) {
		this.jmsTemplate = jmsTemplate;
	}
}


4.4.编写消息的接受者(消费者)

package com.hsmonkey.weijifen.common.activemq;


import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;




public class MessageReceiver implements MessageListener {
	
	public void onMessage(Message message) {
		if (message instanceof TextMessage) {
			TextMessage textMessage = (TextMessage) message;
			try {
				String text = textMessage.getText();
				String value = String.format("Received: %s", text + ",time:" + DateUtil.format(new Date()));
				log.error("value----->" + value);
			        // SystemClock.sleepRandom(3000, 4000);

			} catch (JMSException e) {
				e.printStackTrace();
			}

			return;
		}
		
		
		if (message instanceof MapMessage) {
			MapMessage mapMessage = (MapMessage) message;
			try {
				long userId = mapMessage.getLong("userId");
				long shopId = mapMessage.getLong("shopId");
				long qrcodConfigDOId = mapMessage.getLong("qrcodConfigDOId");
				// log.error("receiveMapMessage:" + DateUtil.format(new Date()) + ",userId=" + userId + ",shopId=" + shopId + ",qrId=" + qrcodConfigDOId);
				System.out.println("receiveMapMessage:userId=" + userId + ",shopId=" + shopId + ",qrId=" + qrcodConfigDOId);
				
				

			} catch (Exception e) {
				// log.error("MessageReceiverMapMessageError", e);
			}
		}
	}
}

4.5使用

// messageSender对象通过spring注入到你需要的地方,然后就可以调用:
long userId = 123L;
long shopId = 456L;
long qrcodeConfigId = 789L; 
messageSender.send(userId, shopId, qrcodeConfigId);
messageSender.send("helloworld");
调用了send方法后,消息就发送出去了,消息接受者就会自动处理了。

5.密码设置与修改,包括端口修改等

   web容器中的访问密码,消息队列中的操作安全验证等其他

5.1 web容器中的访问密码,web容器修改访问端口

5.1.1 访问用户名密码

ActiveMQ使用的是jetty服务器, 打开 apache-activemq-5.9.0/conf/jetty.xml文件:
现在默认已经开启用户名密码验证了, <property name="authenticate" value="true" />


<bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">
        <property name="name" value="BASIC" />
        <property name="roles" value="user,admin" />
        <!-- set authenticate=false to disable login -->
        <property name="authenticate" value="true" />
    </bean>


所有配置文件都放在:apache-activemq-5.9.0/conf/ 这个目录下面
用户名密码保存在 apache-activemq-5.9.0/conf/jetty-realm.properties文件中,内容如下:

## ---------------------------------------------------------------------------
## Licensed to the Apache Software Foundation (ASF) under one or more
## contributor license agreements.  See the NOTICE file distributed with
## this work for additional information regarding copyright ownership.
## The ASF licenses this file to You under the Apache License, Version 2.0
## (the "License"); you may not use this file except in compliance with
## the License.  You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
## ---------------------------------------------------------------------------

# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]

# 特别注意(其实上面注释写得很清楚):这里3个admin依次代表:用户名:密码, 角色名称,所以这里用户admin 和user 都可以登录,因为上面的配置角色也是这两个
admin: admin, admin
user: user, user

5.1.2 web容器访问端口修改

ActiveMQ使用的是jetty服务器, 打开 apache-activemq-5.9.0/conf/jetty.xml文件,现在端口是8161


    <bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
             <!-- the default port number for the web console -->
        <property name="port" value="8161"/>
    </bean>


5.2 消息队列中的操作安全验证

打开配置文件:apache-activemq-5.9.0/conf/activemq.xml

这里引用了这个properties:

<!-- Allows us to use system properties as variables in this configuration file -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <value>file:${activemq.conf}/credentials.properties</value>
        </property>
    </bean>


添加用户名密码验证插件:

<plugins>   
        <simpleAuthenticationPlugin>   
            <users>   
                <authenticationUser username="${activemq.username}" password="${activemq.password}" groups="users,admins"/>   
            </users>   
        </simpleAuthenticationPlugin>   
    </plugins>  

其中activemq.username 和 activemq.password 的值,在apache-activemq-5.9.0/conf/credentials.properties 配置文件中添加或修改处理

另外:activeMQ有很多插件可以配置处理,apache-activemq-5.9.0/examples/conf/*, 当前参考:apache-activemq-5.9.0/examples/conf/activemq-security.xml

5.3 activeMQ的启动日志等

解压文件目录:apache-activemq-5.9.0/data/activemq.log
默认的数据持久化存储位置:apache-activemq-5.9.0/data/kahadb/*

6.更多知识

参考官方例子和说明文档和例子,含有很多的内容,apache-activemq-5.9.0/examples/*,
http://activemq.apache.org/version-5-getting-started.html


猜你喜欢

转载自blog.csdn.net/lsblsb/article/details/53286878
今日推荐