上一篇关于ActiveMQ的博客仅仅是一个简单的小案例,真正的企业中业务更复杂,情况更多变,所以用到的配置和内容也略显复杂。今天就来简单看下一般企业项目中的一些常用配置。
通用配置
这些主要指connection和一些Destination的一些配置。
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="failover:(tcp://localhost:61616?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=10000"/>
<property name="optimizedAckScheduledAckInterval" value="10000" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!--连接池-->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="connectionFactory" ref="targetConnectionFactory"/>
<property name="maxConnections" value="10"/>
</bean>
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="pooledConnectionFactory"/>
</bean>
<!--Topic-->
<bean id="msgTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="com.gh.test.topic"/>
</bean>
<!--Queue-->
<bean id="msgQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="com.gh.test.queue"/>
</bean>
注意:
1、上述连接池和普通的连接方式选择其一就好,根据具体项目选择。
2、Topic和Queue是指消息目的地,生产者把消息发送到这里,消费者从这里取消息消费。
3、Queue(点对点模式),一对一,一个生产者一个消费者。Topic(订阅发布模式),一对多,一个生产者多个消费者。生产者
生产者一般是围绕JmsTemplate做配置,这个类是Spring对ActiveMQ的支撑类,有很多的属性,下面的一些配置也是针对该类的一些属性的配置,具体更详细的大家自己看下源码。
<!-- 消息转换器 -->
<bean id="msgConverter" class="com.gh.test.mq.send.MsgConverter" />
<!-- 消息发送者 -->
<bean id="msgJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="explicitQosEnabled" value="true"/>
<property name="timeToLive" value="600000"/>
<property name="deliveryMode" value="2" />
<property name="priority" value="2"/>
<property name="messageConverter" ref="msgConverter"/>
</bean>
<!-- 发送工具类 -->
<bean id="msgSend" class="com.gh.test.mq.send.MsgSend">
<property name="msgJmsTemplate" ref="msgJmsTemplate"/>
<property name="msgTopic" ref="msgTopic"/>
</bean>
这里我是做了三个配置,下面分别简单介绍下。
1.消息转换器:
这个类是实现MessageConverter接口,它的作用主要有两方面,一方面它可以把我们的非标准化Message对象转换成我们的目标Message对象,这主要是用在发送消息的时候;另一方面它又可以把我们的Message对象转换成对应的目标对象,这主要是用在接收消息的时候。一般与监听器(MessageListenerAdapter)联合使用,后面具体用到了我们再讲。
2.消息发送者:
connectionFactory:MQ连接,这里没用连接池
explicitQosEnabled:默认false,是否开启是否开启 deliveryMode, priority, timeToLive的配置
deliveryMode:默认2,表示持久化,1为非持久化
priority:消息优先级,默认为4
timeToLive:消息过期时间
messageConverter:消息转换器
3.发送工具类:
没什么特殊的地方,就是集成了JmsTemplate和Destination,作为一个工具类方便使用。项目中最好有一个。
消费者
在Spring整合JMS的应用中在定义消息监听器的时候一共可以定义三种类型的消息监听器,分别是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。注意:三种方式都要配一个DefaultMessageListenerContainer消息监听容器。
MessageListener方式
这个很简单,参考http://blog.youkuaiyun.com/u013185616/article/details/51891965 这里的消费者配置。一般简单的MQ应用可以直接这么配置。SessionAwareMessageListener方式
生产者就直接用上面的那个topic的配置,不再贴代码了,看下消费者配置:
<!-- SessionAwareMessageListener方式 begin -->
<!-- 消息监听器 -->
<bean id="sessionAwareMsgListener" class="com.gh.test.mq.receive.SessionAwareMsgReceiver" >
<property name="destination" ref="msgTopic"></property>
</bean>
<!--消息监听容器 -->
<bean id="sessionAwareContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="msgTopic" />
<property name="messageListener" ref="sessionAwareMsgListener" />
</bean>
<!-- SessionAwareMessageListener方式 end -->
SessionAwareMsgReceiver这个类继承了SessionAwareMessageListener,它是Spring为我们提供的,不是标准的JMS MessageListener。MessageListener的设计只是纯粹用来接收消息的,假如我们在使用MessageListener处理接收到的消息时我们需要发送一个消息通知对方我们已经收到这个消息了,那么这个时候我们就需要在代码里面去重新获取一个Connection或Session。SessionAwareMessageListener的设计就是为了方便我们在接收到消息后发送一个回复的消息,它同样为我们提供了一个处理接收到的消息的onMessage方法,但是这个方法可以同时接收两个参数,一个是表示当前接收到的消息Message,另一个就是可以用来发送消息的Session对象。来看下消费者的代码:
public class SessionAwareMsgReceiver implements SessionAwareMessageListener<Message>{
private static Logger log = Logger.getLogger(SessionAwareMsgReceiver.class);
public void onMessage(Message message, Session session) throws JMSException {
//前提知道生产者发的是个TextMessage类型
TextMessage msg = (TextMessage) message;
System.out.println("消息内容是:" + msg.getText());
//回传一条消息给目的地
MessageProducer producer = session.createProducer(destination);
Message textMessage = session.createTextMessage("SessionAwareMsgReceiver已经成功收到消息。。。");
producer.send(textMessage);
}
private Destination destination;
public Destination getDestination() {
return destination;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
}
在上面代码中定义了一个SessionAwareMessageListener,在这个Listener中我们在接收到了一个消息之后,利用对应的Session创建了一个到destination的生产者和对应的消息,然后利用创建好的生产者发送对应的消息。OK,来测试一下:依然通过界面点击发起Action调用发送消息的方式,代码如下:
("sendMessageAction")
public class SendMessageAction {
private String msg;
private MsgSend msgSend;
public String sendMessage() {
try{
String str = "test send textMessage";
msgSend.SendMsg(str);
msg="发送成功";
}catch(Exception e){
msg="发送失败";
e.printStackTrace();
}
return "success";
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
这里就用到了上面所说的发送工具类,我这里就很简单的写了一个发送方法,其实这里可以扩展很多的工具方法,对应我们想发送的不同的消息类型。来看下代码。
public class MsgSend {
public void SendMsg(Object obj){
msgJmsTemplate.convertAndSend(msgTopic, obj);//采用转换器发送
}
private JmsTemplate msgJmsTemplate;
private Destination msgTopic;
public JmsTemplate getMsgJmsTemplate() {
return msgJmsTemplate;
}
public void setMsgJmsTemplate(JmsTemplate msgJmsTemplate) {
this.msgJmsTemplate = msgJmsTemplate;
}
public Destination getMsgTopic() {
return msgTopic;
}
public void setMsgTopic(Destination msgTopic) {
this.msgTopic = msgTopic;
}
}
上面的代码采用了转换器发送,还记得上面生产者的配置中,有<property name="messageConverter" ref="msgConverter"/>这么一句话吧,这个就是说不使用它默认的转换器,直接用我自定义的消息转换器,来看下它的代码。
public class MsgConverter implements MessageConverter{
("unchecked")
public Message toMessage(Object object, Session session)
throws JMSException, MessageConversionException {
if(object instanceof Map){
Map<String,String> map = (Map<String, String>) object;
MapMessage msg = session.createMapMessage();
msg.setString("id", MapUtils.getString(map, "id"));
msg.setString("name", MapUtils.getString(map, "name"));
msg.setString("value", MapUtils.getString(map, "value"));
return msg;
}else if(object instanceof String){
String str = (String) object;
TextMessage msg = session.createTextMessage(str);
return msg;
}else{
return null;
}
}
public Object fromMessage(Message message) throws JMSException,
MessageConversionException {
if (message instanceof MapMessage){
MapMessage msg = (MapMessage) message;
StringBuilder mapStr = new StringBuilder("id:"+msg.getString("id")+";name:"+msg.getString("name")+";value:"+msg.getString("value"));
return mapStr.toString();
}else if(message instanceof TextMessage){
TextMessage msg = (TextMessage) message;
String msgStr = "this is textMessage:" + msg.getText();
return msgStr;
}else{
return message;
}
}
}
我们现在再来理一下这个过程:Action中通过工具类msgSend.SendMsg(str)方法,MsgSend工具类采用的是转换器的发送方式,所以到了MsgConverter类的toMessage(Object object, Session session)方法。这里就是讲我们的刚刚的String对象转换成Message,然后我们的消费者SessionAwareMsgReceiver收到了这条消息,又返回了了一条消息到目的地那里。所以这个消息又被监听到了,然后又走了一遍这个过程,是的你没想错,会一直走下去,停不下来。所以上面所说的Session估计不是这么用的,具体估计还有更好的方式,只是我没用过这种方式,也不知道。。。
MessageListenerAdapter方式
MessageListenerAdapter类实现了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是将接收到的消息进行类型转换,然后通过反射的形式把它交给一个普通的Java类进行处理。简单的说呢:就是我可以自己定义一个类,写一个方法,参数也可以自定义,然后就用这个类来接收消息,不用再继承和实现其他类或者接口。
MessageListenerAdapter会把接收到的消息做如下转换:TextMessage转换为String对象;
BytesMessage转换为byte数组;
MapMessage转换为Map对象;
ObjectMessage转换为对应的Serializable对象。
所以呢,由于这种方式比较灵活,一般采用的也比较多,目前我公司这边一个挺大的项目就是采用这种方式。来看下配置:
<!-- MessageListenerAdapter方式 begin -->
<!-- 消息监听器 -->
<bean id="msgListenerOfAdapter" class="com.gh.test.mq.receive.MsgAdapterReceiver" />
<!-- 消息监听适配器 -->
<bean id="msgListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="msgListenerOfAdapter"/>
<property name="defaultListenerMethod" value="receiveMsg"/>
<property name="messageConverter" ref="msgConverter"/>
</bean>
<!-- 消息监听适配器对应的监听容器 -->
<bean id="msgListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="msgTopic" />
<property name="messageListener" ref="msgListenerAdapter" />
</bean>
<!-- MessageListenerAdapter方式 end -->
MsgAdapterReceiver这个就是一个普通的类,没有太多道道。重点在于msgListenerAdapter这个的配置,通过设置delegate属性让MQ知道我要采用msgListenerOfAdapter这个类来接收消息,通过defaultListenerMethod这个属性让MQ知道我要采用
receiveMsg(自定义)的方法来实现接收逻辑。这里一般需要messageConverter消息转换配置,因为我们自定义的方法参数可以使多种多样,所以需要我们自己去转换。
我们依次看下代码,先是消费者:
public class MsgAdapterReceiver {
public void receiveMsg(String message){
System.out.println("收到消息:"+message);
}
}
很简单吧,就一个方法,参数为String。那么为了体现转换器的作用,Action中我讲发一个Map消息,然后转换器会转成String,被消费者所接收,看下Action代码:
("sendMessageAction")
public class SendMessageAction {
private String msg;
private MsgSend msgSend;
public String sendMessage() {
try{
Map map = new HashMap();
map.put("id", "1001");
map.put("name", "浩浩");
map.put("value", "你好啊");
msgSend.SendMsg(map);
msg="发送成功";
}catch(Exception e){
msg="发送失败";
e.printStackTrace();
}
return "success";
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
解释下代码流程:Action中一个map类型的消息,通过工具类发送,工具类采用的是转换器方式发送,重点来了。发送者进入toMessage方法,将Map里面的消息转成了MapMessage。到了接收阶段,由于消费者msgListenerAdapter的参数也配置了转换器,所以会先进转换器MsgConverter的fromMessage方法,前一阶段map消息已经被转成了MapMessage,所以也就进了第一个if,这里我把它转成了String,因为我们的消费者的入参就是String,所以也就可以被消费了。代码over。
看下结果:
收到消息:id:1001;name:浩浩;value:你好啊
MessageListenerAdapter除了会自动的把一个普通Java类当做MessageListener来处理接收到的消息之外,其另外一个主要的功能是可以自动的发送返回消息。这里由于我本人没研究过,项目中也没用过,所以不再阐述,有机会再说这个吧。
上述内容只是个人项目中所用的经验,如有问题和错误,定当虚心学习。