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

    SpringApplication.run(XxxApplication.class, args);

    其中 XxxApplication.class 类上也需要添加 @SpringBootApplication注解。

    要使用SpringCloud框架,在pom文件中要确保引入 spring-cloud-starter 依赖包, spring-cloud-starter 依赖如下的 jar :

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.springframework.cloud</groupId>
    8. <artifactId>spring-cloud-context</artifactId>
    9. </dependency>
    10. <dependency>
    11. <groupId>org.springframework.cloud</groupId>
    12. <artifactId>spring-cloud-commons</artifactId>
    13. </dependency>
    14. </dependencies>

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

    SpringCloud在构建上下文 (即ApplicationContext实例)时,采用了Spring父子容器的设计,会在 SpringBoot构建的容器(后面称之为应用容器)之上创建一父容器 Bootstrap Application Context .

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

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

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

    1. 代码运行时还是从SpringApplication实例的run方法开始 ,此处会触发 BootstrapApplicationListener 类中的代码 , Bootstrap Application Context 的创建就是通过这个监听器触发的
    1. public ConfigurableApplicationContext run(String... args) {
    2. StopWatch stopWatch = new StopWatch();
    3. stopWatch.start();
    4. ConfigurableApplicationContext context = null;
    5. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    6. configureHeadlessProperty(); //此处会加载spring-cloud-context提供的监听器org.springframework.cloud.bootstrap.BootstrapApplicationListener.class
    7. SpringApplicationRunListeners listeners = getRunListeners(args);
    8. listeners.starting();
    9. try {
    10. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    11. args); //此处会发布ApplicationEnvironmentPreparedEvent事件,触发BootstrapApplicationListener中的代码
    12. ConfigurableEnvironment environment = prepareEnvironment(listeners,
    13. applicationArguments);
    14. configureIgnoreBeanInfo(environment);
    15. Banner printedBanner = printBanner(environment);
    16. context = createApplicationContext();
    17. exceptionReporters = getSpringFactoriesInstances(
    18. SpringBootExceptionReporter.class,
    19. new Class[] { ConfigurableApplicationContext.class }, context);
    20. prepareContext(context, environment, listeners, applicationArguments,
    21. printedBanner);
    22. refreshContext(context);
    23. afterRefresh(context, applicationArguments);
    24. stopWatch.stop();
    25. if (this.logStartupInfo) {
    26. new StartupInfoLogger(this.mainApplicationClass)
    27. .logStarted(getApplicationLog(), stopWatch);
    28. }
    29. listeners.started(context);
    30. callRunners(context, applicationArguments);
    31. }
    32. catch (Throwable ex) {
    33. handleRunFailure(context, ex, exceptionReporters, listeners);
    34. throw new IllegalStateException(ex);
    35. }
    36. try {
    37. listeners.running(context);
    38. }
    39. catch (Throwable ex) {
    40. handleRunFailure(context, ex, exceptionReporters, null);
    41. throw new IllegalStateException(ex);
    42. }
    43. return context;
    44. }
    1. Bootstrap Application Context 的实例化 ,由BootstrapApplicationListener类的 onApplicationEvent方法触发
    1. public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    2. ConfigurableEnvironment environment = event.getEnvironment(); //可以通过环境变量 spring.cloud.bootstrap.enabled来禁止使用Bootstrap容器
    3. if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
    4. true)) {
    5. return;
    6. }
    7. // 由于Bootstrap容器在创建时还是会再次调用上面步骤1的代码,还会再次触发BootstrapApplicationListener类这个方法,所以此处作个判断,          如果当前是Bootstrap容器的处理,则直接返回
    8. if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    9. return;
    10. }
    11. ConfigurableApplicationContext context = null; //获取配置文件的名字,默认为bootstrap.properties或.yml ,并且这个名字可以通过 spring.cloud.bootstrap.name在环境中配置
    12. String configName = environment
    13. .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
    14. for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
    15. .getInitializers()) { //从相应jar中的spring.factories文件中读取初始化器的配置类实例,如果这个实例类是ParentContextApplicationContextInitializer类型 则直接从该类中获取到父容器,默认情况下,没有提供这样的类,下面这段代码会跳过
    16. if (initializer instanceof ParentContextApplicationContextInitializer) {
    17. context = findBootstrapContext(
    18. (ParentContextApplicationContextInitializer) initializer,
    19. configName);
    20. }
    21. }
    22. if (context == null) { //此处分析见步骤3
    23. context = bootstrapServiceContext(environment, event.getSpringApplication(),
    24. configName);
    25. event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
    26. }
    27. apply(context, event.getSpringApplication(), environment);
    28. }
    1. bootstrap容器的创建
    1. private ConfigurableApplicationContext bootstrapServiceContext(
    2. ConfigurableEnvironment environment, final SpringApplication application,
    3. String configName) {
    4. /** 此处代码主要是从各处获取属性配置,此处忽略 **/// TODO: is it possible or sensible to share a ResourceLoader?
    5. SpringApplicationBuilder builder = new SpringApplicationBuilder()
    6. .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
    7. .environment(bootstrapEnvironment)
    8. // Don't use the default properties in this builder
    9. .registerShutdownHook(false).logStartupInfo(false)
    10. .web(WebApplicationType.NONE); //SpringApplicationBuilder的作用:1.构建SpringApplication 2.构建ApplicationContext //这里需要思考一下,springboot在启动时已经构建了一个SpringApplication实例,为何此处又构建了一个 //这是因为这个SpringApplication实例的构建环境和SringBoot原生构建的那个不同,看一下上一行代码就能明白
    11. final SpringApplication builderApplication = builder.application();
    12. if(builderApplication.getMainApplicationClass() == null){
    13. builder.main(application.getMainApplicationClass());
    14. }
    15. if (environment.getPropertySources().contains("refreshArgs")) {
    16. builderApplication
    17. .setListeners(filterListeners(builderApplication.getListeners()));
    18. } //从springFactories文件中查找BootstrapConfiguration的配置类
    19. builder.sources(BootstrapImportSelectorConfiguration.class); //构建出BootstrapContext
    20. final ConfigurableApplicationContext context = builder.run();
    21. context.setId("bootstrap");
    22. // 设置BootstrapContext成为应用Context的父容器,此处分析见步骤4
    23. addAncestorInitializer(application, context);
    24. // It only has properties in it now that we don't want in the parent so remove
    25. // it (and it will be added back later)
    26. bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    27. mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    28. return context;
    29. }
    1. 设置BootstrapContext成为应用Context的父容器 (在SpringApplication实例中动态添加了一个初始化器,相当于给应用Context埋了个雷)
    1. private void addAncestorInitializer(SpringApplication application,
    2. ConfigurableApplicationContext context) {
    3. boolean installed = false; //从spring.factories文件中获取初始化器的配置类且类型为AncestorInitializer
    4. for (ApplicationContextInitializer<?> initializer : application
    5. .getInitializers()) {
    6. if (initializer instanceof AncestorInitializer) {
    7. installed = true;
    8. // New parent
    9. ((AncestorInitializer) initializer).setParent(context);
    10. }
    11. } //默认情况下是没有配围置AncestorInitializer这样的类,此处是则执行由BootstrapListener提供的内部类
    12. if (!installed) { //将BootstrapContext作为父容器传到AncestorInitializer实例中,并将其放入SpringApplication实例的初始器列表中
    13. application.addInitializers(new AncestorInitializer(context));
    14. }
    15. }
    1. 初始化器AncestorInitializer被触发,是由应用Context的处理触发的
    1. public void initialize(ConfigurableApplicationContext context) { //这个context是应用Context
    2. while (context.getParent() != null && context.getParent() != context) {
    3. context = (ConfigurableApplicationContext) context.getParent();
    4. }
    5. reorderSources(context.getEnvironment()); //完成应用容器的父容器的设置
    6. new ParentContextApplicationContextInitializer(this.parent)
    7. .initialize(context);
    8. }
    1. ParentContextApplicationContextInitializer代码
    1. private static class ParentContextApplicationContextInitializer
    2. implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    3. private final ApplicationContext parent;
    4. ParentContextApplicationContextInitializer(ApplicationContext parent) {
    5. this.parent = parent;
    6. }
    7. @Override
    8. public void initialize(ConfigurableApplicationContext applicationContext) {
    9. applicationContext.setParent(this.parent); //设置应用Context的父容器
    10. }
    11. }

    总结:

    1. SpringCloud Context模块的功能 :主要是构建了一个Bootstrap容器,并让其成为原有的springboot程序构建的容器的父容器。

    2. Bootstrap容器的作用:是为了预先完成一些bean的实例化工作,可以把Bootstrap容器看作是先头部队。

    3. Bootstrap容器的构建:是利用了Springboot的事件机制,当 springboot 的初始化 Environment 准备好之后会发布一个事件,这个事件的监听器将负责完成Bootstrap容器的创建。构建时是使用 SpringApplicationBuilder 类来完成的。

    1. **4. 如何让Bootstrap容器与应用Context 建立父子关系 :**由于Bootstrap容器与应用Context都是关联着同一个SpringApplication实例,Bootstrap容器自己完成初始化器的调用之后,会动态添加了一个初始化器 AncestorInitializer,相当于给应用Context 埋了个雷 ,这个初始化器在应用容器进行初始化器调用执行时,完成父子关系的设置。