跟我一起阅读SpringBoot源码(八)——准备应用上下文

312 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情 @TOC

入口

创建好上下文对象和异常报告对象后,开始准备上下文对象。 看下入口:

prepareContext(context, environment, listeners, applicationArguments, printedBanner);

再看下准备方法(这里想吐槽下,这么大串代码写个注释嘛,尽管是private的):

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);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// 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);
	}

setEnvironment

第一行setEnvironment看下接口和实现类的注释好了:

//org.springframework.context.ConfigurableApplicationContext#setEnvironment
/**
	 * 为此应用程序上下文设置Environment 。
	 * @param environment the new environment
	 * @since 3.1
	 */
	void setEnvironment(ConfigurableEnvironment environment);

//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#setEnvironment
/**
	 * 为此应用程序上下文设置Environment 。
	 * 将给定环境委托给基础的AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner成员。
	 */
	@Override
	public void setEnvironment(ConfigurableEnvironment environment) {
		super.setEnvironment(environment);
		this.reader.setEnvironment(environment);
		this.scanner.setEnvironment(environment);
	}
//org.springframework.context.support.AbstractApplicationContext#setEnvironment
	/**
	 *为此应用程序上下文设置Environment 。
	 * 默认值由createEnvironment()确定。 用此方法替换默认值是一个选项,但也应考虑通过getEnvironment()配置。 无论哪种情况,都应在refresh()之前执行此类修改。
	 * @see org.springframework.context.support.AbstractApplicationContext#createEnvironment
	 */
	@Override
	public void setEnvironment(ConfigurableEnvironment environment) {
		this.environment = environment;
	}

后期处理应用程序上下文

/**
	 * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
	 * apply additional processing as required.
	 * @param context the application context
	 */
	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}

这里beanNameGenerator 是null,resourceLoader也是null,只有addConversionService是true,那就debug先看下吧: 在这里插入图片描述 在这里插入图片描述

//org.springframework.beans.factory.support.AbstractBeanFactory#setConversionService
@Override
	public void setConversionService(@Nullable ConversionService conversionService) {
		this.conversionService = conversionService;
	}

貌似也没什么看的。

执行启动器

/**
	 * 在刷新之前,将任何ApplicationContextInitializer应用于上下文。
	 * @param context the configured ApplicationContext (not refreshed yet)
	 * @see ConfigurableApplicationContext#refresh()
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}
//org.springframework.boot.SpringApplication#getInitializers
/**
	 * Returns read-only ordered Set of the {@link ApplicationContextInitializer}s that
	 * will be applied to the Spring {@link ApplicationContext}.
	 * @return the initializers
	 */
	public Set<ApplicationContextInitializer<?>> getInitializers() {
		return asUnmodifiableOrderedSet(this.initializers);
	}

这个时候发现,我们SpringApplication在初始化的时候设置的Initializers在这个时候将执行初始化了 在这里插入图片描述 我们看看这7个执行器的执行初始化都干了什么吧:跟我一起阅读SpringBoot源码(九)——初始化执行器

这里留个想法,我们是不是可以在项目里面配置源里面配置加个源文件(META-INF\spring.factories)配置这些初始化器什么的就可以让spring在启动的时候给实例化了呢?

发布上下文已初始化事件

在完成上面的初始化器的初始化工作后,spring将这个事件多播到所有注册好的监听器上去。下面看看事件发布过程。

这里想起这就是监听者模式的一个实现示例。

看下发布入口,通过准备上下文传进来的SpringApplicationRunListeners执行contextPrepared进行事件发布。

listeners.contextPrepared(context);

如果没记错,前面好像是获取了一个侦听器放在listeners里面,在debug看下是哪个侦听器: 在这里插入图片描述 刚好是事件发布的侦听器。 在这里插入图片描述 大意应该就是说SpringApplicationRunListener用于发布SpringApplicationEvent事件,在上下文实际刷新前,用内部的ApplicationEventMulticaster去取触发这些事件。 接下来看下发布过程:

//org.springframework.boot.SpringApplicationRunListeners#contextPrepared
//注册的所有监听器都将该事件进行发布
void contextPrepared(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextPrepared(context);
		}
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

用多播的方式将ApplicationContextInitializedEvent事件进行发布出去。看下发布出去后监听者是怎么进行处理的:

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		//先解析事件类型,如果多播的时候没有发送事件类型则采用该事件的默认类型。
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		//获取执行器,但是此时此处是null
		Executor executor = getTaskExecutor();
		//获取监听该事件的监听器
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			//如果获取到执行器,则用执行器来执行侦听器的方法,这里是什么方法就不贴源码了,直接贴结果:onApplicationEvent
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			//没有执行器就直接执行,对于这执行器下面再讲。
			else {
				invokeListener(listener, event);
			}
		}
	}
	
	/**
	 * 返回与给定事件类型匹配的ApplicationListeners的集合。 不匹配的听众会尽早被排除在外。
	 * @param event the event to be propagated. Allows for excluding
	 * non-matching listeners early, based on cached matching information.
	 * @param eventType the event type
	 * @return a Collection of ApplicationListeners
	 * @see org.springframework.context.ApplicationListener
	 */
	protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {

		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

		// Quick check for existing entry on ConcurrentHashMap...
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}

		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			// Fully synchronized building and caching of a ListenerRetriever
			synchronized (this.retrievalMutex) {
				retriever = this.retrieverCache.get(cacheKey);
				if (retriever != null) {
					return retriever.getApplicationListeners();
				}
				retriever = new ListenerRetriever(true);
				Collection<ApplicationListener<?>> listeners =
						retrieveApplicationListeners(eventType, sourceType, retriever);
				this.retrieverCache.put(cacheKey, retriever);
				return listeners;
			}
		}
		else {
			// No ListenerRetriever caching -> no synchronization necessary
			return retrieveApplicationListeners(eventType, sourceType, null);
		}
	}

取得过程不多讲了,反正就是从先前注册的侦听器里面取,这里缓存里面有4个侦听器: 在这里插入图片描述 发布事件后这4个侦听器做了什么就不在这里描述了,单独为侦听器写了篇文章,去这里面看吧。 跟我一起阅读SpringBoot源码(十)——侦听器

logStartupInfo

上下文初始化完成后,启动日志信息

if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

日志信息是否启动可以通过设置logStartupInfo标致来控制

/**
	 * Called to log startup information, subclasses may override to add additional
	 * logging.
	 * @param isRoot true if this application is the root of a context hierarchy
	 */
	protected void logStartupInfo(boolean isRoot) {
		if (isRoot) {
			new StartupInfoLogger(this.mainApplicationClass).logStarting(getApplicationLog());
		}
	}
	void logStarting(Log applicationLog) {
		Assert.notNull(applicationLog, "Log must not be null");
		applicationLog.info(LogMessage.of(this::getStartingMessage));
		applicationLog.debug(LogMessage.of(this::getRunningMessage));
	}

如果当前上下文是根,那么实例化一个StartupInfoLogger来记录启动类的信息,并启动日志系统。

	/**
	 * Called to log active profile information.
	 * @param context the application context
	 */
	protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
		Log log = getApplicationLog();
		if (log.isInfoEnabled()) {
			String[] activeProfiles = context.getEnvironment().getActiveProfiles();
			if (ObjectUtils.isEmpty(activeProfiles)) {
				String[] defaultProfiles = context.getEnvironment().getDefaultProfiles();
				log.info("No active profile set, falling back to default profiles: "
						+ StringUtils.arrayToCommaDelimitedString(defaultProfiles));
			}
			else {
				log.info("The following profiles are active: "
						+ StringUtils.arrayToCommaDelimitedString(activeProfiles));
			}
		}
	}

接着打印启动使用的配置文件信息,激活的配置文件获取逻辑是:

  1. 根据spring.profiles.active配置来获取激活的配置文件,多个激活文件用逗号分隔。
	/**
	 * Return the set of active profiles as explicitly set through
	 * {@link #setActiveProfiles} or if the current set of active profiles
	 * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
	 * property and assign its value to the set of active profiles.
	 * @see #getActiveProfiles()
	 * @see #ACTIVE_PROFILES_PROPERTY_NAME
	 */
	protected Set<String> doGetActiveProfiles() {
		synchronized (this.activeProfiles) {
			if (this.activeProfiles.isEmpty()) {
				String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
					setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.activeProfiles;
		}
	}
  1. 如果没有配置激活文件activeProfiles,则获取默认配置文件:
	/**
	 * Return the set of default profiles explicitly set via
	 * {@link #setDefaultProfiles(String...)} or if the current set of default profiles
	 * consists only of {@linkplain #getReservedDefaultProfiles() reserved default
	 * profiles}, then check for the presence of the
	 * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
	 * to the set of default profiles.
	 * @see #AbstractEnvironment()
	 * @see #getDefaultProfiles()
	 * @see #DEFAULT_PROFILES_PROPERTY_NAME
	 * @see #getReservedDefaultProfiles()
	 */
	protected Set<String> doGetDefaultProfiles() {
		synchronized (this.defaultProfiles) {
			if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
				String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
				if (StringUtils.hasText(profiles)) {
					setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.defaultProfiles;
		}
	}
	
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
	
	/**
	 * Return the set of reserved default profile names. This implementation returns
	 * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to
	 * customize the set of reserved names.
	 * @see #RESERVED_DEFAULT_PROFILE_NAME
	 * @see #doGetDefaultProfiles()
	 */
	protected Set<String> getReservedDefaultProfiles() {
		return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
	}
	/**
	 * Name of property to set to specify profiles active by default: {@value}. Value may
	 * be comma delimited.
	 * <p>Note that certain shell environments such as Bash disallow the use of the period
	 * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
	 * is in use, this property may be specified as an environment variable as
	 * {@code SPRING_PROFILES_DEFAULT}.
	 * @see ConfigurableEnvironment#setDefaultProfiles
	 */
	public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

看上面的代码应该不难,看完总结一点就是,Spring默认的激活配置文件是default,然后这个默认配置可以通过spring.profiles.default进行修改。