使用spring-boot集成redis中使用redisTemplate配置泛型导致的问题

问题

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>                                                                                                    

遇到一个问题,使用redis中的List保存部门组装路径后相关数据,方便查询,功能代码为:

@Service
@Slf4j
@RequiredArgsConstructor
public class OACompanyServiceImpl extends ServiceImpl<OACompanyMapper, OACompanyEntity> implements OACompanyService {

    private final OACompanyMapper oaCompanyMapper;
	// 设置redis的泛型
    private final RedisTemplate<String, OAOrgChangeVO> redisTemplate;

    /**
     * 公司名单
     */
    private final static String OA_COMPANY_LIST = "OA_COMPANY_LIST";

    @Override
    public List<OAOrgChangeVO> getChangeCompanyList() {
		// 查看是否存在这个缓存数据
        Boolean hasKey = redisTemplate.hasKey(OA_COMPANY_LIST);
        // 获取操作类
        BoundListOperations<String, OAOrgChangeVO> listOps = redisTemplate.boundListOps(OA_COMPANY_LIST);
        if (Boolean.FALSE.equals(hasKey)) {
        	// 查询对应的部门数据
            MPJLambdaWrapper<OACompanyEntity> queryWrapper = new MPJLambdaWrapper<OACompanyEntity>();
            List<OAOrgChangeVO> vos = oaCompanyMapper.selectJoinList(OAOrgChangeVO.class, queryWrapper);
            if (!vos.isEmpty()){
                OAOrgChangeVO[] values = vos.toArray(new OAOrgChangeVO[]{});
                listOps.leftPushAll(values);
                listOps.expire(12, TimeUnit.HOURS);
            }
            return vos;
        }
        return listOps.range(0, -1);
    }
}

编译通过,但是springboot项目启动结果抛出以下异常在这里插入图片描述

问题排查(配置)

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        RedisSerializer<Object> jackson2serializer = jackson2serializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
		// 设置的连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        // 设置key和value的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer); // 设置key的序列化器为StringRedisSerializer
        redisTemplate.setValueSerializer(jackson2serializer); // 设置value的序列化器为自定义的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer); // 设置hash key的序列化器为jackson2serializer
        redisTemplate.setHashValueSerializer(jackson2serializer); // 设置hash 自定义的序列化方式
        redisTemplate.afterPropertiesSet(); // 初始化RedisTemplate
        return redisTemplate; // 返回配置好的RedisTemplate
    }
    @Bean
    public RedisSerializer<Object> jackson2serializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 自定义ObjectMapper的时间处理模块
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        objectMapper.registerModule(javaTimeModule);

        // 禁用将日期序列化为时间戳的行为
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        //创建JSON序列化器
        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }

第一步想到配置是因为源码中 设置的lettuce连接工厂,类似于连接MySQL的sessionFactory

!
我的配置代码中设置连接工厂的地方为我注册的public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) bean;

打上断点,看看效果,进入到源码中,发现设置的setValueSerializer中的javaType为Object类型,但是其泛型使用的为?

源码:在这里插入图片描述

我使用的:在这里插入图片描述

解决方式

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();
        RedisSerializer<?> jackson2serializer = jackson2serializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        redisTemplate.setConnectionFactory(connectionFactory);
        // 设置key和value的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer); // 设置key的序列化器为StringRedisSerializer
        redisTemplate.setValueSerializer(jackson2serializer); // 设置value的序列化器为自定义的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer); // 设置hash key的序列化器为jackson2serializer
        redisTemplate.setHashValueSerializer(jackson2serializer); // 设置hash 自定义的序列化方式
        redisTemplate.afterPropertiesSet(); // 初始化RedisTemplate
        return redisTemplate; // 返回配置好的RedisTemplate
    }
    @Bean
    public RedisSerializer<?> jackson2serializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 自定义ObjectMapper的时间处理模块
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        objectMapper.registerModule(javaTimeModule);

        // 禁用将日期序列化为时间戳的行为
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        //创建JSON序列化器
        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }


}

出错原因

​ 根本原因需要弄清楚 ?(通配符)和使用Object类的区别

1、通配符?)在 Java 泛型中用于表示未知的类型。通配符的主要用途是在处理泛型集合时,使代码更加灵活和通用。通配符可以分为三种类型:

  • 无界通配符 (?): 表示任意类型。它可以用于你不知道具体类型的情况。例如,List<?> 表示一个可以包含任何类型的 List。它允许你读取 List 中的元素,但不能向其中添加元素(除了 null)。
  • 有界通配符 (? extends T? super T):
    • 上界通配符 (? extends T): 表示一个未知类型,它是 TT 的子类。这种通配符用于读取数据时,确保数据的类型至少是 T。例如,List<? extends Number> 表示一个可以包含 Number 或其子类(如 IntegerDouble)的列表。
    • 下界通配符 (? super T): 表示一个未知类型,它是 TT 的超类。这种通配符用于写入数据时,确保数据的类型不低于 T。例如,List<? super Integer> 表示一个可以包含 Integer 或其超类(如 NumberObject)的列表。

2、Object

Object 是 Java 中所有类的根类。它可以用于表示任何类型的对象。使用 Object 类型可以接收任意类型的数据,但它会丧失泛型的类型安全性和特定的方法。通常,当你使用 Object 类型时,你可能需要在运行时进行类型检查和强制类型转换。

主要区别

  • 类型安全: 使用通配符可以在一定程度上保持类型安全,而 Object 类型则不提供类型安全,可能需要额外的类型检查和转换。
  • 灵活性: 通配符提供了更灵活的类型操作。例如,List<? extends Number> 可以接受 List<Integer>List<Double> 等,而 List<Object> 则可以接受任何类型,但你失去了更多的类型信息。
  • 用途: 通配符主要用于处理泛型集合和方法参数时保持通用性,而 Object 用于接受和处理任意类型的对象,通常在你不关心对象具体类型时使用。

总结

? extends Number>可以接受ListList等,而List` 则可以接受任何类型,但你失去了更多的类型信息。

  • 用途: 通配符主要用于处理泛型集合和方法参数时保持通用性,而 Object 用于接受和处理任意类型的对象,通常在你不关心对象具体类型时使用。

总结

​ 在使用Redisconfig的时候,需要考虑是否存在?和Object的转化,上述代码中使用到了jackson2serializer,?和 Object的转化导致注入类型失败,最终导致设置了泛型为OAOrgChangeVO后,出现Object和OAOrgChangeVO的类型匹配的问题,最终不存在类型参数而异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值