借助SpringBoot子容器来实现多数据源工厂

当我们需要在项目中配置多个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
posted @ 2020-07-13 13:34  kancy  阅读(707)  评论(0编辑  收藏  举报