Spring Boot 結合 Redis 序列化配置的一些問題

  • 2020 年 3 月 18 日
  • 筆記

前言

最近在學習Spring Boot結合Redis時看了一些網上的教程,發現這些教程要麼比較老,要麼不知道從哪抄得,運行起來有問題。這裡分享一下我最新學到的寫法

默認情況下,Spring 為我們提供了一個 RedisTemplate 來進行對 Redis 的操作,但是 RedisTemplate 默認配置的是使用Java本機序列化。

這種序列化方式,對於操作字元串或數字來說,用起來還行,但是如果要對對象操作,就不是那麼的方便了。

所以我們需要配置合適的序列化方式。在 Spring 官方的文檔中,官方也建議了我們使用其他的方式來進行序列化。比如JSON

 

https://docs.spring.io/spring-data/redis/docs/2.2.5.RELEASE/reference/html/#redis:serializer

配置類

配置 Jackson2JsonRedisSerializer 序列化策略

下面就開始自動配置類的書寫

我使用的是 Jackson2JsonRedisSerializer 來對對象進行序列化,所以首先需要一個方法,來配置 Jackson2JsonRedisSerializer 序列化策略

private Jackson2JsonRedisSerializer<Object> serializer() {          // 使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值          Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);          ObjectMapper objectMapper = new ObjectMapper();            // 指定要序列化的域,field,get和set,以及修飾符範圍,ANY是都有包括private和public          objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);            // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常          objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);          return jackson2JsonRedisSerializer;      }

這裡要注意的是

objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

這一句,這一句非常的重要,作用是序列化時將對象全類名一起保存下來

設置之後的序列化結果如下:

[      "com.buguagaoshu.redis.model.User",      {          "name": "1",          "age": "11",          "message": "牛逼"      }  ]

不設置的話,序列化結果如下,將無法反序列化

    {          "name": "1",          "age": "11",          "message": "牛逼"      }

一開始,我在網上搜了一下,發現大多數教程因為時間的原因,這一句用的是

objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

但當我把這段程式碼寫入的時候,發現Idea提示我

著是一個過時的方法,由於我當時並不知道這句話的意思,就把這段程式碼注釋了,覺得可能沒什麼用,但注釋後在向Redis里寫數據的時候,數據會變成

導致數據無法反序列化。

 

最後我查看了這個方法的源碼,找到了

 

 

通過注釋,我得到了這段程式碼的最新寫法。

也明白了這段程式碼的作用。

 

配置  RedisTemplate

@Bean      public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {          RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();          redisTemplate.setConnectionFactory(redisConnectionFactory);          // 用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值          redisTemplate.setValueSerializer(serializer());            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();          // 使用StringRedisSerializer來序列化和反序列化redis的key值          redisTemplate.setKeySerializer(stringRedisSerializer);            // hash的key也採用String的序列化方式          redisTemplate.setHashKeySerializer(stringRedisSerializer);          // hash的value序列化方式採用jackson          redisTemplate.setHashValueSerializer(serializer());          redisTemplate.afterPropertiesSet();          return redisTemplate;      }

這裡就沒有什麼需要注意的了,按照自己的需求,來配置序列化的方式

 

配置快取策略

    @Bean      public CacheManager cacheManager(RedisConnectionFactory factory) {          RedisSerializer<String> redisSerializer = new StringRedisSerializer();          // 配置序列化(解決亂碼的問題)          RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()                  // 快取有效期                  .entryTtl(timeToLive)                  // 使用StringRedisSerializer來序列化和反序列化redis的key值                  .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))                  // 使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值                  .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer()))                  // 禁用空值                  .disableCachingNullValues();            return RedisCacheManager.builder(factory)                  .cacheDefaults(config)                  .build();      }

測試程式碼

@SpringBootTest  public class RedisApplicationTests {      @Autowired      private RedisTemplate<String, Object> redisTemplate;          @Test      void contextLoads() throws Exception {          User user = new User();          user.setName("15");          user.setAge(20);          user.setMessage("牛逼");          redisTemplate.opsForValue().set(user.getName(), user);          User getUser = (User) redisTemplate.opsForValue().get(user.getName());          System.out.println(getUser);          System.out.println(getUser.getMessage());      }    }

再來查看Redis中的數據

數據正常,並且系統也能正常的反序列化了。

 

完整程式碼

package com.buguagaoshu.redis.config;      import com.fasterxml.jackson.annotation.JsonAutoDetect;  import com.fasterxml.jackson.annotation.PropertyAccessor;  import com.fasterxml.jackson.databind.ObjectMapper;  import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.cache.CacheManager;  import org.springframework.cache.annotation.CachingConfigurerSupport;  import org.springframework.cache.annotation.EnableCaching;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.data.redis.cache.RedisCacheConfiguration;  import org.springframework.data.redis.cache.RedisCacheManager;  import org.springframework.data.redis.connection.RedisConnectionFactory;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;  import org.springframework.data.redis.serializer.RedisSerializationContext;  import org.springframework.data.redis.serializer.RedisSerializer;  import org.springframework.data.redis.serializer.StringRedisSerializer;    import java.time.Duration;    /**   * @author Pu Zhiwei {@literal [email protected]}   * create          2020-03-17 21:08   * 繼承 CachingConfigurerSupport,為了自定義生成 KEY 的策略。可以不繼承。   */  @Configuration  @EnableCaching  public class RedisConfig extends CachingConfigurerSupport {      @Value("${spring.cache.redis.time-to-live}")      private Duration timeToLive = Duration.ZERO;        /**       * 配置Jackson2JsonRedisSerializer序列化策略       * */      private Jackson2JsonRedisSerializer<Object> serializer() {          // 使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值          Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);          ObjectMapper objectMapper = new ObjectMapper();            // 指定要序列化的域,field,get和set,以及修飾符範圍,ANY是都有包括private和public          objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);            // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常          objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);          return jackson2JsonRedisSerializer;      }          @Bean      public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {          RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();          redisTemplate.setConnectionFactory(redisConnectionFactory);          // 用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值          redisTemplate.setValueSerializer(serializer());            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();          // 使用StringRedisSerializer來序列化和反序列化redis的key值          redisTemplate.setKeySerializer(stringRedisSerializer);            // hash的key也採用String的序列化方式          redisTemplate.setHashKeySerializer(stringRedisSerializer);          // hash的value序列化方式採用jackson          redisTemplate.setHashValueSerializer(serializer());          redisTemplate.afterPropertiesSet();          return redisTemplate;      }              @Bean      public CacheManager cacheManager(RedisConnectionFactory factory) {          RedisSerializer<String> redisSerializer = new StringRedisSerializer();          // 配置序列化(解決亂碼的問題)          RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()                  // 快取有效期                  .entryTtl(timeToLive)                  // 使用StringRedisSerializer來序列化和反序列化redis的key值                  .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))                  // 使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值                  .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer()))                  // 禁用空值                  .disableCachingNullValues();            return RedisCacheManager.builder(factory)                  .cacheDefaults(config)                  .build();      }  }