目录

  • 作者寄语
  • 容器概述
  • Bootstrap容器启动流程
  • 要点回顾
  • 关注我


作者寄语

今天我们来聊一下Spring的启动流程 以及SpringCloud的父子容器的初始化过程。这个在平时工作中面试中也是出现频率比较高的知识点。特别是SpringCloud的父子容器的概念,如果你的项目中引入了Feign和Ribbon组件 却不知道SpringCloud底层是怎么管理的 在实际开发中排查问题是非常困难的。

容器概述

在微服务的环境中 我们说的IOC容器实际上有三层意思:bootstrap容器,Spring容器,微服务组件容器,如下图所示

Spring什么是父子容器冲突 springboot没有父子容器_bootstrap容器

  1. bootstrapContext 我们常说的SpringCloud容器,由监听器创建 用来初始化SpringCloud上下文。
  2. springbootContext 我们接触最多的容器 平时用的就是这个容器
  3. NamedContextFactory 可以理解为微服务组件的容器工厂,用于管理所有组件容器

Bootstrap容器启动流程

由于启动代码非常多 所以我们文章不可能全部截取 后面贴的代码只是启动容器的主要流程代码

public static void main(String[] args) {
     //进入run方法 会发现最终是new 一个SpringApplication实例 然后调用实例的run方法来启动
    SpringApplication.run(SpringcoudApplication.class, args);
  }

上面的一行代码实际上做了两件事情

  1. 实例化一个SpringApplication对象
  2. 执行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。

Spring什么是父子容器冲突 springboot没有父子容器_IOC容器启动源码_02


看到上图我这里一共加载了13个listener,这里注意 项目依赖的jar包不同这里面可能会有变化。只需要注意第一个listener BootstrapApplicationListener 。这个监听器监听ApplicationEnvironmentPreparedEvent事件

执行SpringApplication的run方法:

StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> 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机制深入分析(一)