SpringCache配合jackson反序列化Map类型Key出现类型不匹配?

最近在项目中集成springCache,在对Map类型的返回值进行反序列化出现key类型不匹配,Map<Integer,AcademicQualificationsEntity>, 类型被反序列化为Map<String,AcademicQualificationsEntity>

springCache配置

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.serializer.*;

@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching 
public class RedisCacheConfig {   
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // key 指定序列化 -string
        config= config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        // value 指定序列化-Jackson
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();

        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

缓存方法

    @Cacheable(key = "#root.caches[0].name", value = "ACADEMIC")
    @Override
    public Map<Integer, AcademicQualificationsEntity> cacheAcademicInfo() {
        return this.baseMapper.selectAllInfoEncapsulatedMap();
    }

测试方法

    @Test
    public void academicTest() {
        Map<Integer, AcademicQualificationsEntity> map = academicQualificationsService.cacheAcademicInfo();
        System.out.println(map);

        for (Integer key : map.keySet()) {
            System.out.println(map.get(key));
        }
    }

测试结果

// for (Integer key:map.keySet()) 此处出现类型转换异常
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

redis 客户端中的json串

{
    "1": {
        "@class": "com.community.healthy.entity.AcademicQualificationsEntity",
        "id": 1,
        "degree": "初等教育",
        "parentId": 0,
        "isActive": 0,
        "version": 0
    },
    "2": {
        "@class": "com.community.healthy.entity.AcademicQualificationsEntity",
        "id": 2,
        "degree": "中等教育",
        "parentId": 0,
        "isActive": 0,
        "version": 0
    },
    "3": {
        "@class": "com.community.healthy.entity.AcademicQualificationsEntity",
        "id": 3,
        "degree": "高等教育",
        "parentId": 0,
        "isActive": 0,
        "version": 0
    },     
    "@class": "java.util.HashMap"
}

Map<Integer, AcademicQualificationsEntity> map = academicQualificationsService.cacheAcademicInfo(); 调用返回的实际类型为Map<String,AcademicQualificationEntity> 但是编译器应该提供类型检查<Integer,AcademicQualificationEntity> Spring Cache 为什么出现了Map<> 转换异常并且欺骗了编译器呢?
请问这个问题该如何解决呢?

阅读 2.7k
1 个回答

当使用 Jackson 反序列化 Map 时,默认情况下,所有的键都会被当作字符串处理。这就是为什么您遇到了这个问题。要解决这个问题,您需要自定义一个 Redis 序列化器,以便在反序列化时可以将键转换回 Integer 类型。

以下是一个自定义的 Redis 序列化器示例,可以解决这个问题:


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

public class CustomRedisSerializer<T> extends Jackson2JsonRedisSerializer<T> {

    private ObjectMapper objectMapper;

    public CustomRedisSerializer(Class<T> type, ObjectMapper objectMapper) {
        super(type);
        this.objectMapper = objectMapper;
    }

    @Override
    protected JavaType getJavaType(Class<?> clazz) {
        if (Map.class.isAssignableFrom(clazz)) {
            return TypeFactory.defaultInstance().constructParametricType(clazz, Integer.class, super.getJavaType(AcademicQualificationsEntity.class));
        }
        return super.getJavaType(clazz);
    }

    public byte[] serialize(Object source) throws SerializationException {
        try {
            return objectMapper.writeValueAsBytes(source);
        } catch (JsonProcessingException e) {
            throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
        }
    }

    public T deserialize(byte[] source) throws SerializationException {
        try {
            return objectMapper.readValue(source, getJavaType(Map.class));
        } catch (Exception e) {
            throw new SerializationException("Could not read JSON: " + e.getMessage(), e);
        }
    }
}

然后,在您的 RedisCacheConfig 类中使用这个自定义序列化器替换 GenericJackson2JsonRedisSerializer:

config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new CustomRedisSerializer<>(Map.class, new ObjectMapper())));

这将确保在反序列化 Map 时将键正确地转换回 Integer 类型。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题