辽宁大学网站怎么做/东莞seo技术培训
概述
今天我们来聊一下Spring的启动流程 以及SpringCloud的父子容器的初始化过程。这个在平时工作中面试中也是出现频率比较高的知识点。特别是SpringCloud的父子容器的概念,如果你的项目中引入了Feign和Ribbon组件 却不知道SpringCloud底层是怎么管理的 在实际开发中排查问题是非常困难的。
容器
在微服务的环境中 我们说的IOC容器实际上有三层意思:bootstrap容器,Spring容器,微服务组件容器,如下图所示
bootstrapContext 我们常说的SpringCloud容器,由监听器创建 用来初始化SpringCloud上下文。
springbootContext 我们接触最多的容器 平时用的就是这个容器
NamedContextFactory 可以理解为微服务组件的容器工厂,用于管理所有组件容器
Bootstrap容器启动流程
由于启动代码非常多 所以我们文章不可能全部截取 后面贴的代码只是启动容器的主要流程代码
public static void main(String[] args) { //进入run方法 会发现最终是new 一个SpringApplication实例 然后调用实例的run方法来启动 SpringApplication.run(SpringcoudApplication.class, args); }
上面的一行代码实际上做了两件事情
实例化一个SpringApplication对象
执行SpringApplication的run方法
实例化一个SpringApplication对象:
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); //下面两行代码 非常重要 1,使用SPI加载classpath下面所有key为ApplicationContextInitializer //全限定名的配置。 2,使用SPI加载classpath下面所有key为ApplicationListener的配置 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
上面的注释也提到了 实例化SpringApplication最重要的两行代码就是通过SPI的方式加载 ApplicationContextInitializer和ApplicationListener。我们下面跟踪代码看一下加载了哪些ApplicationListener。
看到上图我这里一共加载了13个listener,这里注意 项目依赖的jar包不同这里面可能会有变化。只需要注意第一个listener BootstrapApplicationListener 。这个监听器监听ApplicationEnvironmentPreparedEvent事件
执行SpringApplication的run方法:
StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection exceptionReporters = new ArrayList<>();configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //创建一个Environment实例 重点是在这里 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); ...略...
继续跟进prepareEnvironment 这一部分代码我们在 SpringBoot源码系列:Environment机制深入分析有详细描述
//创建一个ConfigurableEnvironment实例 ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置ConfigurableEnvironment configureEnvironment(environment, applicationArguments.getSourceArgs()); //这里会发一个ApplicationEnvironmentPreparedEvent事件,这样是不是和刚才的BootstrapApplicationListener联系上了。 //BootstrapApplicationListener监听的就是ApplicationEnvironmentPreparedEvent事件啊,所以很显然这里会执行BootstrapApplicationListener的onApplicationEvent方法 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment;
通过上面代码分析 spring在初始化环境变量的时候会发送ApplicationEnvironmentPreparedEvent事件,而BootstrapApplicationListener又会监听ApplicationEnvironmentPreparedEvent这个事件,所以在springboot容器没有创建之前 又到了BootstrapApplicationListener这个监听器这里。
接下来我们就看一下onApplicationEvent方法
@Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); //可以看到还可以通过spring.cloud.bootstrap.enabled=false来关闭bootstrap容器 //关闭了就不会实例化springcloud容器 if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,true)) { return; }//这里相当于是一个判断 容器只初始化一次 因为该监听器会执行多遍。为什么这个监听器会执行多遍?//因为第一次调用SpringApplication.run()会执行到创建环境变量的代码 在prepareEnvironment方法中会发送事件//但是当BootstrapApplicationListener监听到事件的时候又会通过SpringApplicationBuilder//来构建一个SpringApplication对象执行其run方法 所以该监听器 最少执行两次 执行几次根据你引入的包来决定不是绝对的 //如果环境变量中已经加载了bootstrap配置 那么久不会继续执行了 if(environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { return; } ConfigurableApplicationContext context = null; //找到对应配置的名字 spring.cloud.bootstrap.name:bootstrap 如果有配置 //spring.cloud.bootstrap.name属性 则返回这个属性对应的值 没有返回默认值bootstrap //注意 这里从environment中取属性 此时的environment还没来得及加载spring的配置文件 所以 //这个属性配置在系统属性或者 环境变量 或者servlet初始参数可以生效 在配置文件中不可能生效 String configName = environment .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}"); //这里检查我们有没有在ApplicationContextInitializer中定制化容器 这个接口我们在前面提到 //是通过spring.factories加载进SpringApplication对象中去的 我们这里不做进一步的展开 后面再对 //ApplicationContextInitializer做详细描述 for (ApplicationContextInitializer> initializer :event.getSpringApplication() .getInitializers()) { if (initializer instanceof ParentContextApplicationContextInitializer) { context = findBootstrapContext( (ParentContextApplicationContextInitializer) initializer, configName); } } if (context == null) { //这个方法是核心方法 我们继续跟进该方法 context = bootstrapServiceContext(environment, event.getSpringApplication(), configName); event.getSpringApplication() .addListeners(new CloseContextOnFailureApplicationListener(context)); } apply(context, event.getSpringApplication(), environment); }
继续跟进bootstrapServiceContext方法
//重新实例化一个Environment 但这个不是StandardServletEnvironment 我们通过之前的 //Environment机制深入分析 知道environment变量其实是一个StandardServletEnvironment,这也很好理解 bootstrap容器不需要构建在web上面 //所以目前bootstrapEnvironment里面只有两个配置 一个是系统变量 还有一个是系统环境变量 StandardEnvironment bootstrapEnvironment = new StandardEnvironment(); MutablePropertySources bootstrapProperties = bootstrapEnvironment .getPropertySources(); //这里是将系统变量和系统环境变量移除 因为不需要 最终bootstrapEnvironment是要和 //最开始初始化的那个Environment合并的 系统变量和系统环境变量在Environment中已经存在 for (PropertySource> source : bootstrapProperties) { bootstrapProperties.remove(source.getName()); } //可以通过spring.cloud.bootstrap.location系统变量来设置bootstrap配置文件的位置 String configLocation = environment .resolvePlaceholders("${spring.cloud.bootstrap.location:}"); Map<String, Object> bootstrapMap = new HashMap<>(); bootstrapMap.put("spring.config.name", configName); bootstrapMap.put("spring.main.web-application-type", "none"); if (StringUtils.hasText(configLocation)) { bootstrapMap.put("spring.config.location", configLocation); } //向bootstrap环境变量 增加一个bootstrap配置 bootstrapProperties.addFirst( new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap)); //又将environment中的配置放入 创建的配置中 这里需要排除占位配置 for (PropertySource> source : environment.getPropertySources()) { if (source instanceof StubPropertySource) { continue; } bootstrapProperties.addLast(source); } //这里我们启动springboot应用差不多 只不过是没有打印banner 环境变量是自定义的环境变量 //自定义环境变量任然可以参考我们之前关于spring环境变量的文章 非servlet容器 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())); } //这里可以稍稍注意一下 最终builder的sources 值会设置到SpringApplication的primarySources字段中 这里先不过多介绍我们后面文章会对这个展开 builder.sources(BootstrapImportSelectorConfiguration.class); //这里其实就是容器创建刷新的流程 后面讲springboot容器的时候会涉及这里就不做展开 final ConfigurableApplicationContext context = builder.run(); context.setId("bootstrap"); //为springboot容器添加父容器 这里你可能会问 到这里 springboot容器都还没开始创建呢? //是的 所以这里只是创建了一个AncestorInitializer加入到SpringApplication 等到springboot容器启动的时候会执行 //SpringApplication中的所有的ApplicationContextInitializer 执行的时候自动就会把当前的容器设置为 父容器 //内部实现为 new ParentContextApplicationContextInitializer(this.parent) // .initialize(context); addAncestorInitializer(application, context); bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME); //这里实际是把两个environment合并 所以到这bootstrap配置文件内容已经成功的放入 //environment mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties); return context;
到这里 bootstrap容器创建完成了。并且调用了SpringApplicationBuilder的run方法启动了容器。
要点回顾
SpringApplication run方法会创建环境变量对象 创建完成会发送ApplicationEnvironmentPreparedEvent事件。
BootstrapApplicationListener监听器监听了这个事件。
spring是怎么加载BootstrapApplicationListener监听器的?通过在SpringApplication的构造方法中通过SPI加载的。所以我们没有引入SpringCloud相关包的时候 是没有这个监听器的。
addAncestorInitializer(application, context); 设置父容器。在springboot容器还没有被创建的的时候。具体请看注释
关于Environment的文章参考 SpringBoot源码系列:Environment机制深入分析(一)