问题描述
在一个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可以明显看到在debug模式启动时我们自己注入的RedisTemplate实例在设置的值
组件注入的RedisTemplate实例
图2是在debug模式下查看RedisUtils的get方法,此时redisTemplate对象中的值跟图3注入RedisTemplate实例时设置的值一样,验证了上面我们的结论。