接码字。。。

5.6. 方便操作String类型的类

一般情况下对于存于Redis的key和value都是String类型的数据。为此Redis模块提供两个扩展RedisConnection和RedisTemplate的实现类,它们分别为StringRedisConnection(和他默认实现类DefaultStringRedisConnection)和StringRedisTemplate,提供对String类型一站式操作。除了绑定String的key,template和connection都要采用StringRedisSerializer进行序列化,这就意味找存储的键值具有可读性(假设你和Redis使用相同的编码),例如:

<?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:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>

  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
  ...
</beans>



public class Example {

  @Autowired
  private StringRedisTemplate redisTemplate;

  public void addLink(String userId, URL url) {
    redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}


类似其他Spring的template,RedisTemplate和StringRedisTemplate允许开发者通过RedisCallback回调接口直接与Redis进行通讯,开发者就可以通过RedisConnection进行完全的控制Redis。注意当一个StringRedisTemplate被使用这个回调接口将会收到一个StringRedisConnection实例。

public void useCallback() {

  redisTemplate.execute(new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      Long size = connection.dbSize();
      // Can cast to StringRedisConnection if using a StringRedisTemplate
      ((StringRedisConnection)connection).set("key", "value");
    }
   });
}

5.7.(Serializers)序列化

从框架的视图来看,存储在Redis的数据就是字节。Redis它自己支持多种数据类型。对于大多数存储的是数据的引用而不是它所代表的数据。这要取决于这些信息被转换成字符串还是任何其他对象。这种转换出现在用户自定义类型和原生数据类型(反之亦然)。这种转换可以通过实现RedisSerializer接口具体的类决定。(org.springframework.data.redis.serializer),多数都是开箱即用。有两个已经在文档中提及了:StringRedisSerializer和JdkSerializationRedisSerializer,然而一个可以使用OxmSerializer 对于Object/xml进行序列化。不过需要Spring 3 OXM 进行支持。JacksonJsonRedisSerializer、Jackson2JsonRedisSerializer或者GenericJackson2JsonRedisSerializer对于JSON格式的数据进行序列化。注意对于格式化不限于value(值),它可以用到keys(键)、hashes等等等。

5.8.Hash Mapping

Redis可以存储多种数据类型。你已经学习到Jackson2JsonRedisSerializer可以转化JSON格式数据。JSON可以值,而键可以简单字符串。多数复杂mapping结构对象也是可以使用Redis Hashes进行存储。Spring Data Redis 提供多种策略去解决mapping数据的Hash值问题。这主要取决于用户使用场景。

1、直接使用HashOperations和序列化去操作mapping

2、使用RedisRepositories

3、使用HashMapper和HashOperations

5.8.1. Hash mappers

Hash mappers是转换器--将map对象转换成Map<K, V>。HashMapper与Redis Hashes进行搭配使用。

多个开箱即用的实现类

1.  BeanUtilsHashMapper 使用的是Spring的BeanUtils

2. ObjectHashMapper 使用的Object to Hash Mapping

3.Jackson2HashMapper 使用的FasterXML Jackson.

public class Person {
  String firstname;
  String lastname;

  // …
}

public class HashMapping {

  @Autowired
  HashOperations<String, byte[], byte[]> hashOperations;

  HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper();

  public void writeHash(String key, Person person) {

    Map<byte[], byte[]> mappedHash = mapper.toHash(person);
    hashOperations.putAll(key, mappedHash);
  }

  public Person loadHash(String key) {

    Map<byte[], byte[]> loadedHash = hashOperations.entries("key");
    return (Person) mapper.fromHash(loadedHash);
  }
}



5.8.2. Jackson2HashMapper

Jackson2HashMapper 使用FasterXML Jackson为域对象提供Redis Hash映射.Jackson2HashMapper可以映射最外层的属性作为Hash字段名,也可以将json进行扁平化。简单的map对简单的值,复制类型(内部类,集合,map)代表内部json。

可以为所有属性进行扁平化。复杂类型尽可能分解成简单类型。

public class Person {
  String firstname;
  String lastname;
  Address address;
}

public class Address {
  String city;
  String country;
}



表格2.正常映射

HashField

Value

firstname

Jon

lastname

Snon

address

{"city":"Castle Black","country":"The North"}

笔者:看来Spring Data Redis 也是很喜欢《权利的游戏》,O(∩_∩)O~

表格3.扁平映射

HashField

Value

firstname

Jon

lastname

Snow

address.city

Castle Black

address.country

The North


提示:

扁平化所有的属性名都不要以路径命名,(也就是不能出现 address.city),使用点或括弧的键组成json不能扁平化。它结果Hash也不能映射成一个对象。

5.9.Redis Messaging/PubSub (Redis的订阅与发布)

Spring Data 提供专门的方法去整合Redis的信息相关。它在命名和功能上非常类似Spring框架整合JMS,事实上,如果读者非常熟悉Spring对于JMS操作,那么对于Redis的信息操作就是轻车熟路。

Redis 信息粗略从功能和命名上分为两个大部分,即生产或发布信息和消费或订阅信息。它们简写就是pubsub(Publish/Subscribe).RedisTemplate 类用作信息生产。对于异步接受类似与Java EE 的消息驱动 bean的风格,Spring Data提供一个专门消息监听容器,它被用来创建消息驱动(Message-Driven POJOS),对于同步 请求需要尊徐RedisConnection的协议。

在包org.springframework.data.redis.conneciton和org.springframework.data.redis.listener为Redis 信息提供核心的功能

5.9.1.Sending/Publishing messages(发送消息)

对于发送一个消息,不管是对于低级RedisConnection还是高级RedisTemplate都是可以完成的。两个实体都提供带有一个消息参数的发送方法。并且需要目标的地址。唯一不同的是RedisConnection需要的原生数据(字节数组),而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");

5.9.2. Receiving/Subscribing for messages(接收消息)

在接收一方,它可以订阅一个或者多个(Channel)频道,它是通过名称或者匹配规则进行订阅。当使用一条命令订阅多个频道的时候,后者(匹配规则)方式相当有用,同时在监听的时候不需要花费创建订阅的时间(只有匹配上才会创建)。

在低级抽象化操作类,RedisConnection提供subscribe和pSubscribe方法,这个方法映射到Redis命令去订阅匹配的渠道。注意多个渠道或者模式都可以被用作参数。为了改变订阅一个连接或简单查询是否被监听,RedisConnection提供了getSubscription 和 isSubscribed方法。

提示:订阅命令在SpringData是阻塞式的,也就是,当在一个连接上调用订阅命令,那么当前线程就处于阻塞状态,它将开始等待消息到来,只有订阅被取消这个线程才会被释放,也就是额外线程在相同连接上调用了unsubscribe或pUnsubscribe方法,详情请参考message listener container去解决这个问题。

如上所述。一旦连接上订阅之后就会等待消息。没有其他命令可以被调用,除非增加新的订阅或者修改取消现有的这个。也就是说,调用其他subscribe、pSubscribe,unsubscribe,或者pUnsubscribe都是非法的,它也将会抛出异常。

为了实现订阅消息就需要实现MessageListener回调函数:每一次信息消息到达的时候,这个回调函数将会被调用,用户写在onMessage 方法内代码将会被执行。这个接口可以获取不仅仅是实际的消息,对于采用匹配规则匹配的渠道也是可以接收消息的。这些消息允许callee(执行中对象)不相同的。它不仅仅是内容,可以是数据。

Message Listener Containers(消息监听容器)

由于订阅的阻塞特性,低级的订阅不那么吸引人了,他要求连接和线程管理每一次监听。为解决这问题,SpringData提供RedisMessageLIstenerContainer,它将会为用户做很多繁杂的事情。如果用户熟悉EJB和JMS这个概念熟悉,它被设计尽可能支持Spring 框架,它就是message-driven POJOS(MDPs消息驱动)。


RedisMessageListenerContainer作为消息监听容器角色。它被用来接收来自Redis 频道的信息。然后驱动MessageListener将会信息注入其中。这个监听容器的职责就是接收所有线程的消息,然后分发给监听器的过程。消息监听容器就是MDP和消息提供者的中间人。它关心注册接收消息,资源获取和释放,以及异常转换。它让你专注于业务逻辑代码,那些样板的代码交给框架去解决。


尽可能使用应用不用关心底层代码

RedisMessageListenerContainer 允许一个连接和一个线程被多个监听器共享,甚至它们不需要分享同一个订阅,所以一个应用无论有多少监听器或者频道,在生命周期中运行消耗将会保持相同。更进一步,容器允许运行时改变配置。比如移除或添加一个监听器,它是可以不用重启的。除此之外,容器使用的延迟订阅模式,只有需要的时候RedisConnection才会被创建,如果所有监听器被取消订阅,清除将会自动完成,监听所有使用的线程将会被释放。


为帮助异步消息处理,容器需要一个java.util.concurrent.Executor(或 Spring's TaskExecutor)去分发消息。对于监听器数量或者运行环境都要取决于加载,它应该进行稍微的调整和改变executor使得服务器达到更好的状态.特别是在管理环境(例如app 服务器),强烈推荐选择合适的TaskExecutor提供性能。


The MessageListenerAdapter (消息适配器)

MessageListenerAdapter 是Spring异步消息支持的最终类:在 nutshell中,它允许你暴露几乎任何类作为MDP(这里当然有些限制)。


考虑到接口定义。注意虽然这个接口并没有继承MessageListener接口。它仍然可以通过使用MessageListenerAdapter把它被作为MDP,注意这些消息处理方法的类型是强类型,它根据是各种消息类型,它都可以被接收和处理,除此之外,频道或模式将会作为第二个参数传入方法当中。

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"?>
 <beans 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">

<!-- the default ConnectionFactory -->
<redis:listener-container>
  <!-- the method attribute can be skipped as the default method name is "handleMessage" -->
  <redis:listener ref="listener" method="handleMessage" topic="chatroom" />
</redis:listener-container>

<bean id="listener" class="redisexample.DefaultMessageDelegate"/>
 ...
<beans>


提示:这里话题要么是频道(例如, topic="chatroom")或模式(例如:topic="*room")

以上例子使用Redis的命名空间去声明一个消息监听容器,它将自动将会POJO类注册成监听器。全文如下,beans定义如下

<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
  <constructor-arg>
    <bean class="redisexample.DefaultMessageDelegate"/>
  </constructor-arg>
</bean>

<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
  <property name="connectionFactory" ref="connectionFactory"/>
  <property name="messageListeners">
    <map>
      <entry key-ref="messageListener">
        <bean class="org.springframework.data.redis.listener.ChannelTopic">
          <constructor-arg value="chatroom">
        </bean>
      </entry>
    </map>
  </property>
</bean>

每一次消息被接收的时候,这个适配器将会自动(使用配置RedisSerializer)在低级格式和要求对象类型透明的转换,任何方法调用产生的异常将会被容器捕获和处理(默认情况记录在日志中)。

待续。。。。。。。。。。。。。