当我们需要在项目中配置多个Redis、MongoDB、DB、RabbitMQ、Kafka时,往往我们的做法是自定一个配置类,然后参考官方的自动配置类进行部分配置。
这种做法虽然可以实现多数据源,但是随着版本迭代,可能兼容性不太好。
下面介绍如果利用Spring Boot 子容器 (Spring 工厂)来创建多数据源的思路。
思路
构建一个轻量级的spring boot容器,将主容器的配置信息,截取前缀,以命令行参数的形式传入,并在子容器中实现自动装配,然后再将对象从子容器取出,注入到主容器进行管理。
代码
这里以Redis自动配置为例,获取多个StringRedisTemplate工具类,这里只做抛砖引玉,也可以可参考org.springframework.cloud.stream.binder.DefaultBinderFactory#getBinderInstance
。
1.RedisConfig.java:创建不同数据源的StringRedisTemplate
/**
* <p>
* RedisConfig
* <p>
*
* @author: kancy
* @date: 2020/7/13 11:34
**/
@Configuration
public class RedisConfig {
/**
* 装配一个来自不同数据源的StringRedisTemplate
* @param springChildContainerProperties
* @return
*/
@Bean
public StringRedisTemplate StringRedisTemplate2(SpringChildContainerProperties springChildContainerProperties){
// 获取主容器的配置属性,并加入到子容器的命令行参数中
SpringChildContainerProperties.Container container = springChildContainerProperties.getContainer().get("redis2");
Assert.state(Objects.equals(container.getType(), "redis"), "config is error.");
// 扁平化环境参数
ArrayList<String> args = new ArrayList<>();
Map<String, String> envProperties = new HashMap<>();
flatten(null, container.getEnvironment(), envProperties);
for (Map.Entry<String, String> property : envProperties.entrySet()) {
args.add(String.format("--%s=%s", property.getKey(), property.getValue()));
}
// 自动装配的类
Class[] ConfigurationClasses = {RedisAutoConfiguration.class};
// 创建子容器
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder()
.sources(ConfigurationClasses)
.bannerMode(Banner.Mode.OFF)
.logStartupInfo(false)
.web(WebApplicationType.NONE);
// 运行子容器
ConfigurableApplicationContext redisProducingContext = springApplicationBuilder.run(args.toArray(new String[0]));
// 从子容器中获取RedisConnectionFactory
RedisConnectionFactory redisConnectionFactory = redisProducingContext.getBean(RedisConnectionFactory.class);
System.out.println(redisConnectionFactory.toString());
// 从子容器中获取StringRedisTemplate
StringRedisTemplate redisTemplate = redisProducingContext.getBean(StringRedisTemplate.class);
System.out.println(redisTemplate.toString());
return redisTemplate;
}
/**
* 扁平化
* @param propertyName
* @param value
* @param flattenedProperties
*/
private void flatten(String propertyName, Object value,
Map<String, String> flattenedProperties) {
if (value instanceof Map) {
((Map<Object, Object>) value).forEach((k, v) -> flatten(
(propertyName != null ? propertyName + "." : "") + k, v,
flattenedProperties));
}
else {
flattenedProperties.put(propertyName, value.toString());
}
}
}
2.SpringChildContainerProperties.java:子容器的属性配置类
/**
* <p>
* SpringChildContainerProperties
* <p>
*
* @author: kancy
* @date: 2020/7/13 13:10
**/
@Data
@Component
@ConfigurationProperties(prefix = "spring")
public class SpringChildContainerProperties {
/**
* 容器
*/
private Map<String, Container> container = Collections.emptyMap();
@Data
public static class Container {
/**
* 类型
*/
private String type;
/**
* 对应的环境变量
*/
private Map<String, Object> environment = Collections.emptyMap();
}
}
3.application.yml:配置文件
spring:
application:
name: application
redis:
host: redis.kancy.top
port: 6379
password: root
database: 10
container:
redis2:
type: redis
environment:
spring:
redis:
password: root
cluster:
nodes: 10.138.60.187:6380,10.138.60.187:6381,10.138.60.187:6382