美文网首页
SpringCloud里面的父子容器

SpringCloud里面的父子容器

作者: 王侦 | 来源:发表于2023-03-07 15:49 被阅读0次

1.创建容器流程

SpringApplication#run

  • SpringApplication#prepareEnvironment
  • 发布ApplicationEnvironmentPreparedEvent
  • BootstrapApplicationListener#onApplicationEvent会处理ApplicationEnvironmentPreparedEvent事件
  • 没有ApplicationContextInitializer初始化context
  • BootstrapApplicationListener#bootstrapServiceContext
  • 创建Bootstrap父容器AnnotationConfigApplicationContext,包含39个bean
  • 创建Springboot子容器AnnotationConfigServletWebServerApplicatiionContext,包含220个bean

又会进入SpringApplication#run创建Bootstrap父容器
SpringApplication#run

  • BootstrapApplicationListener#onApplicationEvent处理ApplicationEnvironmentPreparedEvent事件,发现environment.propertySources里面包含bootstrap,则不会再创建容器,直接返回。
  • ConfigFileApplicationListener#onApplicationEvent处理ApplicationEnvironmentPreparedEvent事件
  • builder.sources(BootstrapImportSelectorConfiguration.class);在BootstrapImportSelector#selectImports会加载spring.factories文件里面的BootstrapConfiguration
  • 然后创建Bootstrap容器是AnnotationConfigApplicationContext,容器里面有39个bean
  • AncestorInitializer,这个会将Springboot容器的父容器设置为bootstrap容器

两个容器的区别:

  • Bootstrap容器会多一个步骤:读取spring.factories文件中的BootstrapConfiguration,SpringBoot子容器读取spring.factories文件中的EnableAutoConfiguration。

bootstrap主要有以下几个应用场景:

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息
  • 一些固定的不能被覆盖的属性
  • 一些加密/解密的场景

1.1 ConfigFileApplicationListener

ConfigFileApplicationListener处理事件:

  • 从spring.factories里面找EnvironmentPostProcessor,找到6个
  • ConfigFileApplicationListener#postProcessEnvironment
  • ConfigFileApplicationListener#addPropertySources
  • ConfigFileApplicationListener.Loader#load()

ConfigFileApplicationListener

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";

1.1.1 bootstrap容器解析

路径加载配置文件的顺序(逆向的),默认spring.config.name是bootstrap:

  • 1)file:./config/
  • 2)file:./config/*/
  • 3)file:./
  • 4)classpath:/config/
  • 5)classpath:/
    boostrap.xml
    boostrap.json
    boostrap.properties
    boostrap.yml
    boostrap.yaml

boostrap-dev.xml
boostrap.xml
boostrap-dev.json
boostrap.json
boostrap-dev.properties
boostrap.properties
boostrap-dev.yml
boostrap.yml
boostrap-dev.yaml
boostrap.yaml
这种顺序又来一遍。

解析出来的配置


1.1.2 Springboot子容器解析

        private Set<String> getSearchNames() {
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
                Set<String> names = asResolvedSet(property, null);
                names.forEach(this::assertValidConfigName);
                return names;
            }
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }

创建子容器时,没有属性spring.config.name,此时使用默认的名字application。

  • 1)file:./config/
  • 2)file:./config/*/
  • 3)file:./
  • 4)classpath:/config/
  • 5)classpath:/
    application.xml
    application.json
    application.properties
    application.yml
    application.yaml

application-dev.xml
application.xml
application-dev.json
application.json
application-dev.properties
application.properties
application-dev.yml
application.yml
application-dev.yaml
application.yaml

1.2 没有bootstrap配置文件

也会创建bootstrap父容器:


2.NamedContextFactory

该工厂根据不同的服务名称,创建不同的容器。该容器有2个实现类,FeignContext 和 SpringClientFactory,分别用来加载对应的配置。

具体Feign 和 Ribbon配置类会创建多少实例,和项目本身依赖发服务有关。如果依赖10个服务,那就是10个Feign配置容器+10个Ribbon配置容器+SpringBoot容器+BootStrap容器。

NamedContextFactory 的用处(Ribbon ):

  • 子容器之间数据隔离。不同的 LoadBalancer 只管理自己的服务实例,明确自己的职责。
  • 子容器之间配置隔离。不同的 LoadBalancer 可以使用不同的配置。例如报表服务需要统计和查询大量数据,响应时间可能很慢。而会员服务逻辑相对简单,所以两个服务的响应超时时间可能要求不同。
  • 子容器之间 Bean 隔离。可以让子容器之间注册不同的 Bean。例如订单服务的 LoadBalancer 底层通过 Nacos 获取实例,会员服务的 LoadBalancer 底层通过 Eureka 获取实例。也可以让不同的 LoadBalancer 采用不同的算法

2.1 NamedContextFactory 创建

2.1.1 FeignContext什么时候创建

@FeignClient(name = "stock-service", path = "/stock")
public interface StockService {
    @RequestMapping("/deduct")
    public String deduct();
}

AbstractAutowireCapableBeanFactory#createBeanInstance

  • 走的时候下面的创建逻辑
        Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
        if (instanceSupplier != null) {
            return obtainFromSupplier(instanceSupplier, beanName);
        }

具体逻辑看FeignClientsRegistrar#registerFeignClient

    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        Class clazz = ClassUtils.resolveClassName(className, null);
        ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
                ? (ConfigurableBeanFactory) registry : null;
        String contextId = getContextId(beanFactory, attributes);
        String name = getName(attributes);
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setBeanFactory(beanFactory);
        factoryBean.setName(name);
        factoryBean.setContextId(contextId);
        factoryBean.setType(clazz);
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(clazz, () -> {
                    factoryBean.setUrl(getUrl(beanFactory, attributes));
                    factoryBean.setPath(getPath(beanFactory, attributes));
                    factoryBean.setDecode404(Boolean
                            .parseBoolean(String.valueOf(attributes.get("decode404"))));
                    Object fallback = attributes.get("fallback");
                    if (fallback != null) {
                        factoryBean.setFallback(fallback instanceof Class
                                ? (Class<?>) fallback
                                : ClassUtils.resolveClassName(fallback.toString(), null));
                    }
                    Object fallbackFactory = attributes.get("fallbackFactory");
                    if (fallbackFactory != null) {
                        factoryBean.setFallbackFactory(fallbackFactory instanceof Class
                                ? (Class<?>) fallbackFactory
                                : ClassUtils.resolveClassName(fallbackFactory.toString(),
                                        null));
                    }
                    return factoryBean.getObject();
                });

创建过程:

  • FeignClientFactoryBean#getObject
  • beanFactory.getBean(FeignContext.class)
  • 最终使用factoryMethod来创建
  • FeignAutoConfiguration#feignContext

FeignClientFactoryBean#getTarget

    <T> T getTarget() {
        FeignContext context = beanFactory != null
                ? beanFactory.getBean(FeignContext.class)
                : applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(url)) {

            if (LOG.isInfoEnabled()) {
                LOG.info("For '" + name
                        + "' URL not provided. Will try picking an instance via load-balancing.");
            }
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
                // not load balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient) client).getDelegate();
            }
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((RetryableFeignBlockingLoadBalancerClient) client)
                        .getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(type, name, url));
    }

最后注入到orderController中的stock-servcie是个动态代理,h是feign.ReflectiveFeign.FeignInvocationHandler:


FeignContext针对每一个Feign服务都要一个Spring容器:


容器里面都是关于feign的相关配置:


2.1.2 SpringClientFactory什么时候创建

需要在真正调用的时候才创建。

容器里面都是关于ribbon的相关配置:


参考

相关文章

网友评论

      本文标题:SpringCloud里面的父子容器

      本文链接:https://www.haomeiwen.com/subject/vvuvldtx.html