<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入解析Spring Boot中RedisTemplate的序列化機(jī)制與JSON存儲(chǔ)

          共 21378字,需瀏覽 43分鐘

           ·

          2024-04-11 21:40

          在昨天的文章(SpringBoot 響應(yīng) json 數(shù)據(jù)的后端源碼處理流程)中,有人留言說(shuō),讓我嘮叨嘮叨 SpringBoot 中 redis 的序列化器。

          redis 緩存序列化,用的人很多,但是很少有人關(guān)注底層的細(xì)節(jié),基于此,我就在寫一個(gè)姊妹篇,聊聊 SpringBoot 中 redis 相關(guān)的序列化知識(shí)點(diǎn)。

          插播一條 AD,阮一峰老師也推薦過(guò)的 Hulu AI,即將漲價(jià)。

          在 SpringBoot 中,RedisTemplate 比較常用。默認(rèn)使用 JdkSerializationRedisSerializer 來(lái)進(jìn)行序列化。但是默認(rèn)的序列化會(huì)有個(gè)問(wèn)題:Java 端存完了,取 redis-cli 執(zhí)行key *,會(huì)發(fā)現(xiàn)鍵值都帶有\\xAC\\xED\\x00\\x05t\\x00\\x05這種字符,不方便查看,而且這如果在集群環(huán)境下會(huì)導(dǎo)致問(wèn)題!

          下面,我們就從 RedisTemplate 聊起吧。

          RedisTemplate

          可以看到 4 個(gè)序列化相關(guān)的屬性 ,主要是用于KEYVALUE的序列化,比如說(shuō)我們經(jīng)常會(huì)將 POJO 對(duì)象存儲(chǔ)到Redis中,一般情況下會(huì)使用JSON方式序列化成字符串存儲(chǔ)到Redis中 。

          SpringBoot 提供的 Redis 數(shù)據(jù)結(jié)構(gòu)的操作類。

          • ValueOperations 類,提供 Redis String API 操作
          • ListOperations 類,提供 Redis List API 操作
          • SetOperations 類,提供 Redis Set API 操作
          • ZSetOperations 類,提供 Redis ZSet(Sorted Set) API 操作
          • GeoOperations 類,提供 Redis Geo API 操作
          • HyperLogLogOperations 類,提供 Redis HyperLogLog API 操作

          StringRedisTemplate

          RedisTemplate 支持泛型,StringRedisTemplate K/V 均為 String 類型。

          org.springframework.data.redis.core.StringRedisTemplate 繼承 RedisTemplate 類,使用 org.springframework.data.redis.serializer.StringRedisSerializer字符串序列化方式。

          RedisSerializer序列化接口

          RedisSerializer接口是 Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化。

          RedisSerializer 接口的實(shí)現(xiàn)類如下:

          默認(rèn)為 Redis 提供了 11 中的序列化方式,歸類一下主要分為:

          • JDK序列化方式(默認(rèn))
          • String序列化方式
          • JSON序列化方式
          • XML序列化方式

          JDK序列化方式(默認(rèn))

          org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,默認(rèn)不配置的情況 RedisTemplate 采用的是該數(shù)據(jù)序列化方式,可以查看一下源碼:

          SpringBoot 自動(dòng)化配置 RedisTemplate Bean 對(duì)象時(shí),就未設(shè)置默認(rèn)的序列化方式。絕大多數(shù)情況下,并不推薦使用 JdkSerializationRedisSerializer 進(jìn)行序列化。主要是不方便人工排查數(shù)據(jù)。我們來(lái)做個(gè)測(cè)試:

          運(yùn)行單元測(cè)試:

          發(fā)現(xiàn) key 跟 value 的值都是 16 進(jìn)制字符串,可以看到 key 跟 value 實(shí)際上保存的都是以byte[]字節(jié)數(shù)組的格式存儲(chǔ):

          key 被序列化成這樣,線上通過(guò) key 去查詢對(duì)應(yīng)的 value 非常不方便,所以 key 肯定是不能被這樣序列化的。value 被序列化成這樣,除了閱讀可能困難一點(diǎn),不支持跨語(yǔ)言外,實(shí)際上也沒多大問(wèn)題。不過(guò),實(shí)際線上場(chǎng)景,還是使用 JSON 序列化居多。

          String序列化方式

          org.springframework.data.redis.serializer.StringRedisSerializer,字符串和二進(jìn)制數(shù)組都直接轉(zhuǎn)換:

          默認(rèn)的話,StringRedisTemplate的 key 和 value 采用的就是這種序列化方案。

          JSON序列話方式

          GenericJackson2JsonRedisSerializer

          org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer 使用 Jackson 實(shí)現(xiàn) JSON 的序列化方式,Generic 單詞翻譯過(guò)來(lái)表示:通用的意思,可以看出,是支持所有類。

          RedisConfig配置

          通過(guò)配置方式選擇對(duì)應(yīng) Redis 數(shù)據(jù)的序列化方式,配置如下:

          package com.example.redis.serializer.config;

          import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
          import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
          import com.fasterxml.jackson.annotation.JsonAutoDetect;
          import com.fasterxml.jackson.annotation.JsonTypeInfo;
          import com.fasterxml.jackson.annotation.PropertyAccessor;
          import com.fasterxml.jackson.databind.ObjectMapper;
          import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
          import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.data.redis.connection.RedisConnectionFactory;
          import org.springframework.data.redis.core.RedisTemplate;
          import org.springframework.data.redis.core.StringRedisTemplate;
          import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
          import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
          import org.springframework.data.redis.serializer.RedisSerializer;
          import org.springframework.data.redis.serializer.StringRedisSerializer;

          /**
           * Redis配置
           */

          @Configuration
          public class RedisConfig {

              //GenericJackson2JsonRedisSerializer
              @Bean
              @ConditionalOnMissingBean(name = "redisTemplate")
              public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
                  RedisTemplate<String, Object> template = new RedisTemplate<>();
                  template.setConnectionFactory(factory);

                  //String的序列化方式
                  StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
                  // 使用GenericJackson2JsonRedisSerializer 替換默認(rèn)序列化(默認(rèn)采用的是JDK序列化)
                  GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

                  //key序列化方式采用String類型
                  template.setKeySerializer(stringRedisSerializer);
                  //value序列化方式采用jackson類型
                  template.setValueSerializer(genericJackson2JsonRedisSerializer);
                  //hash的key序列化方式也是采用String類型
                  template.setHashKeySerializer(stringRedisSerializer);
                  //hash的value也是采用jackson類型
                  template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
                  template.afterPropertiesSet();
                  return template;
              }

              //Jackson2JsonRedisSerializer
              //@Bean
              //@ConditionalOnMissingBean(name = "redisTemplate")
              //public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
              //    RedisTemplate<String, Object> template = new RedisTemplate<>();
              //    template.setConnectionFactory(factory);
              //
              //    //String的序列化方式
              //    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
              //    //使用Jackson2JsonRedisSerialize 替換默認(rèn)序列化(默認(rèn)采用的是JDK序列化)
              //    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
              //
              //    // 如果采用Jackson2JsonRedisSerializer序列化方式,沒有ObjectMapper配置在強(qiáng)轉(zhuǎn)對(duì)象的時(shí)候會(huì)反序列化失敗,也就是User user = (User) redisTemplate.opsForValue().get(key) 會(huì)失敗;
              //    ObjectMapper objectMapper = new ObjectMapper();
              //    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
              //    objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
              //    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
              //
              //    //key序列化方式采用String類型
              //    template.setKeySerializer(stringRedisSerializer);
              //    //value序列化方式采用jackson類型
              //    template.setValueSerializer(jackson2JsonRedisSerializer);
              //    //hash的key序列化方式也是采用String類型
              //    template.setHashKeySerializer(stringRedisSerializer);
              //    //hash的value也是采用jackson類型
              //    template.setHashValueSerializer(jackson2JsonRedisSerializer);
              //    template.afterPropertiesSet();
              //    return template;
              //}

              ////FastJsonRedisSerializer
              //@Bean("redisTemplate")
              //public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
              //    RedisTemplate<String, Object> template = new RedisTemplate<>();
              //    template.setConnectionFactory(factory);
              //
              //    //String序列化方式
              //    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
              //    // 使用FastJsonRedisSerializer替換默認(rèn)序列化(默認(rèn)采用的是JDK序列化)
              //    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
              //
              //    //key序列化方式采用String類型
              //    template.setKeySerializer(stringRedisSerializer);
              //    //value序列化方式采用jackson類型
              //    template.setValueSerializer(fastJsonRedisSerializer);
              //    //hash的key序列化方式也是采用String類型
              //    template.setHashKeySerializer(stringRedisSerializer);
              //    //hash的value也是采用jackson類型
              //    template.setHashValueSerializer(fastJsonRedisSerializer);
              //    template.afterPropertiesSet();
              //    return template;
              //}

          }

          運(yùn)行以下測(cè)試類:

          @Test
          void redisTemplateSerializeTest() {
              String redisTemplateStringKey = "redisTemplateStringKey";
              String redisTemplateUserObjectKey = "redisTemplateUserObjectKey";
              String redisTemplateUserArrayObjectKey = "redisTemplateUserArrayObjectKey";
              String redisTemplateJSONObjectKey = "redisTemplateJSONObjectKey";
              String redisTemplateJSONArrayKey = "redisTemplateJSONArrayKey";

              //序列化String類型和反序列化String類型
              redisTemplate.opsForValue().set(redisTemplateStringKey, "austin");
              String austin = (String) redisTemplate.opsForValue().get(redisTemplateStringKey);
              System.out.println("stringGet: " + austin);

              //序列化Object對(duì)象類型和反序列化Object對(duì)象類型 (User對(duì)象)
              User user = new User("123""austin"25);
              redisTemplate.opsForValue().set(redisTemplateUserObjectKey, user);
              User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
              System.out.println("userGet: " + userGet);

              //序列化Object對(duì)象數(shù)組類型和反序列化Object對(duì)象數(shù)組類型 (User[]對(duì)象數(shù)組)
              User user1 = new User("1""austin1"25);
              User user2 = new User("2""austin2"25);
              User[] userArray = new User[]{user1, user2};
              redisTemplate.opsForValue().set(redisTemplateUserArrayObjectKey, userArray);
              User[] userArrayGet = (User[]) redisTemplate.opsForValue().get(redisTemplateUserArrayObjectKey);
              System.out.println("userArrayGet: " + userArrayGet);

              //序列化JSONObject對(duì)象類型和反序列化JSONObject對(duì)象類型
              JSONObject jsonObject = new JSONObject();
              jsonObject.put("id""123");
              jsonObject.put("name""austin");
              jsonObject.put("age"25);
              redisTemplate.opsForValue().set(redisTemplateJSONObjectKey, jsonObject);
              JSONObject jsonObjectGet = (JSONObject) redisTemplate.opsForValue().get(redisTemplateJSONObjectKey);
              System.out.println("jsonObjectGet: " + jsonObjectGet);

              //序列化JSONArray對(duì)象類型和反序列化JSONArray對(duì)象類型
              JSONArray jsonArray = new JSONArray();
              JSONObject jsonObject1 = new JSONObject();
              jsonObject1.put("id""1");
              jsonObject1.put("name""austin1");
              jsonObject1.put("age"25);
              JSONObject jsonObject2 = new JSONObject();
              jsonObject2.put("id""1");
              jsonObject2.put("name""austin2");
              jsonObject2.put("age"25);
              jsonArray.add(jsonObject1);
              jsonArray.add(jsonObject2);
              redisTemplate.opsForValue().set(redisTemplateJSONArrayKey, jsonArray);
              JSONArray jsonArrayGet = (JSONArray) redisTemplate.opsForValue().get(redisTemplateJSONArrayKey);
              System.out.println("jsonArrayGet: " + jsonArrayGet);
          }

          觀察 redis 數(shù)據(jù)的存儲(chǔ)格式:

          key- value

          • 字符串類型
          Key: redisTemplateStringKey
          Value: "austin"
          • 對(duì)象類型
          Key: redisTemplateUserObjectKey
          Value:
          {
              "@class""com.example.jedisserializefrombytestojson.User",
              "id""123",
              "name""austin",
              "age": 25
          }
          • 對(duì)象數(shù)組類型
          Key: redisTemplateUserArrayObjectKey
          Value: 
          [
              "[Lcom.example.jedisserializefrombytestojson.User;",
              [
                  {
                      "@class""com.example.jedisserializefrombytestojson.User",
                      "id""1",
                      "name""austin1",
                      "age": 25
                  },
                  {
                      "@class""com.example.jedisserializefrombytestojson.User",
                      "id""2",
                      "name""austin2",
                      "age": 25
                  }
              ]
          ]
          • JSONObject類型
          Key: redisTemplateJSONObjectKey
          Value:
          {
              "@class""com.alibaba.fastjson.JSONObject",
              "name""austin",
              "id""123",
              "age": 25
          }
          • JSONArray類型
          Key: redisTemplateJSONArrayKey
          Value: 
          [
              "com.alibaba.fastjson.JSONArray",
              [
                  {
                      "@class""com.alibaba.fastjson.JSONObject",
                      "name""austin1",
                      "id""1",
                      "age": 25
                  },
                  {
                      "@class""com.alibaba.fastjson.JSONObject",
                      "name""austin2",
                      "id""1",
                      "age": 25
                  }
              ]
          ]

          運(yùn)行 redisTemplateSerializeTest 測(cè)試類,結(jié)果發(fā)現(xiàn)該方式序列化和反序列化都沒有問(wèn)題,果然是通用性序列化方式:

          我們來(lái)思考下,在將一個(gè)對(duì)象序列化成一個(gè)字符串,怎么保證字符串反序列化成對(duì)象的類型呢?Jackson 通過(guò) Default Typing,會(huì)在字符串多冗余一個(gè)類型,這樣反序列化就知道具體的類型了。

          從結(jié)果發(fā)現(xiàn),使用 GenericJackson2JsonRedisSerializer 序列化方式,String 類型、對(duì)象、對(duì)象數(shù)組、JSONObject、JSONArray 序列化和反序列化都沒有問(wèn)題,value 值序列化后多了 @class 屬性,反序列化的對(duì)象的類型就可以從這里獲取到。@class 屬性完美解決了反序列化后的對(duì)象類型,所以實(shí)際項(xiàng)目中,目前很多采用 GenericJackson2JsonRedisSerializer 序列化方式。

          Jackson2JsonRedisSerializer

          org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer

          觀察 redis 數(shù)據(jù)的存儲(chǔ)格式:

          key- value:

          • 字符串類型
          Key: redisTemplateStringKey
          Value: "austin"
          • 對(duì)象類型
          Key: redisTemplateUserObjectKey
          Value:
          {
              "id""123",
              "name""austin",
              "age": 25
          }

          與上面 GenericJackson2JsonRedisSerializer 序列化方式結(jié)果不同的是,value 沒有 @class 屬性。

          • 對(duì)象數(shù)組類型
          Key: redisTemplateUserArrayObjectKey
          Value: 
          [
              {
                  "id""1",
                  "name""austin1",
                  "age": 25
              },
              {
                  "id""2",
                  "name""austin2",
                  "age": 25
              }
          ]

          與上面 GenericJackson2JsonRedisSerializer 序列化方式結(jié)果不同的是,value 沒有"@class": "com.example.jedisserializefrombytestojson.User"對(duì)象類型屬性。

          • JSONObject類型
          Key: redisTemplateJSONObjectKey
          Value:
          {
              "name""austin",
              "id""123",
              "age": 25
          }

          與上面 GenericJackson2JsonRedisSerializer 序列化方式結(jié)果不同的是,value 沒有"@class": "com.alibaba.fastjson.JSONObject"屬性。

          • JSONArray類型
          Key: redisTemplateJSONArrayKey
          Value: 
          [
              {
                  "name""austin1",
                  "id""1",
                  "age": 25
              },
              {
                  "name""austin2",
                  "id""1",
                  "age": 25
              }
          ]

          與上面 GenericJackson2JsonRedisSerializer 序列化方式結(jié)果不同的是,value 沒有"com.alibaba.fastjson.JSONArray"對(duì)象類型屬性。

          Jackson2JsonRedisSerializer 與 GenericJackson2JsonRedisSerializer 序列化結(jié)果不同的是,前者并沒有 @class 或者 @type 類型屬性,這種序列化方式可能會(huì)導(dǎo)致獲取 redis 數(shù)據(jù)反序列化成 POJO 對(duì)象時(shí)候出錯(cuò),導(dǎo)致反序列化失敗,所以一般也很少使用該方式。

          比如在對(duì)象強(qiáng)制轉(zhuǎn)換的情況,會(huì)報(bào)錯(cuò):

          報(bào)錯(cuò)信息很明顯,不能直接將 JSONObject 對(duì)象強(qiáng)制轉(zhuǎn)換成 User 對(duì)象,不能通過(guò)方式獲取轉(zhuǎn)換:

          //該方式強(qiáng)轉(zhuǎn)會(huì)報(bào)錯(cuò)
          User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);

          而正確的方式應(yīng)該是:

          //通過(guò)com.fastxml.jackson的ObjectMapper對(duì)象進(jìn)行轉(zhuǎn)換
          Object userObject = redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
          ObjectMapper objectMapper = new ObjectMapper();
          User userGet = objectMapper.convertValue(userObject, User.class);
          System.out.println("userGet: " + userGet);

          這也是 redis 序列化和反序列化主要非常注意地方。

          總結(jié)

          采用GenericJackson2JsonRedisSerializer序列化方式對(duì)于 String、對(duì)象、對(duì)象數(shù)組、JSONObject、JSONArray 的序列化反序列化操作都正常,對(duì)象強(qiáng)轉(zhuǎn)是沒有任何問(wèn)題,但是采用Jackson2JsonRedisSerializer序列化方式在對(duì)象強(qiáng)制時(shí),也就是使用 User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);方式獲取對(duì)象,會(huì)操作對(duì)象轉(zhuǎn)換失敗,建議的解決方式是默認(rèn)都采用 com.fastxml.jackson 的 ObjectMapper 對(duì)象進(jìn)行轉(zhuǎn)換,也就是:

          ObjectMapper objectMapper = new ObjectMapper();
          objectMapper.convertValue(Object fromValue, Class<T> toValueType);

          該方式支持將任意類型的 Object 對(duì)象轉(zhuǎn)換為相應(yīng)的實(shí)體對(duì)象。

          瀏覽 101
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天综合7799精品视频 | 国产美女做爱A片是免费 | 粉嫩小泬BBBB毛茸茸 | 毛片視| 伊人久久人妻 |