springcloud bootstrap context加载过程解析以及怎么成为main context的parent

  1. The Bootstrap Application Context概念

这里是引用
A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application. By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence, so they cannot be overridden by local configuration.

翻译下意思:
Spring云应用程序bootstrap context进行操作(bootstrap context是主应用程序的父上下文)。bootstrap context负责从外部源加载配置属性,并解密本地外部配置文件中的属性。这两个上下文共享一个环境,该环境是任何Spring应用程序外部属性的来源。默认情况下,bootstrap属性(不是bootstrap.properties而是引导阶段加载的属性)具有较高的优先级,因此不能被本地配置覆盖。

截个图展示一下主上下文和bootstrap上下文
在这里插入图片描述
有了上图的直接认识后,我们来一步一步梳理下是怎么生成bootstrap context的
2. 入口代码

@EnableAutoConfiguration
public class SpringBootApplicationBootstrap {
    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringBootApplicationBootstrap.class)
                .run(args);
    }
}

SpringApplicationBuilder run方法org.springframework.boot.builder.SpringApplicationBuilder#run 进去

public ConfigurableApplicationContext run(String... args) {
		if (this.running.get()) {
			// If already created we just return the existing context
			return this.context;
		}
		configureAsChildIfNecessary(args);
		if (this.running.compareAndSet(false, true)) {
			synchronized (this.running) {
				// If not already running copy the sources over and then run.
				// 这里进去就是SpringApplication 的run方法
				this.context = build().run(args);
			}
		}
		return this.context;
	}

org.springframework.boot.SpringApplication#run(java.lang.String…) 方法进去, 这个就springboot启动的主方法,会将几个关键路径梳理

public ConfigurableApplicationContext run(String... args) {
		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);
			//1. 准备环境, 同时会进行广播事件,  广播到org.springframework.cloud.bootstrap.BootstrapApplicationListener  监听,
			// BootstrapApplicationListener监听器很重要, 监听器中会启动bootstrapcontext		
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 2. 准备上下文时会,会设置主上下的parent为bootstrap 上下文		
			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;
	}

3.org.springframework.boot.SpringApplication#prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 1. 广播事件,最终会调到org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

如下图,广播事件给监听器, 其中标出的就是org.springframework.cloud.bootstrap.BootstrapApplicationListener
在这里插入图片描述
org.springframework.cloud.bootstrap.BootstrapApplicationListener 是在spring.factories里配置的

在这里插入图片描述
在构造SpringApplication的时候通过spi的方式加载的
在这里插入图片描述
4. org.springframework.cloud.bootstrap.BootstrapApplicationListener 的监听方法

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		// don't listen to events in a bootstrap context
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		ConfigurableApplicationContext context = null;
		String configName = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
				.getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext(
						(ParentContextApplicationContextInitializer) initializer,
						configName);
			}
		}
		if (context == null) {
		    // 启动bootstrap上下文,传参event.getSpringApplication()就是主程序上下文
			context = bootstrapServiceContext(environment, event.getSpringApplication(),
					configName);
		}
		apply(context, event.getSpringApplication(), environment);
	}

org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext 启动bootstrap上下文

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		String configLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
		// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap will fail
		// force the environment to use none, because if though it is set below in the builder
		// the environment overrides it
		bootstrapMap.put("spring.main.web-application-type", "none");
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
		bootstrapProperties.addFirst(
				new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (source instanceof StubPropertySource) {
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		List<String> names = new ArrayList<>(SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader));
		for (String name : StringUtils.commaDelimitedListToStringArray(
				environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
			names.add(name);
		}
		// 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);
		final SpringApplication builderApplication = builder.application();
		if(builderApplication.getMainApplicationClass() == null){
			// gh_425:
			// SpringApplication cannot deduce the MainApplicationClass here
			// if it is booted from SpringBootServletInitializer due to the
			// absense of the "main" method in stackTraces.
			// But luckily this method's second parameter "application" here
			// carries the real MainApplicationClass which has been explicitly
			// set by SpringBootServletInitializer itself already.
			builder.main(application.getMainApplicationClass());
		}
		if (environment.getPropertySources().contains("refreshArgs")) {
			// If we are doing a context refresh, really we only want to refresh the
			// Environment, and there are some toxic listeners (like the
			// LoggingApplicationListener) that affect global static state, so we need a
			// way to switch those off.
			builderApplication
					.setListeners(filterListeners(builderApplication.getListeners()));
		}
		List<Class<?>> sources = new ArrayList<>();
		for (String name : names) {
			Class<?> cls = ClassUtils.resolveClassName(name, null);
			try {
				cls.getDeclaredAnnotations();
			}
			catch (Exception e) {
				continue;
			}
			sources.add(cls);
		}
		AnnotationAwareOrderComparator.sort(sources);
		builder.sources(sources.toArray(new Class[sources.size()]));
		// bootstrap上下文SpringApplicationBuilder 的run方法
		final ConfigurableApplicationContext context = builder.run();
		// gh-214 using spring.application.name=bootstrap to set the context id via
		// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
		// spring.application.name
		// during the bootstrap phase.
		context.setId("bootstrap");
		// Make the bootstrap context a parent of the app context
		//  创建主程序application的初始化器,如下图; 会将bootstrap context放到初始化器的parent中,初始化器再将parent传给主程序上下文
		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;
	}

在这里插入图片描述
主application新增的初始化器

  1. 初始化器再将parent传给主程序上下文,org.springframework.boot.SpringApplication#prepareContext方法内调用初始化器
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		// 应用初始化器
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

org.springframework.cloud.bootstrap.BootstrapApplicationListener.AncestorInitializer#initialize 调用BootstrapApplicationListener.AncestorInitializer的initialize方法进行初始化

public void initialize(ConfigurableApplicationContext context) {
			while (context.getParent() != null && context.getParent() != context) {
				context = (ConfigurableApplicationContext) context.getParent();
			}
			reorderSources(context.getEnvironment());
			// 实际是调用的ParentContextApplicationContextInitializer类的初始化方法, 这里parent就是bootstrap, context就是主程序应用上下文
			new ParentContextApplicationContextInitializer(this.parent)
					.initialize(context);
		}

org.springframework.boot.builder.ParentContextApplicationContextInitializer的初始化方法

@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		if (applicationContext != this.parent) {
		   // 设置parent
			applicationContext.setParent(this.parent);
			applicationContext.addApplicationListener(EventPublisher.INSTANCE);
		}
	}

总结:

  1. 构建SpringApplication时会将BootstrapApplicationListener监听器通过spi方式加载并注册
  2. 主SpringApplication run时,会prepareEnvironment, 并广播 ApplicationEnvironmentPreparedEvent事件
  3. BootstrapApplicationListener 监听到事件,进行bootstrapServiceContext启动bootstrap上下文, bootstrapServiceContext后addAncestorInitializer 添加初始化器给主应用上下文
  4. 主SpringApplication run方法内,调用prepareContext准备上下文方法时会调用applyInitializers应用初始化器
  5. 第3步添加的初始化器,org.springframework.cloud.bootstrap.BootstrapApplicationListener.AncestorInitializer(bootstrap context存在AncestorInitializer的parent中)会将bootstrap context设为main context的parent
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值