在SDR(Spring Data Redis)中提供了一套基于Redis的消息集成,这个消息集成与在Sping框架中的JMS的在功能上和命名上非常相似,正因为如此开发者可以轻而易举的通过Redis的服务器实现消息的发布和订阅。
在SDR中实现消息发布订阅有两种方法,这里我只介绍其中一种比较简便的方法。这个方法是基于SDR提供的RedisMessageListenerContainer来实现的。
首先,添加Maven的依赖,如下图所示:
其次,加入Redis的配置文件:
接下来,由于Redis的消息机制是通过拆分为两个领域来实现的--发布和订阅。因而需要进行分别实现。
首先实现发布端:
package com.pcitc.lbopen.acs.sdk.redis.service;
import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.pcitc.lbopen.acs.sdk.security.message.SessionMessage;
import com.pcitc.lbopen.tools.basic.testcase.SpringContextTestCase;
/**
* Title: (一句话功能简述) Description: (功能详细描述)
*
* @Author :LiangPeng
* @Date :2015年7月17日
* @Version:1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:config/demo-springredis.xml")
public class RedisServiceTest extends SpringContextTestCase {
private StringRedisTemplate<String,String> redisTemplate;
private String uncacheChannel = "shiro.session.uncache";
@Test
public void SendMessageTest() {
String sessionId = "1111";
String host = ManagementFactory.getRuntimeMXBean().getName();
SessionMessage.MessageBody messageBody = new SessionMessage.MessageBody(sessionId, host);
redisTemplate.convertAndSend(uncacheChannel, messageBody);
System.out.println("发送成功");
}
}
在这里,采用了SDR提供的RedisTemplate类提供的CoverntAndSend方法进行发布。在这个方法中需要两个参数,一个是channel,另一个是消息体。定义的channel(也就是图中的uncacheChannel),这个channel要与订阅端的channel要保持一致,
只有这样,订阅端才能够接收到订阅的消息。同时,为了能够采用SDR的消息对象Message,使用的好处是这个对象可以在消息通道中自动实现序列化,我封装了一个继承DefaultMessage的ShiroMessage,该类中的静态类MessageBdoy正是需要channel传送的
消息对象体。以下ShiroMessage的实现。
package com.pcitc.lbopen.acs.sdk.security.message;
import java.io.Serializable;
import org.springframework.data.redis.connection.DefaultMessage;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
/**
* Session的消息对象
*/
public class SessionMessage extends DefaultMessage {
/**
* serialVersionUID:TODO(用一句话描述这个变量表示什么)
*
* @since Ver 1.0
*/
private static final long serialVersionUID = 1L;
private JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer();
public final MessageBody msgBody;
public SessionMessage(byte[] channel, byte[] body) {
super(channel, body);
msgBody = (MessageBody) redisSerializer.deserialize(body);
}
public static class MessageBody implements Serializable {
public final Serializable sessionId;
public final String host;
public String msg = "";
public MessageBody(Serializable sessionId, String host) {
this.sessionId = sessionId;
this.host = host;
}
public MessageBody(Serializable sessionId, String host,String msg) {
this.sessionId = sessionId;
this.host = host;
this.msg = msg;
}
@Override
public String toString() {
return "MessageBody{" +
"sessionId='" + sessionId + '\'' +
", nodeId='" + host + '\'' +
", msg='" + msg + '\'' +
'}';
}
}
}
接下来是实现订阅端。订阅端的类首先要实现MessageListener,在这个接口中只有onMessage方法,这个方法是用来实现当消息接收到后要进行的操作逻辑。为了便于统一,我封装了一个继承它的RedisMessageListener,这样在具体实现阶段,只需要实现该抽象类的handleMessage方法即可。
/**
* @Author :LiangPeng
* @version :0.1.1
* Date :2016年1月22日
*/
package com.pcitc.lbopen.acs.sdk.redis.service.impl;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
/**
* Description:信息接收
*/
public abstract class RedisMessageListener implements MessageListener{
@Override
public final void onMessage(Message message, byte[] pattern){
handleMessage(message);
}
public abstract void handleMessage(Message message);
}
下面是具体的订阅端的实现类。
* @Author :LiangPeng
* @version :0.1.1
* Date :2016年1月22日
*/
package com.pcitc.lbopen.acs.sdk.redis.service;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import com.pcitc.lbopen.acs.sdk.redis.service.impl.RedisMessageListener;
import com.pcitc.lbopen.acs.sdk.security.message.SessionMessage;
/**
* Description: (功能详细描述)
*/
public class RedisMessageListenerService extends RedisMessageListener{
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"classpath:config/demo-springredis.xml"});
while (true) { // 这里是一个死循环,目的就是让进程不退出,用于接收发布的消息
try {
System.out.println("current time: " + new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void handleMessage(Message message){
JdkSerializationRedisSerializer Serializer=new JdkSerializationRedisSerializer();
if (message == null) {
System.out.println("null");
}
else{
String channel =new String(message.getChannel());
System.out.println(channel);
System.out.println("----------");
SessionMessage.MessageBody msgbody=(SessionMessage.MessageBody)Serializer.deserialize(message.getBody());
Serializable sessionId=msgbody.sessionId;
String strHost=StringUtils.substringAfter(msgbody.host,"@");
System.out.println(sessionId);
System.out.println("----------");
System.out.println(strHost);
System.out.println("----------");
}
}
}
由于之前采用的DefaultMessage的继承类,因而在订阅端的实现方法中,还需要将消息体反序列化,这样才是我们发布前的对象(在这个类中加入的Main方法主要是为了能不间断的接收来自发布方的消息)。
最后,我还需要加入订阅方的配置,由于采用SDR的订阅机制,因而要进行如下配置:
<bean id="RedisMessageListenerService" class="com.pcitc.lbopen.acs.sdk.redis.service.RedisMessageListenerService"/>
<redis:listener-container connection-factory="jedisConnectionFactory">
<!-- the method attribute can be skipped as the default method name is "handleMessage" -->
<redis:listener ref="RedisMessageListenerService" topic="shiro.session.uncache"/>
</redis:listener-container>
配置中的Topic属性是用来指定名称的频道和模式匹配的频道。
这样就实现了一个SDR的消息发布和订阅的功能。