SpringBoot 是如何实现启动的?

  |   0 评论   |   0 浏览

SpirngBoot

一个读者,也是我的好朋友投稿的一篇关于 SpringBoot 启动原理的文章,才大二就如此优秀,未来可期。


我一直想了解一下 SpirngBoot 的是如何启动的,我想就来写一篇关于 SpirngBoot 启动分析吧。第一次写那么高深的技术话题理解不到位的话也请多多包涵。

源码版本

SpinrgBoot 2.0.2

众所周知 SpringBoot 的启动类是在一个 main 方法中调用 SpringApplication.run() 方法启动的,如:

@SpringBootApplication
public class DiveInSpringBootApplication {

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

}

启动顺序分析如下:

初始化阶段 -> 运行阶段

初始化阶段:

进入run方法中,SpringApplication.run() 会先为其创建一个 SpringApplication 对象:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //加载应用资源(URL资源、File资源、ClassPath资源)
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // primarySources 为 run 方法传入的引导类
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //推断Web应用类型
        this.webApplicationType = deduceWebApplicationType();
        //加载应用上下文(初始化Initializers)
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //加载应用事件监听器(初始化ApplicationListener)
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //推断引导类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

Step1. 通过 deduceWebApplicationType() 来推断我们Web类型应用

private WebApplicationType deduceWebApplicationType() {

    //根据当前应用的ClassPath中是否存在相关实现类来推断Web类型
	if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
		&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {

		return WebApplicationType.REACTIVE;
	}

	for (String className : WEB_ENVIRONMENT_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}

	return WebApplicationType.SERVLET;
}

看看使用到的 3 个常量值:

常量值应用类型
REACTIVE_WEB_ENVIRONMENT_CLASSorg.springframework.web.reactive.DispatcherHandler
MVC_WEB_ENVIRONMENT_CLASSorg.springframework.web.servlet.DispatcherServlet
WEB_ENVIRONMENT_CLASSES{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }

也就是说,有如下三种情况:

  1. 如果应用程序中存在 org.springframework.web.reactive.DispatcherHandler 这个类,则表示是一个响应式 web 应用,项目在启动时,需要去加载启动内嵌的响应式 web 服务器。
  2. 如果应用程序中既不存在 javax.servlet.Servlet 类,也不存在org.springframework.web.context.ConfigurableWebApplicationContext 这个类,则
    表示当前应用不是一个web应用,启动时无需加载启动内嵌的 web 服务器。
  3. 除上述两种情况外,则表示当前应用是一个 servlet 的 web 应用,启动时需要加载启动内嵌的 servlet 的 web 服务器(比如 Tomcat )。

Step2. 如何加载应用上下文初始器(初始化 Initializers )

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
	Class<?>[] parameterTypes, Object... args) {

	ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 

	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(
		SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 

	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
		classLoader, args, names);

	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

利用Spirng的工厂加载机制,实例化ApplicationContextInitializer实现类,并排序集合。具体实现方法如下:

  1. 通过SpringFactoriesLoader.loadFactoryNames 来扫描META-INF/spring.factories 下符合 ApplicationContextInitializer 类型的资源名称。
  2. 实例化所有在META-INF/spring.factories 下找到的资源信息
  3. 对实例化的资源信息进行优先级顺序排序,或通过@order注解和Ordered接口进行排序
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#Spring容器的常见的错误配置警告
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#设置Spring应用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

Step3. 加载应用事件监听器( ApplicationListener )

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,

	Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(
		SpringFactoriesLoader.loadFactoryNames(type, classLoader));

	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
		classLoader, args, names);

	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

利用 Spring 工厂加载机制,实例化 ApplicationListene r实现类,并排序对象集合,具体方法跟上面 初始化Initializers 类似,不赘述。

# Application Listeners
org.springframework.context.ApplicationListener=\
#Spring应用上下文加载完成之后清除缓存
org.springframework.boot.ClearCachesApplicationListener,\
#父容器关闭时通知各个子容器关闭,
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
#文件编码
org.springframework.boot.context.FileEncodingApplicationListener,\
#控制台彩色输出
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
#外部化配置 管理factories或者YMAL文件
org.springframework.boot.context.config.ConfigFileApplicationListener,\
#将指定事件广播给指定的监听器
org.springframework.boot.context.config.DelegatingApplicationListener,\
#将需要输出的日志打印到指定的级别 DEBUG INFO ERROR
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
#初始化日志系统
org.springframework.boot.context.logging.LoggingApplicationListener,\
#控制可执行Spirng文件版本
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

Step4. 推断引导类

private Class<?> deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
                //根据 Main 线程执行堆栈来判断实际的引导类。
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
			// Swallow and continue
	}
	return null;
}

运行阶段

整个 SpringApplication 围绕着 run 这个方法并分为两个小阶段:

  • 加载SpringApplication 运行监听器,并监听Spring Boot 事件
  • 创建 Spring 应用上下文和创建 Environment
public ConfigurableApplicationContext run(String... args) {
    //记录运行时间
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
        //Spring 应用的上下文
	ConfigurableApplicationContext context = null;
        //记录启动期间的错误
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //配置文件加载及优先级判断
	configureHeadlessProperty();
        //获取SpringApplicationRunListeners
	SpringApplicationRunListeners listeners = getRunListeners(args);
        //加载运行监听器
	listeners.starting();
	try {
            //创建ApplicationArguments对象
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
			args);
            //加载属性配置
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
			applicationArguments);
		configureIgnoreBeanInfo(environment);
            //打印Banner
		Banner printedBanner = printBanner(environment);
            //创建应用上下文
		context = createApplicationContext();
            //实例化SpringBootExceptionReporter用于报告启动过程错误。
		exceptionReporters = getSpringFactoriesInstances(
			SpringBootExceptionReporter.class,
			new Class[] { ConfigurableApplicationContext.class }, context);
            //初始化应用上下文
		prepareContext(context, environment, listeners, applicationArguments,
			printedBanner);
            //刷新应用上下文(IOC容器的准备,初始化Bean)
		refreshContext(context);
            //应用上下刷新完成之后
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
            //启动日志记录器
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
			.logStarted(getApplicationLog(), stopWatch);
		}
            //启动运行监听器
		listeners.started(context);
            //启动后需要的操作
		callRunners(context, applicationArguments);
		....
	}
}

Step1. 加载 SpringApplication运行监听器

private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
		SpringApplicationRunListener.class, types, this, args));
}

利用 Spirng 的工厂加载机制,实例化 SpringApplicationRunListeners 实现类,并排序集合。具体实现方法如下:

  1. 通过 SpringFactoriesLoader.loadFactoryNames 来扫描 META-INF/spring.factories 下符合 SpringApplicationRunListeners 类型的资源名称。
  2. 实例化所有在 META-INF/spring.factories 下找到的资源信息
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListenr

由此可见就只有 EventPublishingRunListenr 一个实现类

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
        //实例化SimpleApplicationEventMulticaster事件发布者
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
        //以迭代的方法逐一进行ApplicationListener的监听
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(
				new ApplicationStartingEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {

	}

	@Override
	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));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(
				new ApplicationStartedEvent(this.application, this.args, context));
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
		context.publishEvent(
				new ApplicationReadyEvent(this.application, this.args, context));
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,
				this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

	private static class LoggingErrorHandler implements ErrorHandler {

		private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);

		@Override
		public void handleError(Throwable throwable) {
			logger.warn("Error calling ApplicationEventListener", throwable);
		}

	}

}

EventPublishingRunListener 实例化的时候,会实例化一个 SimpleApplicationEventMulticaster 事件发布者(它的作用就是监听容器中发布的事件,只要事件发生,就触发监听器的回调,来完成事件驱动开发),于是接下来调用 listeners.starting() 方法就会通过其内部的 initialMulticaster 属性发布 ApplicationStartingEvent 事件。

Step2. 创建Spirng应用上下文

	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
                //初始化阶段的推断Web类型
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableApplicationContext 实例

如果推断的为 SERVLETWeb 类型就实例化这个对象

web.servlet.context.AnnotationConfigServletWebServerApplicationContext

Step3. 创建 Environment

	private ConfigurableEnvironment prepareEnvironment(
	SpringApplicationRunListeners listeners,
	ApplicationArguments applicationArguments) {
		// Create and configure the environment
        //创建 ConfigurableEnvironment 对象
	ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置 ConfigurableEnvironment
	configureEnvironment(environment, applicationArguments.getSourceArgs());
        //发布 ApplicationEnvironmentPreparedEvent 事件
	listeners.environmentPrepared(environment);
        //将 ConfigurableEnvironment 绑定到 SpringApplication 中
	bindToSpringApplication(environment);
	if (this.webApplicationType == WebApplicationType.NONE) {
		environment = new EnvironmentConverter(getClassLoader())
		.convertToStandardEnvironmentIfNecessary(environment);
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableEnvironment 实例。

至此整一个的 SpringBoot 过程已经分析完毕:我们来总结一下:

  1. 首先初始化 SpringApplication 类,并推断 WEB 启动类型,再初始化和实现应用事件监听器,然后推断引导类。
  2. 通过 SpringFactoriesLoader 加载的 SpringApplicationRunListener,调用它们的 started 方法。
  3. 根据 Web 服务类型创建不同的 Spring 应用上下文,并将之前准备好的 Environment 设置给 Spring 应用上下文 ApplicationContext 使用。
  4. 创建并配置当前 Spring Boot 应用将要使用的 Environment,如 applocation.properties 文件和外部配置。
  5. SpirngBoot 开始启动。

推荐阅读

java | 什么是动态代理?

SpringBoot | 是如何实现日志的?

SpringBoot | 是如何实现自动配置的?

后语

如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。
另外,关注之后在发送 1024 可领取免费学习资料。

资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享

一个优秀的废人


标题:SpringBoot 是如何实现启动的?
作者:JavaFish
地址:https://www.javafish.top/articles/2021/07/16/1626435264154.html