-- 以下内容均基于2.1.8.RELEASE版本

紧接着上一篇(三)SpringBoot启动过程的分析-创建应用程序上下文,本文将分析上下文创建完毕之后的下一步操作:预处理上下文容器。

预处理上下文容器

预处理上下文容器由prepareContext()方法完成,本篇内容全部都是基于这个方法所涉及的内容进行分析。

// SpringApplication.java

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {

	// 设置环境对象,传入的对象是解析完毕profiles的对象,Context内部则是不完整的对象
	context.setEnvironment(environment);
	// 设置上下文参数
	postProcessApplicationContext(context);
	// 加载ApplicationContextInitializers
	applyInitializers(context);
	// 触发开始准备上下文事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 将启动参数包装为名为springApplicationArguments的DefaultApplicationArguments对象,并以单例模式注册
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

	// 设置打印的Banner
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}

	// 设置是否允许覆盖BeanDefinition
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}

	// 加载资源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));

	// 触发上下文加载完毕事件
	listeners.contextLoaded(context);
}

设置上下文参数

// SpringApplication.java

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {

	// 以单例模式注册beanNameGenerator
	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());
	}
}

将前面步骤初始化的属性赋值给上下文容器,代码中的this代表的是SpringApplication。

加载ApplicationContextInitializers

在SpringApplication.run()的一开始它就通过SPI获取到所有的ApplicationContextInitializers,在这里他们将被执行。

protected void applyInitializers(ConfigurableApplicationContext context) {
	// 调用每一个实现类的initialize方法
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}

// 将之前获取到的集合进行排序并返回只读集合
public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}

DelegatingApplicationContextInitializer

用于初始化配置文件(属性名:context.initializer.classes)中指定的ApplicationContextInitializer实现类

public void initialize(ConfigurableApplicationContext context) {
	// 获取环境对象
	ConfigurableEnvironment environment = context.getEnvironment();
	// 获取context.initializer.classes属性指定的实现类
	List<Class<?>> initializerClasses = getInitializerClasses(environment);
	if (!initializerClasses.isEmpty()) {
		// 调用其initialize()方法
		applyInitializerClasses(context, initializerClasses);
	}
}

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
	// 通过指定属性去获取,此处常量属性值为context.initializer.classes
	String classNames = env.getProperty(PROPERTY_NAME);
	List<Class<?>> classes = new ArrayList<>();
	if (StringUtils.hasLength(classNames)) {
		// 根据代码可以推断出,context.initializer.classes的值可以用逗号拼接
		for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			classes.add(getInitializerClass(className));
		}
	}
	return classes;
}

这里看起来好像很奇怪,本身当前的类就是一个ApplicationContextInitializer, 它已经被上游代码调用了initializer()方法,在initializer()方法中它又去获取ApplicationContextInitializer,然后接着调用initializer(),好像很绕。不过它的类描述已经说明了问题,它用于加载从配置文件指定
的那些ApplicationContextInitializer。如果阅读过第一篇概览SpringApplication.java #构造方法的话就会明白,当前对象就是在SpringApplication类的构造方法中通过SPI方式获取到的,而当前方法则是通过配置文件指定的方式
来获取。由此就可以得出一个结论:在SpringBoot中实现ApplicationContextInitializer并确保其被加载有三种方法,一是通过SpringApplication公开的addInitializers()方法直接添加,二是以SPI方式配置,三是以配置文件方式配置。

在SpringBoot中三种配置ApplicationContextInitializer的方法:

SharedMetadataReaderFactoryContextInitializer

在ConfigurationClassPostProcessor和Spring Boot之间创建共享的CachingMetadataReaderFactory, 干啥的我也不知道,后续debug在研究。

ContextIdApplicationContextInitializer

用于设置SpringApplication.getId()的值,如果配置文件中指定了spring.application.name,则本类不起作用,反之。

ConfigurationWarningsApplicationContextInitializer

添加BeanFactoryPostProcessor:ConfigurationWarningsPostProcessor用于打印配置错误的日志

ServerPortInfoApplicationContextInitializer

用于设置server.ports属性的值,它是Web服务器实际监听的应用程序端口。同时实现了ApplicationListener接口用于监听WebServerInitializedEvent事件,WebServer初始化完毕之后设置端口。

// ServerPortInfoApplicationContextInitializer.java

// ①
public void initialize(ConfigurableApplicationContext applicationContext) {
	applicationContext.addApplicationListener(this);
}

// ②
public void onApplicationEvent(WebServerInitializedEvent event) {
	String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
	setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}

// ③
private String getName(WebServerApplicationContext context) {
	String name = context.getServerNamespace();
	return StringUtils.hasText(name) ? name : "server";
}

// ④
private void setPortProperty(ApplicationContext context, String propertyName, int port) {
	if (context instanceof ConfigurableApplicationContext) {
		setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), propertyName, port);
	}
	if (context.getParent() != null) {
		setPortProperty(context.getParent(), propertyName, port);
	}
}

@SuppressWarnings("unchecked")
private void setPortProperty(ConfigurableEnvironment environment, String propertyName, int port) {
	MutablePropertySources sources = environment.getPropertySources();
	PropertySource<?> source = sources.get("server.ports");
	if (source == null) {
		source = new MapPropertySource("server.ports", new HashMap<>());
		sources.addFirst(source);
	}
	((Map<String, Object>) source.getSource()).put(propertyName, port);
}

① - 向上下文监听器列表中添加当前类(作为监听器加入)
② - 在监听事件中设置端口,默认属性为local.server.port
③ - 若设置了服务器Namespace,则属性值为local.Namespace的值.port
④ - 将监听端口信息填入server.ports属性下

ConditionEvaluationReportLoggingListener

用于设置ConditionEvaluationReportListener监听器,此监听器监听ContextRefreshedEvent和ApplicationFailedEvent,分别打印出上下文容器成功刷新和失败的日志报告

在这一章节中,可以看到ApplicationContextInitializer扩展接口的实际应用。通过Spring框架内置的一些initializer可以实现框架功能,同理我们也可以利用这一特性在上下文容器还未刷新之前做一些扩展功能。

发布ApplicationContextInitializedEvent事件

当上下文容器加载完毕所有的ApplicationContextInitializer之后,触发该事件,通知那些关注了此事件的监听器进行下一步操作。每次发布事件的时候可能已经有新的监听器被加进来,在上一章节中我们就看到会在ApplicationContextInitializer里面添加监听器
但我们只需要关注那些监听了当前事件的监听器即可。这里仅仅作为一个提示,监听器可能在任何地方被加进来。这里并未发现有监听器监听此事件。

记录活动的Profiles日志

日志打印的:The following profiles are active: dev,test 这一句就来自于此。代码比较简单一看便知。

if (this.logStartupInfo) {
	logStartupInfo(context.getParent() == null);
	logStartupProfileInfo(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));
		}
	}
}

注册启动SpringApplication时的参数

将参数对象注册为单例模式

// SpringApplication.java

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

是否允许覆盖Bean定义

这个是和spring.main.allow-bean-definition-overriding参数有关,默认情况下在DefaultListableBeanFactory中是true,但是SpringBoot在此处给设置成了false。

if (beanFactory instanceof DefaultListableBeanFactory) {
	((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}

加载Bean到上下文中

加载需要两个必备条件:谁来加载Bean,从哪加载Bean;此处加载BeanDefinition是由BeanDefinitionLoader来完成。

// SpringApplication.java#prepareContext()

// 获取当前应用要加载的资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载Bean
load(context, sources.toArray(new Object[0]));

// 创建Bean加载器,并加载
protected void load(ApplicationContext context, Object[] sources) {
	if (logger.isDebugEnabled()) {
		logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
	}
	// 创建BeanDefinitionLoader
	BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
	if (this.beanNameGenerator != null) {
		loader.setBeanNameGenerator(this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		loader.setResourceLoader(this.resourceLoader);
	}
	if (this.environment != null) {
		loader.setEnvironment(this.environment);
	}
	loader.load();
}



初始化BeanDefinitionLoader

用于创建BeanDefinitionLoader,并从source加载类,它是对加载Bean的整体功能做了封装,内部由不同的资源加载类来完成不同类型的资源加载,例如从基于注解的类来开始加载,从xml文件开始加载

// SpringApplication.java

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
	return new BeanDefinitionLoader(registry, sources);
}

class BeanDefinitionLoader {

	// 类的来源
	private final Object[] sources;

	// JavaConfig类读取器
	private final AnnotatedBeanDefinitionReader annotatedReader;

	// xml文件类读取器
	private final XmlBeanDefinitionReader xmlReader;

	// groovy类读取器
	private BeanDefinitionReader groovyReader;

	// classpath类读取器
	private final ClassPathBeanDefinitionScanner scanner;

	// 资源加载器
	private ResourceLoader resourceLoader;

	BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
		Assert.notNull(registry, "Registry must not be null");
		Assert.notEmpty(sources, "Sources must not be empty");
		this.sources = sources;
		this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
		this.xmlReader = new XmlBeanDefinitionReader(registry);
		if (isGroovyPresent()) {
			this.groovyReader = new GroovyBeanDefinitionReader(registry);
		}
		this.scanner = new ClassPathBeanDefinitionScanner(registry);
		this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
	}
	// ...省略部分代码
}

通过观察它的内部属性和构造方法可以看出,它支持加载基于编程方式配置的类,支持xml文件配置的类,支持groovy和xml混合定义的类的加载。它使用门面模式封装了这些具体的加载器。此处只需要先知道BeanDefinitionLoader用于加载Bean,且它内部封装了多个类读取器即可
不必深入。先了解大体的功能性即可。后续会开单独篇章介绍。

使用BeanDefinitionLoader加载Bean

// BeanDefinitionLoader.java

// ①
public int load() {
	int count = 0;
	for (Object source : this.sources) {
		count += load(source);
	}
	return count;
}

// ②
private int load(Object source) {
	Assert.notNull(source, "Source must not be null");
	if (source instanceof Class<?>) {
		return load((Class<?>) source);
	}
	if (source instanceof Resource) {
		return load((Resource) source);
	}
	if (source instanceof Package) {
		return load((Package) source);
	}
	if (source instanceof CharSequence) {
		return load((CharSequence) source);
	}
	throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

① - 记录加载的资源数量
② - 根据传入的资源类型选择不同的加载方式

此处我们选择SpringBoot典型的资源加载方式Class方式来分析,在应用程序启动入口我们使用的是SpringApplication.run(Example.class, args);而Example.class是我们的main函数所在类,以它作为资源来加载。

// BeanDefinitionLoader.java

private int load(Class<?> source) {
	// ①
	if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
		// Any GroovyLoaders added in beans{} DSL can contribute beans here
		GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
		load(loader);
	}
	// ②
	if (isComponent(source)) {
		this.annotatedReader.register(source);
		return 1;
	}
	return 0;
}

① - 判断是否支持Groovy
② - 判断是否包含@Component注解,如果包含才开始注册

// BeanDefinitionLoader.java

private boolean isComponent(Class<?> type) {

	if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
		return true;
	}
	if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass() || type.getConstructors() == null || type.getConstructors().length == 0) {
		return false;
	}
	return true;
}

// AnnotationUtils.java

public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
		return findAnnotation(clazz, annotationType, true);
}


@SuppressWarnings("unchecked")
@Nullable
private static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType, boolean synthesize) {

	Assert.notNull(clazz, "Class must not be null");
	if (annotationType == null) {
		return null;
	}

	// ①
	AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
	A result = (A) findAnnotationCache.get(cacheKey);
	if (result == null) {
		result = findAnnotation(clazz, annotationType, new HashSet<>());
		if (result != null && synthesize) {
			result = synthesizeAnnotation(result, clazz);
			findAnnotationCache.put(cacheKey, result);
		}
	}
	return result;
}

① - 从缓存获取被加载的资源类是否有@Component注解的缓存

默认情况下是没有缓存,此时将会去查找Example.class是否包含@Component注解。那在默认情况下我们的启动类并未直接标明这个注解, 一个典型的SpringBoot Web应用如下:


@RestController
@SpringBootApplication
public class Example {

	@RequestMapping("/")
	String home() {
		return "Hello World!";
	}

	public static void main(String[] args) {
		SpringApplication.run(Example.class, args);

	}

}

实际上@RestController和@SpringBootApplication两个注解都包含了@Component注解。因此Example.class自然也就具有@Component注解,感兴趣的可以亲自点进去看看这两个注解内部。更多细节将来会单独分析。在确定了它支持@Component注解之后,开始加载Bean。

// BeanDefinitionLoader#load(Class<?> source)

this.annotatedReader.register(source);

// AnnotatedBeanDefinitionReader.java

<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

	// ①
	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
	// ②
	if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
		return;
	}

	// ③
	abd.setInstanceSupplier(instanceSupplier);
	// ④
	ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
	abd.setScope(scopeMetadata.getScopeName());
	String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
	// ⑤
	AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
	// ⑥
	if (qualifiers != null) {
		for (Class<? extends Annotation> qualifier : qualifiers) {
			if (Primary.class == qualifier) {
				abd.setPrimary(true);
			}
			else if (Lazy.class == qualifier) {
				abd.setLazyInit(true);
			}
			else {
				abd.addQualifier(new AutowireCandidateQualifier(qualifier));
			}
		}
	}
	// ⑦
	for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
		customizer.customize(abd);
	}

	// ⑧
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

① - 创建一个带注解元数据的类定义。
② - 判断是否有@Conditional注解,以确定是否满足条件需要排除。
③ - 设置一个创建Bean的回调,基本都是空值。
④ - 获取当前Bean的作用域元数据,判断依据是是否包含@Scope注解。ScopeMetadata包含类的作用域描述(singleton | prototype)和代理模式描述ScopedProxyMode,默认为单例模式,不使用代理创建。
⑤ - 处理常用注解,包括@Lazy、@Primary、@Role、@Description、@DependsOn,他们将被设置到BeanDefinition的属性当中。
⑥ - 检查传入的Qualifiers。
⑦ - 暂时不知道干啥的,以后再说
⑧ - 确认好当前Bean的代理模式,并注册。

在真正将一个类以BeanDefinition来注册的时候需要把可能涉及到的一些特性全部都检查一遍。此处只加载了我们的示例类Example.class,并未加载其他类。

发布ApplicationPreparedEvent事件

在真正触发事件之前,它处理了ApplicationContextAware实现,为其设置了上下文。

为ApplicationContextAware扩展接口设置上下文

public void contextLoaded(ConfigurableApplicationContext context) {
	for (ApplicationListener<?> listener : this.application.getListeners()) {
		if (listener instanceof ApplicationContextAware) {
			((ApplicationContextAware) listener).setApplicationContext(context);
		}
		// 将所有监听器赋值给上下文
		context.addApplicationListener(listener);
	}
	this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

ConfigFileApplicationListener

添加了一个BeanFactoryPostProcessor:PropertySourceOrderingPostProcessor,用于重排序defaultProperties

LoggingApplicationListener

以单例模式注册注册当前日志系统

总结

在预处理上下文时将先前加载的一些属性赋值给context,执行了ApplicationContextInitializers的实现类,为ApplicationContextAware接口填充context对象。

03-27 06:01