Spring Cloud NamedContextFactory 用于创建子容器

本文介绍如何利用SpringCloud的NamedContextFactory在不同场景下创建独立的Spring子容器,以实现灵活的接口实现切换。通过创建子容器,每个场景都能拥有独特的配置,适用于如open-feign等场景。
摘要由CSDN通过智能技术生成

我们在编程的时候,经常是面向接口编程的。假设有这样的一个需求,在一个 Spring 应用中,我们有一个接口,在不同的场景下,使用不同的实现类去执行逻辑,如何可以办到呢?常规情况下,我们可以使用 @Bean(name = “名称”)或者@Bean(“名称”),给 bean 指定一个名称,然后再使用 @Qualifier 来配合 @Autowired 注入指定名称的类即可。
假设有如下场景:

我们有10个场景,每个场景对应10个接口,每个场景对应的实现类都有可能不一样,这样怎么办呢?

Spring Cloud 提供了 NamedContextFactory 来解决这样的问题。正如其名字,是一个 Spring 容器上下文的创建工厂,是命名式的。是从 spring-cloud-netflix 引进来的。

首先看下类的定义:

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
}

这是一个抽象类,看到 ApplicationContextAware 就知道,这个注入了父容器的 ApplicationContext 。那这个类怎么使用呢?其常用的方法如下,其中可以从 T getInstance 开始看起:

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    private Map<String, C> configurations = new ConcurrentHashMap<>();

    //  以追加的形式配置类
    public void setConfigurations(List<C> configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }

    // 常用的入口方法,其中 name 为容器的名字, type 为需要返回的实例类型
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        try {
            return context.getBean(type);
        } catch (NoSuchBeanDefinitionException e) {
            // ignore
        }
        return null;
    }
    // 实际获取上下文的方法,是根据名字去获取的
    protected AnnotationConfigApplicationContext getContext(String name) {
        // contexts 是一个 map 容器,用来缓存已经生成的容器,可以看到这是一个懒加载的过程,只有第一次用到,才去生成。
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    // 核心在于 createContext 这个方法
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
    // 实际创建上下文的方法,可以看到这个是返回一个 AnnotationConfigApplicationContext 上下文。
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context;
        if (this.parent != null) {
            DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
            if (parent instanceof ConfigurableApplicationContext) {
                beanFactory.setBeanClassLoader(
                        ((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
            }
            else {
                beanFactory.setBeanClassLoader(parent.getClassLoader());
            }
            context = new AnnotationConfigApplicationContext(beanFactory);
            context.setClassLoader(this.parent.getClassLoader());
        }
        else {
            context = new AnnotationConfigApplicationContext();
        }
        // 以上代码,new 了一个 AnnotationConfigApplicationContext。如果有父上下文,则设置相同的类加载器,
        
        if (this.configurations.containsKey(name)) {
            // 这里是获取配置的关键, configurations 是一个全局的配置,包含所有命名的配置类,每个配置都是NamedContextFactory.Specification的实现了,里面可以包含多个配置类,遍历所有的配置类,注册到对应的容器中即可。
            for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
                context.register(configuration);
            }
        }
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                // 默认配置,所有的容器都同意注册
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        // 注册默认配置项
        context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
        // 将当前的容器的名字,注册到上下文中。方便当前上下文的 bean,可以通过 getInstance 方法获取相同容器中的bean
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
                Collections.<String, Object>singletonMap(this.propertyName, name)));
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
        }
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }

    public interface Specification {

        String getName();

        Class<?>[] getConfiguration();

    }
}

总体而言,NamedContextFactory 并不复杂,通过创建 Spring 子容器,可以获取独立的上下文,每个上下文的类是隔离的。应用场景比如open-feign中,每一个接口都对应一个子容器,可以拥有不同的配置。这篇文章同样是 Spring Cloud Loadbalancer 模块学习的前置文章,对学习 Spring Cloud Loadbalancer 有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值