Springboot 启动过程六

用于源码分析的代码:Github
接着上一篇继续debug。上一篇已经将源码4.3的逻辑分析完了,这一篇从源码4.4处继续:

public ConfigurableApplicationContext run(String... args) {
    //StopWatch就是一个监控程序启动时间的类,start方法表示开始计时,stop方法表示计时结束
    //用于日志输出启动时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 设置一个环境变量,该环节变量标识当前程序是在没有显示器、鼠标等显示设备上运行的
		// 目的是为了能直接访问支持在无显示设备下的图形和文字处理对象的API,比如AWT的绘图API
		configureHeadlessProperty();
		// 1.0
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 2.0
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 3.0
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			// 4.0
			configureIgnoreBeanInfo(environment);
			// 4.1
			Banner printedBanner = printBanner(environment);
			// 4.2
			context = createApplicationContext();
			// 4.3
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 4.4
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			// 4.5
			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;
	}  

还是首先列出自己的问题,带着问题看源码

待解答的问题

  • 这段源码做了什么?
  • 为什么这么做?
  • 学到了哪些东西?

项目启动命令

使用远程debug的方式来启动,先将项目使用mvn package 命令打成jar包,然后cd到jar包所在目录,使用命令方式启动项目,我这里启动命令是:java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 -Dlogging.level.root=info -jar spring-boot-learn-1.0-SNAPSHOT.jar --spring.profiles.active=profile --spring.main.lazy-initialization=false ,5005就是远程debug时的端口,然后通过idea来做远程debug,在启动过程一中有讲到远程debug的配置。

源码分析

源码4.4

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

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		// context是整个Spring启动过程中最顶层的那个上下文对象,所以把environment也放到这个顶层对象里
		context.setEnvironment(environment);
		// 向context中注册一些基础功能类,详细看下面的 源码4.4.1 解析
		postProcessApplicationContext(context);
		// 执行所有的ApplicationContextInitializer 详情看下面的 源码4.4.2 解析
		applyInitializers(context);
		// 执行所有的执行所有的ApplicationContextInitializer 详情看下面的 源码4.4.3 解析
		listeners.contextPrepared(context);
		// 打印一些启动相关的日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		// 将命令行启动参数封装成对象添加到beanFactory里,比如我的启动参数是java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 -Dlogging.level.root=info -jar spring-boot-learn-1.0-SNAPSHOT.jar --spring.profiles.active=profile --spring.main.lazy-initialization=false  那么这里的命令行参数就是紧跟在jar后的--spring.profiles.active=profile --spring.main.lazy-initialization=false
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
		// 将输出banner图的功能类加入到beanFactory里
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
		// 是否允许已经加载的beanDefinition在遇到相同名字时候被顶替,默认是false
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// 是否需要懒加载,默认为false
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// 获取Spring启动类的入口类,这里就是Application.class
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		// 将Application.class包装成beanDefinition,注册到beanFactory里
		load(context, sources.toArray(new Object[0]));
		// 发布context的loaded事件,通知该事件的监听者执行相应程序
		listeners.contextLoaded(context);
	}
4.4.1源码

向context中注册一些基础功能类,其中包括负责类型转换的功能类ApplicationConversionService

/**
	 * 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());
		}
	}
4.4.2源码

执行所有的ApplicationContextInitializer,ApplicationContextInitializer类就是在context执行refresh方法前需要执行的逻辑,而refresh方法就是真正解析bean的核心方法,这里的initializers是怎么加载进来的呢?在《启动过程二》中已经讲到。这个是Spring留给我们的一个扩展点,可以定义自己的ApplicationContextInitializer,在我们自己的ApplicationContextInitializer里添加一些逻辑,比如注册一些后置处理器,事件监听器等逻辑,比如ConfigurationWarningsApplicationContextInitializer类,在initialize方法里就是注册一个后置处理器。


applyInitializers(context);

@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方法
			initializer.initialize(context);
		}
	}
	

/**
 * 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);
}

private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
		List<E> list = new ArrayList<>(elements);
		list.sort(AnnotationAwareOrderComparator.INSTANCE);
		return new LinkedHashSet<>(list);
	}
4.4.3源码

这个是发布context的prepared事件,让监听了这个事件的Listener执行相应的逻辑,SpringApplicationRunListeners类会负责发布以下事件:
在这里插入图片描述

在启动过程三中发布过starting事件,在那个源码分析中,讲了怎么通过事件类型来匹配Listener,这里就不重复了,有兴趣的到《启动过程三》看下。

listeners.contextPrepared(context);  

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));
	}
4.4.4源码
load(context, sources.toArray(new Object[0]));

先看debug图,这里的sources里就是我们springboot项目的入口类:
在这里插入图片描述

中间的debug步骤我就跳过,debug到load方法的截图如下:

在这里插入图片描述

springboot入口类会被@SpringBootApplication注解标注,而这个注解会被@SpringBootConfiguration标注,@SpringBootConfiguration注解被@Configuration标注,@Configuration有被@Component标注,所以这里的isComponent返回true,最终会将这个类解析成BeanDifinition注册到beanFactory里
在这里插入图片描述

总结及思考

一句话总结下,这个方法是为后面的最核心的refreshContext()方法做准备,下一篇开始研究refreshContext()!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值