参考文档: https://docs.spring.io/spring-data/redis/docs/2.0.3.RELEASE/reference/html/
Redis中文教程: http://www.redis.net.cn/tutorial/3501.html
MDP:(Message Driven POJO)消息驱动POJO
从框架的角度来看,存储在Redis中的数据只是bytes。虽然Redis本身支持各种类型,但大多数情况下这些指的是存储数据的方式,而不是它所代表的内容。由用户决定是否将信息转换为字符串或任何其他对象。
用户(自定义)类型和原始数据(反之亦然)之间的转换是在org.springframework.data.redis.serializer包中的Spring Data Redis中处理的。
这个包包含两种类型的serializers,顾名思义,它负责序列化过程:
这些变化之间的主要区别在于,RedisSerializer主要序列化为byte [],而readers和writers使用ByteBuffer。
多种实现都是即插即用的,其中两种已经在本文档中提到过了:
StringRedisSerializer
JdkSerializationRedisSerializer
无论怎样,一种是可以通过Spring OXM支持使用OxmSerializer进行Object/XML映射,或者使用Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer以JSON格式存储数据。
请注意,存储格式不仅限于values - 它可以用于keys, values, hashes,没有任何限制。
数据可以使用Redis中的各种数据结构进行存储。 您已经了解到可以转换JSON格式的对象的Jackson2JsonRedisSerializer。 理想情况下,JSON可以使用plain keys普通键存储为值。 使用Redis Hashes可以实现更复杂的结构化对象映射。 Spring Data Redis提供了根据用例将数据映射到hashes的各种策略。
Direct mapping using HashOperations
and a serializer
Using Redis Repositories
Using HashMapper
and HashOperations
Hash mappers 是转换器用于映射objects 到一个 Map
并返回. HashMapper
用于 Redis Hashes.
以下是多种即插即用的实现:
BeanUtilsHashMapper
using Spring’s BeanUtils.
ObjectHashMapper
using Object to Hash Mapping.
Jackson2HashMapper
using FasterXML Jackson.
public class Person {
String firstname;
String lastname;
// …
}
public class HashMapping {
@Autowired
HashOperations hashOperations;
HashMapper
Jackson2HashMapper使用FasterXML Jackson为Domain objects域对象提供Redis Hash mapping。
Jackson2HashMapper可以将数据映射到Hash的最上层(第一层)的Keys-Values,并可选地将结构展平。 简单类型映射到简单值。 复杂类型(嵌套对象,集合,映射;nested objects, collections, maps)表示为嵌套JSON。
展平会为所有嵌套属性创建单独的hash entries,并尽可能将复杂类型解析为简单类型。
public class Person {
String firstname;
String lastname;
Address address;
}
public class Address {
String city;
String country;
}
Hash Field | Value |
---|---|
firstname |
|
lastname |
|
address |
|
Hash Field | Value |
---|---|
firstname |
|
lastname |
|
address.city |
|
address.country |
|
注意:
展平需要所有属性名称不会干扰JSON路径。 属性名称不要使用使用点 . 或 括号(),否则 生成的Hash无法映射回对象。
Spring Data为Redis提供了专用的消息集成,在功能和命名上与Spring Framework中的JMS集成非常相似; 实际上,熟悉Spring中JMS支持的用户应该感到宾至如归。
Redis消息可以大致分为两个功能区域,即生产或发布和消费或订阅消息,因此是快捷方式pubsub(发布/订阅)。 RedisTemplate类用于消息生成。 对于类似于Java EE的消息驱动Bean风格的异步接收,Spring Data提供了一个专用的消息侦听器容器,用于创建消息驱动的POJO(MDP)和同步接收RedisConnection协定。
包org.springframework.data.redis.connection和org.springframework.data.redis.listener提供了使用Redis消息传递的核心功能。
要发布消息,可以像使用其他操作一样,使用低层的RedisConnection或高级的RedisTemplate。 两个实体都提供发布方法,该方法接受需要发送的消息以及目标channel作为参数。
虽然RedisConnection需要原始数据(字节bytes数组),但RedisTemplate允许将任意对象作为消息传入:
// send message through connection RedisConnection con = ...
byte[] msg = ...
byte[] channel = ...
con.publish(msg, channel); // send message through RedisTemplate
RedisTemplate template = ...
template.convertAndSend("hello!", "world");
在底层,RedisConnection提供了subscribe和pSubscribe方法,它们分别按照channel和pattern映射Redis命令。请注意,多个channel或pattern是可以用作参数的。要更改connection的subscription或简单查询是否正在侦听,RedisConnection会提供getSubscription和isSubscribed方法。
Spring Data Redis中的订阅命令是阻塞的。 也就是说,在连接上调用订阅会导致当前线程阻塞,因为它将开始等待消息 - 只有在订阅被取消时,线程才会被释放,这是额外的线程在相同的连接上调用退订或取消订阅。 有关此问题的解决方案,请参阅下面的。
如上所述,一旦订阅连接开始等待消息。 除了添加新订阅或修改/取消现有订阅之外,不能调用其他命令,都是非法的,并且会抛出异常。由于其阻塞的特性,低级订阅并不具吸引力,因为它需要每个单一监听器的连接和线程管理。为了缓解这个问题,Spring Data提供了RedisMessageListenerContainer,它代表用户完成所有繁重的工作 - 熟悉EJB和JMS的用户应该找到熟悉的概念,因为它尽可能地接近Spring Framework和message-driven POJO(MDPs)
RedisMessageListenerContainer充当消息侦听器容器;它用于接收来自Redis channel的消息并注入其中的MessageListener。RedisMessageListenerContainer负责所有线程的消息接收和分派到listener进行处理。RedisMessageListenerContainer是MDP和消息传递提供程序之间的中介,负责注册以接收消息,资源获取和释放,异常转换等。这允许您作为应用程序开发人员编写与接收消息(并对其作出响应)相关联(可能是复杂的)业务逻辑,并将Redis基础架构关注事项的样板委托给框架。
此外,为了最大限度地减少应用程序的占用空间,RedisMessageListenerContainer允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其整个生命周期内都保持不变。此外,容器允许运行时配置更改,以便在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用延迟订阅方式,仅在需要时才使用RedisConnection - 如果所有侦听器都取消订阅,则会自动执行清理并释放使用的线程。
为了帮助使用异步方式的消息,容器需要一个java.util.concurrent.Executor(或Spring的TaskExecutor)来分派消息。根据负载,监听器数量或运行时环境,应该更改或调整执行程序以更好地满足其需求 - 特别是在托管环境中(例如应用程序服务器),强烈建议选择合适的TaskExecutor它的运行时间的优势。.
MessageListenerAdapter类是Spring异步消息传递支持中的最后一个组件:简而言之,它允许您将几乎任何类作为MDP公开(当然有一些限制)。
考虑下面的接口定义。请注意,尽管接口并未扩展MessageListener接口,但它仍可以通过使用MessageListenerAdapter类用作MDP。 还要注意各种消息处理方法是如何根据它们可以接收和处理的各种消息类型的内容进行强类型化的。 另外,消息发送到的通道或模式可以作为String类型的第二个参数传递给方法:
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message); void handleMessage(byte[] message);
void handleMessage(Serializable message);
// pass the channel/pattern as well
void handleMessage(Serializable message, String channel);
}
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
特别要指出的是,MessageDelegate接口的上述实现(上面的DefaultMessageDelegate类)完全没有Redis依赖关系。 它确实是一个POJO,我们将通过以下配置将其制作为MDP。
xml version="1.0" encoding="UTF-8"?>
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:redis="http://www.springframework.org/schema/redis"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd">
ref="listener"method="handleMessage"topic="chatroom"/>
id="listener"class="redisexample.DefaultMessageDelegate"/>
...
topic="chatroom"
) or a pattern (e.g. topic="*room"
)
...
每次接收到消息时,适配器都会自动执行低级格式low-level-format与所需对象类型之间的透明转换(使用配置的RedisSerializer)。
由方法调用引起的任何异常都会被容器捕获并处理(默认情况下会被记录)。