0%

自定义RedisTemplate初探

在 spring boot 项目中使用了 redis,在此对 RedisTemplate 的使用做个记录,另外在腾讯云上安装了 redis 作为缓存服务器进行了测试。

RedisTemplate 的使用

首先,redis 提供了 RedisTemplate<Object,Object> 和 StringRedisTemplate 两个默认实现,其中 StringRedisTemplate 继承自 RedisTemplate。 RedisTemplate<Object,Object> 使用序列化的方式存储 key 和 value,StringRedisTemplate 则是一个 key 和 value 都是 String 的 RedisTemplate<String,String>。

对以上两个类的使用不再叙述。看一下以下错误的解决情况,并对应源码简单分析:

There is more than one beans ‘RedisTemplate’ type

在尝试直接使用以下代码注入 RedisTemplate,会有如上报错,

1
2
@Autowired
private RedisTemplate mRedisTemplate;

这是因为 RedisTemplate 使用了泛型,此时框架无法确定需要注入的是 redisTemplate 还是 stringRedisTemplate。因此,使用 RedisTemplate 时需要指定泛型。

使用自定义实体类时, No beans of ‘RedisTemplate<String,City>’ type found

我们建立如下实体类 City,并且实现了 Serializable 接口,以在 RedisTemplate 中使用二进制序列化的方式存储 City 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Entity
public class City implements Serializable {

Integer id;
Integer provinceId;
String cityName;
String description;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getProvinceId() {
return provinceId;
}

public void setProvinceId(int provinceId) {
this.provinceId = provinceId;
}

public String getCityName() {
return cityName;
}

public void setCityName(String cityName) {
this.cityName = cityName;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

}

然后尝试使用如下代码注入 RedisTemplate<String,City>,

1
2
@Autowired
private RedisTemplate<String, City> mRedisTemplate;

会报错‘No beans of ‘RedisTemplate<String,City>’ type found’,这是因为 redis 没有注册这一类型的 bean,需要我们增加添加这一bean。

我们可以创建一个 RedisConfig 类,在其中进行配置。

首先,为了自定义 City 类的序列化行为,我们可以继承 RedisSerializer 实现类 MyRedisSerializer 自定义序列化行为,只需要实现接口的 serialize() 方法和 deserialize() 方法,不再叙述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, City> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, City> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new MyRedisSerializer());
template.afterPropertiesSet();
return template;
}

}

这样我们就实现了 RedisTemplate<String, City> 这一 bean,而且使用自定义的 MyRedisSerializer 类的序列化规则对 City 进行序列化。

关于这一配置 bean 的方法,其原理我们可以查看 RedisAutoConfiguration 类,其中有如下代码:

1
2
3
4
5
6
7
8
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

这里的关键是 @ConditionalOnMissingBean(name = “redisTemplate”) 注解,注解的作用是:如果用户没有配置 name 为 redisTemplate 的 bean,那么使用此方法定义的 RedisTemplate<Object, Object> 进行注入,否则使用用户配置的 bean。

所以,我们可以用之前的代码自定义一个 bean,实现 RedisTemplate<String, City>。

自定义 StringRedisTemplate

如果我们想自己实现 StringRedisTemplate,如上,我们可以 RedisAutoConfiguration 类看到如下代码:

1
2
3
4
5
6
7
8
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

这里仍然是使用了 @ConditionalOnMissingBean 注解,所以我们需要按照如下代码来实现:

1
2
3
4
5
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 自定义配置
//....
}

接下来注入和使用 StringRedisTemplate 实例,使用断点或者 log 等可以看到,此时注入的 StringRedisTemplate 是我们自己配置的实例。

要注意的是,只有我们使用了 RedisConnectionFactory 对 StringRedisTemplate 进行了自定义配置时并调用了 template.afterPropertiesSet() 方法使配置生效后,我们的 StringRedisTemplate 才与默认的 StringRedisTemplate 不同,才可以使用,不然项目会启动失败。另外,若只是需要进行指定序列化方式等,RedisTemplate 类本身就提供了相应方法,不必为此自行配置 bean。

使用代码进行 Redis 的配置

如果我们想对 配置文件中的 redis 配置项进行覆盖,或者我们不想使用配置文件,想完全使用代码进行配置,可以使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class RedisConfig {
@Bean
RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
//configuration.setXXX();
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
factory.afterPropertiesSet();
return factory;
}
}

这样,我们在 configuration 实例中设置的项会覆盖掉配置文件中内容和默认内容。这里的原理上文提到的相同,我们可以在 LettuceConnectionConfiguration 类中看到如下代码:

1
2
3
4
5
6
7
8
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(
ClientResources clientResources) throws UnknownHostException {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(
clientResources, this.properties.getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}

仍然是 @ConditionalOnMissingBean 注解的作用。