Spring Boot微服务个人见解

以前开发一个项目,要花费不少时间在搭建项目,配置文件上,到现在Spring Boot开箱即用,需要技术栈导入pom就可以了,技术变更带来效率提示是巨大的。有时候我会疑惑,这一切如何得来的,Spring Boot怎么抛弃war部署,抛弃繁琐xml配置。

阅读本文章需要一定的Spring框架知识储备,最后能了解Spring如何进行Bean初始化的,至少知道BeanDefinition之类的知识点,才能更好阅读文章。下面代码基于Spring Boot 2.7.2 、 Spring Cloud 2021.0.3。

先从项目启动放入入口,每一个Spring Boot 项目都需要main入口都要调用SpringApplication.run开始

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //web 项目类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList<>(
                getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

webApplicationType是一个枚举类,描述当前项目web类型
NONE: 当前项目不是一个web项目
SERVLET: 基于servlet api的传统web项目
REACTIVE: Spring webFlux 响应式web框架
deduceFromClasspath : 根据项目jar判断当前项目属于上面哪个一个类型,后面创建Spring 上下文对象需要用到
getSpringFactoriesInstances:从给定接口从文件META-INF/spring.factories 使用类名去加载全类名,并且返回接口所有实现类, 配置文件格式如下

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

这个类似JVM的SPI机制,对于Spring为什么没有使用SPI来 引入扩展实例,我猜SPI不满足多构造参数的实现类初始化,这里暂时将这种机制称作:SpringFactoriesLoader加载

BootstrapRegistryInitializer:用于初始化BootstrapRegistry的回调接口,在 使用BootstrapRegistry之前调用它。

ApplicationContextInitializer:在执行Spring工厂类调用AbstractApplicationContext.refresh(Spring 工厂核心方法bean初始化)之前初始化ConfigurableApplicationContext的回调接口。主要是做一个配置文件设置、属性设置。
ConfigurableApplicationContext 是一个SPI接口用于通过 配置方式初始化ApplicationContext 。Spring Boot作为Spring框架的集大成者上下文对象ApplicationContext往往根据不同环境有所区别的,这时很需要ApplicationContextInitializer这种接口,由不同组件根据自身情况去实现接口初始化上下文对象。

ApplicationContextInitializer接口

DelegatingApplicationContextInitializer: 通过环境变量 context.initializer.classes 类名,加载所有ConfigurdiableApplicationContext子类,实例化,排序执行ApplicationContextInitializer接口(接口参数)。
SharedMetadataReaderFactoryContextInitializer: 注册CachingMetadataReaderFactoryPostProcessor 用于向容器注册SharedMetadataReaderFactoryBean,用于缓存Spring加载资源
ContextIdApplicationContextInitializer: 初始化ContextId
ConfigurationWarningsApplicationContextInitializer:报告@ComponentScan配置错误信息输入告警日志
RSocketPortInfoApplicationContextInitializer: 创建一个监听事件,将server.ports赋值到 local.rsocket.server.port
ServerPortInfoApplicationContextInitializer: 创建web事件监听: 发布server namespace网络端口
ConditionEvaluationReportLoggingListener: 创建一个事件监听,spring初始化成功或失败,打印相关信息。

ApplicationListener列表

EnvironmentPostProcessorApplicationListener: 监听ApplicationEnvironmentPreparedEvent事件,执行EnvironmentPostProcessor 配置文件前置处理器,加载配置文件到ConfigurableEnvironment
AnsiOutputApplicationListener: 监听Spring刚启动事件,从配置文件加载ansi配置。
LoggingApplicationListener: 加载日志相关配置进行初始化设置。
BackgroundPreinitializer: 通过多线程方式初始化Formatter、Validation、HttpMessageConverter、jackson、UTF-8设置。
DelegatingApplicationListener:从配置文件 key:context.listener.classes加载监听器类名并实例化注册到容器中
ParentContextCloserApplicationListener: 监听父级容器关闭事件,并且将事件传递到子级逐级传递下取。
ClearCachesApplicationListener: 清除类加器缓存
FileEncodingApplicationListener: 检测当前系统环境的file.encoding和spring.mandatory-file-encoding设置的值是否一样,如果不一样的话,就会抛出一个IllegalStateException异常,程序启动立马停止

run方法

    public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
                //调用BootstrapRegistryInitializer接口对上下文进行初始化
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
                // 设置 java.awt.headless  缺失显示设备需要CPU介入显示
        configureHeadlessProperty();
                //获取事件发布器实例,这里会将上面监听器实例装进发布器,监听器类似事件消费者
        SpringApplicationRunListeners listeners = getRunListeners(args);
                //发布starting 事件
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
             //获取所有启动参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //创建配置文件对象
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
             //从配置文件中忽视bean
            configureIgnoreBeanInfo(environment);
            //Banner  配置 打印
            Banner printedBanner = printBanner(environment);
             //使用ApplicationContextFactory 初始化ApplicationContentx Spring 工厂
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //配置文件对象配置
            //开始对applicationContext context 进行初始化
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context); // 调用refresh
            //空方法
            afterRefresh(context, applicationArguments);
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
            }
            listeners.started(context, timeTakenToStartup);
             //调用所有 ApplicationRunner   CommandLineRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

run()Spring Boot框架启动流程

  1. 获取Java 命令行启动参数,从中提取Spring 配置参数,转换从对应变量
  2. 创建配置文件对象ConfigurableEnvironment ,命令行中会有profile设置,所以要根据profile加载配置文件,在执行配置文件事件
  3. 已经加载好文件了,从环境变量中检测是否存在配置spring.beaninfo.ignore,如果设置,写入到ConfigurableEnvironment中
  4. 开始打印banner,平常看到各种banner就是在这里执行
  5. 开始创建ConfigurableApplicationContext ,Spring 容器工厂上下文对象
  6. 对刚刚创建ConfigurableApplicationContext 调用ApplicationContextInitializer 进行属性设置
  7. 启动Spring 容器IOC、AOP
  8. 发布Spring启动完成事件
  9. 从容器中所有ApplicationRunner CommandLineRunner在调用方法
    在run方法里面就完成完成整个Spring容器启动流程了,包括Spring Cloud加载也是这里完成的。下面详细分析prepareEnvironment(),配置文件上下文如何初始化的

prepareEnvironment

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // 根据webApplicationType 创建
        // SERVLET =>  ApplicationServletEnvironment
        //REACTIVE=> ApplicationReactiveWebEnvironment
        // NONE => ApplicationEnvironment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 命令行可能会有profile,可以选择那个profile,也会将命令行参数生成一个PropertySources
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 添加 configurationProperties  PropertySource到propertySourceList 队列最前面
        ConfigurationPropertySources.attach(environment);
        // 执行所有SpringApplicationRunListener
        listeners.environmentPrepared(bootstrapContext, environment);
        // 将defaultProperties sources 移致队尾
        DefaultPropertiesPropertySource.moveToEnd(environment);
        Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                "Environment prefix cannot be set via properties.");
        // 从配置文件对应spring.main.* 属性注入
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
           //将类型转换器设置到environment
            environment = convertEnvironment(environment);
        }
        // 因为EnvironmentPostProcessor  可能加载到配置文件里,这时需要configurationProperties 放入第一
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

getOrCreateEnvironment() 如果当前environment如何为空,则会根据根据webApplicationType 类型选择对应类进行初始化。大家可能好奇environment怎么可能有值呢,接着玩下看,当我分析Spring Cloud时你就会返回environment不需要创建了。
ps: Environment 内部使用PropertySource区分不同配置文件,每一个源配置都有自己的名字,比如系统变量systemProperties、环境变量systemEnvironment等等。使用一个propertySourceList一个list将所有PropertySource保存起来,在队列前面永远最优先加载。


image.png

在上面写过一个监听器EnvironmentPostProcessorApplicationListener,它处理environmentPrepared事件,使用SpringFactoriesLoader加载所有EnvironmentPostProcessor 前置处理器,其中之一ConfigDataEnvironmentPostProcessor就是去做读取配置文件,里面还有很多逻辑处理,这里就不展开了,有兴趣的同学自行去分析代码。读取文件本身也是根据环境变量来的,这里有几个Spring内置配置

  • spring.config.location 设定加载文件路径,没有则是使用类路径./、config/
  • spring.config.additional-location: 加载外部文件路径,这个可以spring.config.location 共存,优先级最大
  • spring.config.name 设定文件名前置,默认 application
    上面这些变量都是从环境变量、系统变量中获取的,当然不会从配置文件读取到。通过设定文件路径、文件名这样方式确定加载文件,加载文件规则如下

{spring.config.location}/{spring.config.name}-{profile}.{extension}

  • extension:文件名后缀 内置支持4种,分别是: properties、yml、xml、yaml
    看下ConfigurableApplicationContext 如何被初始化的

prepareContext

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        // 初始化 ConfigurableEnvironment
        context.setEnvironment(environment);
        //初始化resourceLoader ConversionService
        postProcessApplicationContext(context);
        //执行上面从SpringFactoriesLoader加载 ApplicationContextInitializer  对ConfigurableApplicationContext 属性设置
        applyInitializers(context);
        // 调用SpringApplicationRunListener.contextPrepared 事件
        listeners.contextPrepared(context);
        // 执行BootstrapContextClosedEvent 事件
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        //下面添加特定单例对象,为Spring初始化bean IOC 处理必要的bean
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        // 这里已经从配置文件加载 设置到自身属性上了,这时设置给上下文对象
        // allowCircularReferences 允许同名bean覆盖   lazyInitialization 对所有bean使用懒加载
        if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) beanFactory)
                        .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // 这个前置处理器主要作用就是将配置defaultProperties 移到队尾
        context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
        // Load the sources 这里有启动类
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 初始化 BeanDefinitionLoader   并将启动类注册成BeanDefinition
        load(context, sources.toArray(new Object[0]));
        // 所有监听器执行contextLoaded 事件
        listeners.contextLoaded(context);
    }

在这里完成了Spring 容器初始化,下一步就是启动了。

bean初始化

其实我一直很好奇@Configuration这个注入如何实现配置类,还有还么多Class要被Spring进行初始化,如何变成BeanDefinition最后变成bean。我确定从AbstractApplicationContext.refresh()debug,终于被我发现Spring魔法,在invokeBeanFactoryPostProcessors()
在执行invokeBeanFactoryPostProcessors方法中回去获取BeanDefinitionRegistryPostProcessor类型内置对象,并且执行所有实现类。

  • BeanDefinitionRegistryPostProcessor: 你可以理解成BeanDefinition注册前置处理器,主要就是生成BeanDefinition,再还给容器。在Spring还没有初始化bean时,这个接口运行实现类去初始化BeanDefinition再交还给Spring工厂对象,简白点就是这个对象会创建BeanDefinition,交给Spring,后续进行初始化bean。下面要讲解其中一个实现类ConfigurationClassPostProcessor

postProcessBeanFactory创建postProcessBeanFactory

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) { //这个方法只能执行一次,通过记录上下文id标记执行
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
        // 解析Class 生成BeanDefinition
        processConfigBeanDefinitions(registry);
    }

    /**
     * Build and validate a configuration model based on the registry of
     * {@link Configuration} classes.
     */
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        //candidateNames 为前期使用BeanDefinitionRegistry 添加进去单例对象,除了拥有Spring 工厂对象外还有
        // SpringBoot main 启动类 这里能起到作用就是Spring Boot main 函数
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            // 检查beanDef 是不是配置类,带有@Configuration都算
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        // 这里不存在没有配置类,只有配置@SpringBootApplication Class 就是一个配置类
        if (configCandidates.isEmpty()) {
            return;
        }

        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }

        // Parse each @Configuration class
        // ConfigurationClassParser 看名字就知道,这是一个解析@Configuration 解析类
        // 将解析Class 工作专门委派给parse去做了,解析后的结果会变成 ConfigurationClass
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do { //这里是一个循环
            StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
            parser.parse(candidates);
            parser.validate();
            //已经将所有配置类全部解析出来 变成ConfigurationClass
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed); // 删除已经解析过

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses); //将所有ConfigurationClass 转化BeanDefinition ,并注册到容器中
            alreadyParsed.addAll(configClasses); //添加已经注册过的,上面删除对应
            processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

            candidates.clear();
            // 当ConfigurationClassParser  解析出ConfigurationClass 就会大于candidateNames
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        // bd 就是一个配置类
                        // bd 已经注册到容器中,但是不是在ConfigurationClassParser 解析出来的结果,则说明bd并没有通过解析生成
                        // 可能为第三方 BeanDefinitionRegistryPostProcessor 生成BeanDefinition,加入到candidates 再次进入循环中
                        //被ConfigurationClassParser 解析,可以生成更多BeanDefinition
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty()); // 当所有BeanDefinition 都已经被解析完了,循环就可以退出了
        //下面省略


    }

看完上面的代码,ConfigurationClassPostProcessor就是Spring将带有@Configuration 标记Class经过一系列处理生成BeanDefinition的机制。在@SpringBootApplication 中有个一个@EnableAutoConfiguration带有@Import(AutoConfigurationImportSelector.class),这个会被ConfigurationClassPostProcessor解析加载。其中AutoConfigurationImportSelector使用SpringFactoriesLoader加载,会将所有@EnableAutoConfiguration的配置类全部都加载ClassName,可以让Spring Boot 加载ScanPackage 基础包路径之外的配置类,再通过@ConditionalOnBean、@ConditionalOnProperty这类注解,根据Class、配置判断是否进行解析。
也就是说Spring Boot一开始就已经获取到所有配置类,只有当符合条件时才会进入解析、加载、实例化。

Spring Cloud

上面说了Spring Boot自动化配置接下来就是Spring Cloud方面,看了上面源码,发现没有代码有关Spring Cloud,现在还不知道配置中心的配置如何作用到已经开始运行Spring 容器中。在开始分析代码之前,先简单看一个例子


image.png

可以看到applicatioinContext 有一个父级上下文,而这个就是Spring Cloud 上下文对象。看到这个是不是很惊奇,这个父级上下文在哪里初始化的呢,从代码角度去看了。
上面分析过ApplicationListener监听器中,在Spring Cloud lib jar中有一个实现类BootstrapApplicationListener,通过它来启动Spring Cloud。

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        // 两个条件  environment 配置 spring.cloud.bootstrap.enabled 或者某个类是否存在,其实就是 spring-cloud-starter-bootstrap jar class
        // 配置 spring.config.use-legacy-processing  这个配置是用来兼容旧版本配置文件加载
        //我这里环境引入spring-cloud-starter-bootstrap  第一个条件返回true,第二条件不用判断
        if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
            return;
        }
        // don't listen to events in a bootstrap context
        // 判断environment 是否已经存在bootstrap 文件,已经加载过不需要往下执行了
        //当父级初始化也会执行监听器事件,到时来到这里时,父级监听器不会往下执行了
        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }
        ConfigurableApplicationContext context = null;
        // 默认配置文件名,没有在环境变量配置默认就是bootstrap
        String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) { //在已经存在ParentContextApplicationContextInitializer 中返回父级容器
                context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
            }
        }
        if (context == null) { //当上面ParentContextApplicationContextInitializer 没有执行就会走下面初始化父级容器方法
            // 这里会返回父级容器,也就是Spring Cloud 上下文对象
            context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
            event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
        }
        //从父级容器中获取ApplicationContextInitializer 交给SpringApplication
        //父级生成ApplicationContextInitializer 用于增强子类
        apply(context, event.getSpringApplication(), environment);
    }

这个监听器主要根据配置文件信息来启动Spring Cloud组件,如果没有相应的配置根据项目环境来,看下Spring Cloud上下文如何被初始化出来的。

    private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
            final SpringApplication application, String configName) {
        ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() {
        };
        MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
        String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
        String configAdditionalLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
        Map<String, Object> bootstrapMap = new HashMap<>();
        // 使用代码生成一个Spring Cloud加载文件的配置信息,规则类似上面加载applicaton 配置
        bootstrapMap.put("spring.config.name", configName);
        bootstrapMap.put("spring.main.web-application-type", "none");
        if (StringUtils.hasText(configLocation)) {
            bootstrapMap.put("spring.config.location", configLocation);
        }
        if (StringUtils.hasText(configAdditionalLocation)) {
            bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
        }
                //将加载文件的配置信息放入配置文件上下文 environment
        bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
        for (PropertySource<?> source : environment.getPropertySources()) {
            if (source instanceof StubPropertySource) {
                continue;
            }
            bootstrapProperties.addLast(source);
        }
        // TODO: is it possible or sensible to share a ResourceLoader?
        // SpringApplicationBuilder  为SpringApplication 包装类,重新生成SpringApplication来创建ApplicationContext 上下文
        SpringApplicationBuilder builder = new SpringApplicationBuilder().profiles(environment.getActiveProfiles())
                .bannerMode(Mode.OFF).environment(bootstrapEnvironment)
                // Don't use the default properties in this builder
                .registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
        final SpringApplication builderApplication = builder.application();
        if (builderApplication.getMainApplicationClass() == null) {
            builder.main(application.getMainApplicationClass());
        }
        if (environment.getPropertySources().contains("refreshArgs")) {
            builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
        }
        / BootstrapImportSelectorConfiguration
        builder.sources(BootstrapImportSelectorConfiguration.class);
        // 这里将调用SpringApplication.run 上面已经分析,
        final ConfigurableApplicationContext context = builder.run();
        context.setId("bootstrap");
        //这里添加AncestorInitializer 是一个ApplicationContextInitializer 实现类,目的就是让子applicationContext 和父级关联起来
        addAncestorInitializer(application, context);
        //当前environment 为子集配置对象,这里要删除掉父级加载文件信息
        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
        //将 springCloudDefaultProperties  配置文件信息copy到environment 中
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

现在所有代码都看完了,我们来理一理整一个流程就会清晰明了。
在BootstrapApplicationListener中会根据配置文件或者是项目环境jar来是否启动加载bootstrap配置文件。先从生成加载Spring Cloud配置信息,
使用SpringApplicationBuilder来构建SprAppingApplication对象,然后执行SpringApplication.run 方法,这个代码我们已经分析过了,初始化Spring容器上下文对象,然后进入核心refresh方法执行IOC。SpringApplicationBuilder构造SpringApplication 中没有像我们写启动类main方法,会设置启动类Class。所以被ConfigurationClassPostProcessor解析BeanDefinition,并没有@SpringApplication 这个注解,所以这个Spring Cloud 工厂没有获取到basepackae、@EnableAutoConfiguration这些东西。根据上面代码知道Spring Cloud将BootstrapImportSelectorConfiguration 作为BeanDefinition交给ConfigurationClassPostProcessor,这样父级容器只有加载BootstrapConfiguration标记类,父级bean和子级bean相互隔离。这样父级容器就可以去启动与Spring Cloud有关的bean。当Spring Cloud容器已经完成bean初始化后,再来执行SpringApplicaton.run 启动Spring 容器创建。这样在子级启动之前已经将配置中心的配置对应的对象已经创建出来了。再通过ApplicationContextInitializer接口将配置对象加载ConfigurableEnvironment中。

这里使用较短的篇幅来分析Spring Boot这个框架如何工作,站在自己的思维上,使用3个知识点来展示Spring Boot技术细节实现。第一个从SpringApplication.run了解Spring两大工厂对象ConfigurableApplicationContextConfigurableEnvironment如何初始化处理出来的,配置文件如何被加载的,加载规则,知识点SpringFactoriesLoader机制,如果要做Spring Boot组件必须要这个。了解了Spring Boot ApplicationContextInitializer、ApplicationListener这些接口,还有SpringApplicationRunListener为整个Spring Boot事件监听器,对应整个框架的不同阶段处理。第二简单分析了
Spring容器启动时如何生成BeanDefinition的机制实现类:BeanDefinitionRegistryPostProcessor,了解了Spring Boot组件如何被加载、实例化,这个依赖启动类的注解。最后Spring Cloud组件如何加载实例化,这个依赖于前面两个。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容