Spring Data Redis 多源
完整代码:Ciiiiing/springboot_multi_redis
最近需要在同一个项目中访问多个 redis
而 spring data redis
默认是只支持一个数据源的,那就需要我们自己改造
网上搜了一些文章,大多有一些错漏,并且只给出了结果(还是错的)没有为什么,所以自己研究了一下,分享一下过程
首先在一个 spring boot
项目的 pom
文件中引入 spring data redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 yml
做如下配置,host
换成你自己的 redis
服务器地址
spring:
redis:
host: 192.168.253.132
port: 6379
database: 0
这样我们就可以使用 spring data redis
提供的 bean
来访问 redis
了,我们不妨先来试一试
@SpringBootApplication
public class SpringbootMultiRedisApplication implements CommandLineRunner {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public SpringbootMultiRedisApplication(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootMultiRedisApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
stringRedisTemplate.opsForValue().set("test", "1");
}
}
先用 RDM
看看数据
运行一下我们的程序
好的,成功插入了,至此我们已经成功集成了单源 redis
接下来我们看看怎么实现多源
我们要知道 spring boot
之所以能完成自动配置是因为有很多 autoConfiguration
类来帮我们做了初始化,那我们就来搜一搜 redis
的自动配置类
ok,我们已经发现确实存在 redis
的自动配置类,我们进入看看
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
嗯,我们能看到 redis
的自动配置类还是比较简单的,大部分功能基本都靠注解来完成,那我们就一个个的来看看这些注解的作用
第一个 @Configuration
,点进去就能看到官方对他的说明
Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime
表示一个类声明了一个或多个 @Bean 方法,并可由 Spring 容器处理,以便在运行时为这些 Bean 生成 Bean 定义和服务请求
第二个 @ConditionalOnClass
@Conditional that only matches when the specified classes are on the classpath.
在指定的类存在于 classpath 时加载
第三个 @EnableConfigurationProperties
Enable support for @ConfigurationProperties annotated beans. @ConfigurationProperties beans can be registered in the standard way (for example using @Bean methods) or, for convenience, can be specified directly on this annotation.
启用对 @ConfigurationProperties 注释的 bean 的支持。@ConfigurationProperties beans 可以以标准方式注册(例如使用 @Bean 方法),或者为了方便,可以直接在这个注解上指定
第四个 @Import
Indicates one or more component classes to import — typically @Configuration classes.
表示要导入的一个或多个组件类 -- 通常是 @Configuration 类
那么那这个注解就是说
RedisAutoConfiguration
这个类是个配置类,用于定义一个或多个 beanclasspath
下存在RedisOperations.class
这个类则加载它- 启用
RedisProperties.class
该配置类 - 导入配置类
LettuceConnectionConfiguration.class
和JedisConnectionConfiguration.class
点进 RedisProperties.class
这里类里边看看
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
...
}
就是这个类指定了我们配置文件的格式(我们之前写的 yml
)
继续看剩下的注解
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
第一个 @Bean
Indicates that a method produces a bean to be managed by the Spring container
表明这个方法用于生产一个归 spring 管理的 bean 对象
第二个 @ConditionalOnMissingBean
@Conditional that only matches when no beans meeting the specified requirements are already contained in the BeanFactory. None of the requirements must be met for the condition to match and the requirements do not have to be met by the same bean.
@Conditional,只有在 BeanFactory 中已经没有满足指定要求的 Bean 时才会匹配。条件必须不满足任何要求才能匹配,而且这些要求不一定要由同一个 Bean 来满足
第三个 @ConditionalOnSingleCandidate
@Conditional that only matches when a bean of the specified class is already contained in the BeanFactory and a single candidate can be determined.
@Conditional,只有在 BeanFactory 中已经包含了指定类别的 Bean 并且可以确定一个候选者时才会匹配
The condition will also match if multiple matching bean instances are already contained in the BeanFactory but a primary candidate has been defined;
如果 BeanFactory 中已经包含了多个匹配的 Bean 实例,但已经定义了一个 primary(主要) 的候选者,那么该条件也将匹配
ok,那我们来理解一下是什么意思
- 表明
redisTemplate
这个方法用于生成一个由spring
管理的bean
- 若没有
redisTemplate
这个bean
则加载redisTemplate
- 若容器中只有一个
RedisConnectionFactory.class
类
好的,总体梳理一下,我们之所以只需要在 yml
文件中写好 redis
地址就能注入一个 StringRedisTemplate
对象来直接使用,是因为 RedisAutoConfiguration
这个自动配置类帮我们做了初始化
流程大概清楚了,该如何自定义数据源呢?
再看看这段代码
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
注意到 RedisConnectionFactory
根据命名我们猜测这是一个工厂类,回忆一下设计模式,工厂模式
用于初始化复杂的对象,ok 那应该就是在这里做了初始化,那么这个工厂类又是哪来的呢?
还记得之前的配置类吗?
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
我们使用的是 Lettuce
作为客户端,所以去看看 LettuceConnectionConfiguration.class
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
那么能看到是这里注入了 redisConnectionFactory
,跟进这个方法
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
我们使用的是单点 redis
所以再看看 getStandaloneConfig()
这个方法
protected final RedisStandaloneConfiguration getStandaloneConfig() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
if (StringUtils.hasText(this.properties.getUrl())) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
config.setHostName(connectionInfo.getHostName());
config.setPort(connectionInfo.getPort());
config.setUsername(connectionInfo.getUsername());
config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
}
else {
config.setHostName(this.properties.getHost());
config.setPort(this.properties.getPort());
config.setUsername(this.properties.getUsername());
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
config.setDatabase(this.properties.getDatabase());
return config;
}
nice,可以看到实在这里读取了配置文件
我们只要仿照这个建立自己的工厂类就可以了,让我们动手试试
建立一个 RedisConfig
来创建我们自己的工厂
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory firstFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("192.168.253.132", 6379);
configuration.setDatabase(1);
return new LettuceConnectionFactory(configuration);
}
@Bean
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
先建立一个工厂,给 db1
写入数据试试看
好的没有问题,我们接下来建立第二个工厂并指定给 db2
插入数据
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory firstFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("192.168.253.132", 6379);
configuration.setDatabase(1);
return new LettuceConnectionFactory(configuration);
}
@Bean
public LettuceConnectionFactory secondFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("192.168.253.132", 6379);
configuration.setDatabase(2);
return new LettuceConnectionFactory(configuration);
}
@Bean
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
StringRedisTemplate stringRedisTemplate2(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
好,我们运行看看
Parameter 0 of method stringRedisTemplate in com.example.springboot_multi_redis.config.RedisConfig required a single bean, but 2 were found:
- firstFactory: defined by method 'firstFactory' in class path resource [com/example/springboot_multi_redis/config/RedisConfig.class]
- secondFactory: defined by method 'secondFactory' in class path resource [com/example/springboot_multi_redis/config/RedisConfig.class]
很遗憾,我们看到程序报错了,这是为什么呢?
通过报错信息 required a single bean, but 2 were found
可以看到有一个地方需要一个 bean
但程序发现了两个不知道该使用哪一个,看到这里,你想起我们之前说的 @ConditionalOnSingleCandidate
这个注解了吗?从官方说明中我们知道,需要一个 primary
就可以了,嘿,这里我们用 @Primary
注解就好
@Bean
@Primary
public LettuceConnectionFactory firstFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("192.168.253.132", 6379);
configuration.setDatabase(1);
return new LettuceConnectionFactory(configuration);
}
再次运行程序(记得每次重新运行前清掉 redis
的内容,或者每次使用不同的 key
方便你判断是否成功)
ok,我们的代码是 stringRedisTemplate.opsForValue().set("test", "1");
所以插入 db1
是符合预期的
现在我们想用 stringRedisTemplate2
来操作 db2
,但细心的我们发现
@Bean
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
StringRedisTemplate stringRedisTemplate2(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
这两段代码除了函数名以外完全一致,怎么能使用不同的工厂呢?
这就要用到另一个注解 @Qualifier
了
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring.
用于在自动注入时指定使用的 bean,也就是 by type 的注入,@Autowired 默认是 by name
让我们加入这个注解
@Bean
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
StringRedisTemplate stringRedisTemplate2(@Qualifier("secondFactory") RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
修改一下主类
@SpringBootApplication
public class SpringbootMultiRedisApplication implements CommandLineRunner {
private final StringRedisTemplate stringRedisTemplate;
private final StringRedisTemplate stringRedisTemplate2;
@Autowired
public SpringbootMultiRedisApplication(StringRedisTemplate stringRedisTemplate,
StringRedisTemplate stringRedisTemplate2) {
this.stringRedisTemplate = stringRedisTemplate;
this.stringRedisTemplate2 = stringRedisTemplate2;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootMultiRedisApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
stringRedisTemplate.opsForValue().set("test", "1");
stringRedisTemplate2.opsForValue().set("test", "2");
}
}
运行程序
好的,大功告成,我们可以通过同样的方法加入更多的 redis
源
这篇博客也就此结束了~~
祝大家学习愉快