springboot多模块项目中使用RedisTemplate的一次坑(注入了两个实例导致项目报错)

问题描述

在一个springboot多模块项目中,common模块中有一个RedisTemplateConfig类,指定redis使用的序列化方式,代码如下:
最开始代码:

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        redisTemplate.setValueSerializer(genericToStringSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;

    }
}

同时在该模块下有一个操作redis的util类,注入了redisTemplate,代码如下:
RedisUtils代码

@Component
@Slf4j
public class RedisUtils {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    public RedisTemplate<Object, Object> getRedisTemplate() {
        return this.redisTemplate;
    }

有两个模块(pc端和console端)都依赖了common模块,pc端能正常启动,但console模块启动不了,报redisTemplate注入不了,不存在,错误如下:

Field redisTemplate in com.baidu.hcm.utils.RedisUtils required a bean of type 'org.springframework.data.redis.core.RedisTemplate' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)

The following candidates were found but could not be injected:
    - Bean method 'redisTemplate' in 'RedisAutoConfiguration' not loaded because @ConditionalOnMissingBean (names: redisTemplate; SearchStrategy: all) found beans named redisTemplate


Action:

Consider revisiting the entries above or defining a bean of type 'org.springframework.data.redis.core.RedisTemplate' in your configuration.

第一眼看到这个报错觉得很奇怪,明明在RedisTemplateConfig中注入了redisTemplate,而且pc端也能正常启动使用,为什么console端却不行。。。
第一次修改代码

@Configuration
public class RedisTemplateConfig {

    @Bean(name = "xxxRedisTemplate"))
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        redisTemplate.setValueSerializer(genericToStringSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;

    }
}

修改后不能注入的问题解决了,pc和console能正常启动了,简单的验证了下pc端也能正常使用redis,以为没问题了,但是过了两天又出现另外一个新问题,之前RedisUtils中有一个自增的方法报:RedisCommandExecutionException: ERR value is not an integer or out of range

public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

当时修改没有全面验证redisUtil中的方法,导致过了两天这个自增的方法当时没有验证到。

解决

修改后代码

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        redisTemplate.setValueSerializer(genericToStringSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;

    }
}

RedisUtils代码修改:

@Component
@Slf4j
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public RedisTemplate<String, Object> getRedisTemplate() {
        return this.redisTemplate;
    }

去掉RedisTemplateConfig 中为RedisTemplate指定的名字,将RedisUtils 中的RedisTemplate泛型改为RedisTemplate<String, Object>,与RedisTemplateConfig 中的类型保持一致,从而保证注入容器中的类型跟我们需要从容器中取的类型一致。

原因

springboot自动配置类RedisAutoConfiguration中有一段代码:

public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

第一次console启动报错
在没修改代码之前,自己的RedisTemplateConfig配置类为容器注入了一个名为“redisTemplate”的RedisTemplate实例(类型RedisTemplate<String, Object>),springboot的自动配置类RedisAutoConfiguration中的自动注入未被调用,而RedisUtils中要求一个类型为RedisTemplate<Object, Object>的RedisTemplate实例,容器中不存在(这时需要值得注意的一个点:RedisTemplate<Object, Object>、RedisTemplate<String, Object>这两种类型并不相等),所以报错'org.springframework.data.redis.core.RedisTemplate' that could not be found.

第二次自增报错
RedisTemplateConfig 中手动为我们自己的redisTemplate指定名字后,我们主动的给容器中注入了一个名字为xxxRedisTemplate的RedisTemplate实例(类型RedisTemplate<String, Object>),springboot的自动配置类RedisAutoConfiguration 没有找到名字为"redisTemplate"的RedisTemplate实例,于是也往容器中注入了一个名字为redisTemplate的RedisTemplate实例(类型RedisTemplate<Object, Object>),这时容器中有两个RedisTemplate的实例:RedisTemplate<String, Object>、RedisTemplate<Object, Object>,而RedisUtils中使用的是RedisTemplate<Object, Object>,它使用的是默认的序列化器(org.springframework.data.redis.serializer.JdkSerializationRedisSerializer),而不是我们指定的StringRedisSerializer序列化器,可以参考详解

待解决

在第一次修改代码之前,启动pc端时RedisUtils也要求一个RedisTemplate<Object, Object>,启动没报错,通过debug程序发现RedisUtils中的RedisTemplate实例就是我们RedisTemplateConfig 中指定的,但为什么同样的启动console却报错了。
经过检查代码发现:pc端的启动类上多了一个注解@EnableXXXClient(内部一个组件),pc端引用了该组件,在组件中有一个RedisConfiguration类:

@Bean(name = "xxxRedisTemplate")
 public RedisTemplate redisTemplate() {
  //省略部分代码
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class) {
            // 省略部分代码
        };
        // 省略部分代码
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
 }

该配置类向容器中添加了一个RedisTemplate<Object, Object>类型的RedisTemplate 实例,故pc端启动时RedisUtils中能注入,而console没有依赖该组件,故注入报错。
下图为debug调试证明:
自己注入的RedisTemplate实例


图1

通过图1可以明显看到在debug模式启动时我们自己注入的RedisTemplate实例在设置的值
组件注入的RedisTemplate实例


图2

图3

图2是在debug模式下查看RedisUtils的get方法,此时redisTemplate对象中的值跟图3注入RedisTemplate实例时设置的值一样,验证了上面我们的结论。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • SpringBoot-Redis 入门 Redis 的数据类型 String 字符串 string 是 redis...
    寒沧阅读 1,528评论 0 0
  • 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializ...
    月生89阅读 43,043评论 0 1
  • 一:简介Redis是一个基于键值对的开源内存数据存储,Spring对Redis的支持是通过Spring Data ...
    爱的旋转体阅读 1,301评论 0 0
  • 本文是本人在SpringBoot中使用Redis的笔记,若有误还请指正。本文目录1、配置2、Redis读写字符串3...
    树蜂阅读 287评论 0 0
  • @[toc] 一、缓存 1、缓存使用 为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而db承担数...
    runewbie阅读 895评论 0 1

友情链接更多精彩内容