Spring-Cloud-Context模块

SpringCloud这个框架本身是建立在SpringBoot基础之上的,所以使用SpringCloud的方式与SpringBoot相仿。也是通过类似如下代码进行启动。

SpringApplication.run(XxxApplication.class, args);

其中 XxxApplication.class 类上也需要添加 @SpringBootApplication注解。
要使用SpringCloud框架,在pom文件中要确保引入 spring-cloud-starter 依赖包,spring-cloud-starter依赖如下的jar:


        org.springframework.boot
        spring-boot-starter

        org.springframework.cloud
        spring-cloud-context

        org.springframework.cloud
        spring-cloud-commons

其中 spring-cloud-context-x.y.z.RELEASE.jarspring-cloud-commons-x.y.z.RELEASE.jar 下的 META-INF 目录下都包含 spring.factories 文件,所以 可以把这两个jar看作是springCloud程序的入口

SpringCloud在构建上下文

那么SpringCloud设计出Bootstrap Application Context ,并把它作为应用容器的父容器的 目的是什么呢?

因为SpringCloud 作为一个分布式微服务框架,需要使用 全局的配置中心,而配置中心的配置是可以提供给 应用容器的,所以在应用容器初始化和实例化Bean之前需要先完成配置中心的实例化,这个任务就由Bootstrap Application Context 来完成,而配置中心的相关配置属性就从 bootstrap.propertiesbootstrap.yml 文件中读取。
但要 注意的是,在Bootstrap Application Context 启动工作完成之后,其从bootstrap.properties或bootstrap.yml文件中读取的配置,是会被应用容器对应的application.properties或yml文件中的同名属性覆盖的。

从源码角度分析上面的论述

1.代码运行时还是从SpringApplication实例的run方法开始 ,此处会触发 BootstrapApplicationListener 类中的代码 , Bootstrap Application Context 的创建就是通过这个监听器触发的

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 此处会加载spring-cloud-context提供的监听器org.springframework.cloud.bootstrap.BootstrapApplicationListener.class
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 此处会发布ApplicationEnvironmentPreparedEvent事件,触发BootstrapApplicationListener中的代码
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

2.Bootstrap Application Context 的实例化 ,由BootstrapApplicationListener类的 onApplicationEvent方法触发

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    // 可以通过环境变量 spring.cloud.bootstrap.enabled来禁止使用Bootstrap容器
    if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
        return;
    }
    // 由于Bootstrap容器在创建时还是会再次调用上面步骤1的代码,还会再次触发
    // BootstrapApplicationListener类这个方法,所以此处作个判断,
    // 如果当前是Bootstrap容器的处理,则直接返回
    if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
        return;
    }
    ConfigurableApplicationContext context = null;
    // 获取配置文件的名字,默认为bootstrap.properties或.yml ,并且这个名字可以通过 spring.cloud.bootstrap.name在环境中配置
    String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
    for (ApplicationContextInitializer initializer : event.getSpringApplication()
         .getInitializers()) {
        // 从相应jar中的spring.factories文件中读取初始化器的配置类实例,如果这个实例类是
        // ParentContextApplicationContextInitializer类型,则直接从该类中获取到父容器
        // 默认情况下,没有提供这样的类,下面这段代码会跳过
        if (initializer instanceof ParentContextApplicationContextInitializer) {
            context = findBootstrapContext(
                (ParentContextApplicationContextInitializer) initializer,
                configName);
        }
    }
    if (context == null) {            // 此处分析见步骤3
        context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
        event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
    }

    apply(context, event.getSpringApplication(), environment);
}

3.bootstrap容器的创建

private ConfigurableApplicationContext bootstrapServiceContext(
    ConfigurableEnvironment environment, final SpringApplication application,
    String configName) {
    /**此处代码主要是从各处获取属性配置,此处忽略**/
    // TODO: is it possible or sensible to share a ResourceLoader?

    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);
    // SpringApplicationBuilder的作用:1.构建SpringApplication  2.构建ApplicationContext
    // 这里需要思考一下,SpringBoot在启动时已经构建了一个SpringApplication实例,为何此处又构建了一个
    // 这是因为这个SpringApplication实例的构建环境和SringBoot原生构建的那个不同,看一下上一行代码就能明白
    final SpringApplication builderApplication = builder.application();
    if(builderApplication.getMainApplicationClass() == null){
        builder.main(application.getMainApplicationClass());
    }
    if (environment.getPropertySources().contains("refreshArgs")) {
        builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
    }
    // 从springFactories文件中查找BootstrapConfiguration的配置类
    builder.sources(BootstrapImportSelectorConfiguration.class);
    // 构建出BootstrapContext
    final ConfigurableApplicationContext context = builder.run();
    context.setId("bootstrap");
    // 设置BootstrapContext成为应用Context的父容器,此处分析见步骤4
    addAncestorInitializer(application, context);
    // It only has properties in it now that we don't want in the parent so remove
    // it (and it will be added back later)
    bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    return context;
}

4.设置BootstrapContext成为应用Context的父容器 (在SpringApplication实例中动态添加了一个初始化器,相当于给应用Context埋了个雷)

private void addAncestorInitializer(SpringApplication application, ConfigurableApplicationContext context) {
    boolean installed = false;
    // 从spring.factories文件中获取初始化器的配置类且类型为AncestorInitializer
    for (ApplicationContextInitializer initializer : application.getInitializers()) {
        if (initializer instanceof AncestorInitializer) {
            installed = true;
            // New parent
            ((AncestorInitializer) initializer).setParent(context);
        }
    }
    // 默认情况下是没有配围置AncestorInitializer这样的类,此处是则执行由BootstrapListener提供的内部类
    if (!installed) {
        // 将BootstrapContext作为父容器传到AncestorInitializer实例中,并将其放入SpringApplication实例的初始器列表中
        application.addInitializers(new AncestorInitializer(context));
    }
}

5.初始化器AncestorInitializer被触发,是由应用Context的处理触发的

public void initialize(ConfigurableApplicationContext context) {
    // 这个context是应用Context
    while (context.getParent() != null && context.getParent() != context) {
        context = (ConfigurableApplicationContext) context.getParent();
    }
    reorderSources(context.getEnvironment());
    // 完成应用容器的父容器的设置
    new ParentContextApplicationContextInitializer(this.parent)
        .initialize(context);
}

6.ParentContextApplicationContextInitializer代码

private static class ParentContextApplicationContextInitializer
    implements ApplicationContextInitializer {
    private final ApplicationContext parent;

    ParentContextApplicationContextInitializer(ApplicationContext parent) {
        this.parent = parent;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 设置应用Context的父容器
        applicationContext.setParent(this.parent);
    }

}

Original: https://www.cnblogs.com/gotodsp/p/15604935.html
Author: dai.sp
Title: Spring-Cloud-Context模块

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541370/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • javaweb学习

    了解HTTP posted @2022-03-24 21:19 HelloHui 阅读(8 ) 评论() 编辑 Original: https://www.cnblogs.com/…

    Java 2023年6月9日
    079
  • 笔试小总结

    Arraylist里面存储默认是Object的,不能直接赋值给String类型的变量,需要强转。 值传递和引用传递 class Test { String str = new St…

    Java 2023年6月5日
    060
  • 在Spring中测试CGLIB动态代理遇到ClassNotFoundException: org.objectweb.asm.Type问题

    在Spring项目中进行简单的cglib动态代理的学习测试,主要代码如下: 一个目标类HaveDinnerService public class HaveDinnerServic…

    Java 2023年6月9日
    079
  • 如何写出同事看不懂的Java代码?

    原创:微信公众号 &#x7801;&#x519C;&#x53C2;&#x4E0A;,欢迎分享,转载请保留出处。 哈喽大家好啊,我是没更新就是在家忙着…

    Java 2023年6月5日
    090
  • 小程序码生成

    如何获取小程序码 小程序码的本质是,向腾讯的服务器发送一个 get 请求,携带两个参数: page 和 scene。 以获取id 为 1 的问题为例,在本项目中,它被封装成这样一个…

    Java 2023年6月7日
    076
  • mac下安装MYSQL-python

    今天在mac电脑上安装python的mysql时出现如下问题: 该安装时是使用sudo pip install MySQL-python来安装mysql模块,安装没有问题,但是在p…

    Java 2023年6月8日
    086
  • springboot项目Java轻松实现Excel导出

    Springboot项目Java轻松实现Excel导出 一、需求背景 要求Java后端实现一个Excel导出功能。 二、工程包引入 java;gutter:false; imple…

    Java 2023年5月29日
    060
  • SpringBoot定时任务-开箱即用分布式任务框架xxl-job

    除了前文介绍的ElasticJob,xxl-job在很多中小公司有着应用(虽然其代码和设计等质量并不太高,License不够开放,有着个人主义色彩,但是其具体开箱使用的便捷性和功能…

    Java 2023年6月6日
    075
  • IntelliJ IDEA 插件

    前言 几天前,一个小型社区合作伙伴私下和我聊天,问我使用了哪些idea插件。我想法的主题看起来不错。 IDEA激活码:https://docs.qq.com/doc/DTUJYWU…

    Java 2023年6月7日
    072
  • InnoDB学习(二)之ChangeBuffer

    ChangeBuffer是InnoDB缓存区的一种特殊的数据结构,当用户执行SQL对非唯一索引进行更改时,如果索引对应的数据页不在缓存中时,InnoDB不会直接加载磁盘数据到缓存数…

    Java 2023年6月8日
    075
  • Java项目启动时执行指定方法的几种方式

    Java项目启动时执行指定方法的几种方式 1.使用 @PostConstruct,作用于方法上面。 @Component public class PostConstruct { …

    Java 2023年5月29日
    096
  • 面向对象—多态

    package com.gao.test.Test6; public class Girl { //跟猫玩 // public void play(Cat cat){ // cat…

    Java 2023年6月5日
    052
  • 【leetcode】151. 颠倒字符串中的单词

    给你一个字符串 s ,颠倒字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空…

    Java 2023年6月6日
    078
  • 面向对象编程,不美了么?

    “我是旧时代的残党,新时代没有承载我的船。” 如果面向对象编程是一个人,我猜他自己在不断被非议的今天,一定会这样感慨。 说实话,我用面向对象方式编程已经十几…

    Java 2023年6月7日
    077
  • 为什么实体类要实现Serializable进行序列化?

    为什么实体类要实现Serializable进行序列化? 转载 ​ 客户端访问了某个能开启会话功能的资源,web服务器就会创建一个与该客户端对应的HttpSession对象,每个Ht…

    Java 2023年6月8日
    084
  • 我的友链朋友们

    Original: https://www.cnblogs.com/rayob1/p/16103182.htmlAuthor: Ray言午Title: 我的友链朋友们

    Java 2023年6月6日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球