探究spring-boot的启动--加载application.properties和启动Web容器

Spring-Boot启动流程

  • 探究spring-boot的启动--加载application.properties和启动Web容器
    • new SpringApplication(ApplicationConfig.class)
    • run()方法中所作的事
    • 加载application.properties
    • 创建Web Server

探究spring-boot的启动–加载application.properties和启动Web容器

spring-boot通过下面一段简单的代码就能启动运行起来,那么它是如何做的呢,本篇文章就是来梳理new SpringApplication(ApplicationConfig.class).run(args);的执行过程。主要探索的主题有两个:

  1. spring-boot是什么时候怎么加载application.properties。
  2. spring-boot是何时启动web容器的。
    本篇文章基于spring-boot版本是2.0.8.RELEASE。
@SpringBootApplication
public class Chapter03Application {
     
    public static void main(String[] args) {
     
        new SpringApplication(ApplicationConfig.class).run(args);
    }
}

new SpringApplication(ApplicationConfig.class)

首先是new SpringApplication(ApplicationConfig.class)做了什么

public SpringApplication(Class<?>... primarySources) {
     
		// 调用另一个构造器
		this(null, primarySources);
}

new SpringApplication(ApplicationConfig.class)是做了如下操作

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
     
		this.resourceLoader = resourceLoader; // null
		// 主配置类,即通常是@SpringBootApplication注解的类,本例中指的是Chapter03Application,
		// 也可以不是只能标注在main方法类上,也可以其他标注了@SpringBootApplication的配置类,通常不这么做
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 这里推断使用哪种应用服务器类型,两种SERVLET和REACTIVE,WebApplicationType枚举类中定义,稍后看deduceFromClasspath()方法里做了什么。
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 这里是从spring.factories文件中读取ApplicationContextInitializer类型的类,并实例化。从名称上来看他们时上下文初始化器,用来控制上下文初始化的
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		// 这里是从spring.factories文件中读取ApplicationListener类型的类,并实例化
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 通过运行时的栈信息获取main方法入口所在的类
		this.mainApplicationClass = deduceMainApplicationClass();
	}
  1. WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = {
      "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
			+ "web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org."
			+ "springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

	static WebApplicationType deduceFromClasspath() {
     
	// 这里会根据classpath下是否具有相应的类型来确定应用启动的方式,
	// 当引入webflux的依赖,并排除webmvc依赖时将采用REACTIVE的方式启动应用,
	// 否则的话将采用SERVLET的方式,这是一般常用的方式,即使用内嵌的tomcat启动应用
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
     
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
     
			if (!ClassUtils.isPresent(className, null)) {
     
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}
  1. getSpringFactoriesInstances(ApplicationContextInitializer.class)
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
     
		return getSpringFactoriesInstances(type, new Class<?>[] {
     });
	}

	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<>(
				// 加载出spring.factories中的org.springframework.context.ApplicationContextInitializer的值
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		// 实例化这些类型,将会在prepareContext()中准备应用上下文时使用这些初始化器,
		// 稍后会在run方法中看到
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
  1. getSpringFactoriesInstances(ApplicationListener.class)加载监听器,同2
  2. deduceMainApplicationClass()
private Class<?> deduceMainApplicationClass() {
     
		try {
     
			// 从运行方法栈中推断出main方法类,也即是在debugger过程中看到的调用栈
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
     
				if ("main".equals(stackTraceElement.getMethodName())) {
     
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
     
			// Swallow and continue
		}
		return null;
	}

即下面这个调用栈信息
探究spring-boot的启动--加载application.properties和启动Web容器_第1张图片
以上在new SpringApplication()主要做了这么几件事儿,1. 设置主配置,2. 推断出应用引入了那种web依赖,时webmvc还是webflux,判断采用那种web server类型,3. 读取spring.factories文件,加载配置的ApplicationContextInitializer和ApplicationListener.

run()方法中所作的事

run(args)

public ConfigurableApplicationContext run(String... args) {
     
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// 定义应用上下文和异常报告
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 设置java.awt.headless,Headless模式是系统的一种配置模式。在系统可能缺少显示
		// 设备、键盘或鼠标这些外设的情况下可以使用该模式。
		configureHeadlessProperty();
		// 从spring.factories中加载SpringApplicationRunListener,用于在应用启动过程中发布启动各阶段的事件
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 发布开始事件
		listeners.starting();
		try {
     
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// 创建运行环境,这里将会发布org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent事件,
			// 然后org.springframework.boot.context.config.ConfigFileApplicationListener监听到该事件
			// 后会加载application.properties,稍后会分析这部分代码
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			// 创建应用上下文
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] {
      ConfigurableApplicationContext.class }, context);
			// 准备应用上下文,对于web应用,这里的应用上下文是一个ReactiveWebServerApplicationContext(RREACTIVE)
			// 或是AnnotationConfigServletWebServerApplicationContext(SERVLET)
			// 他们都实现了onRefresh()方法,在该方法中将创建Web Server
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 刷新应用上下文,即启动Spring IOC, 将会调用onRefresh()
			refreshContext(context);
			// 空方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
     
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			// 将会执行实现了ApplicationRunner或CommandLineRunner接口的对象的run方法
			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;
	}

加载application.properties

加载application.properties是在ConfigFileApplicationListener监听到ApplicationEnvironmentPreparedEvent事件后做的

@Override
public void onApplicationEvent(ApplicationEvent event) {
     
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
     
		// 处理事件
		onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
     
		onApplicationPreparedEvent(event);
	}
}

private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
     
	// 从spring.factories中加载org.springframework.boot.env.EnvironmentPostProcessor值对应的类型
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	// ConfigFileApplicationListener本身也是一个EnvironmentPostProcessor
	postProcessors.add(this);
	// 根据Order排序
	AnnotationAwareOrderComparator.sort(postProcessors);
	// 执行EnvironmentPostProcessor 
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
     
		postProcessor.postProcessEnvironment(event.getEnvironment(),
				event.getSpringApplication());
	}
}
// ConfigFileApplicationListener的postProcessEnvironment()
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
		SpringApplication application) {
     
	// 添加属性配置到environment
	addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment,
		ResourceLoader resourceLoader) {
     
	RandomValuePropertySource.addToEnvironment(environment);
	// 加载外部化属性配置application.properties
	new Loader(environment, resourceLoader).load();
}

private void load(Profile profile, DocumentFilterFactory filterFactory,
		DocumentConsumer consumer) {
     
	// 加载application.properties的位置
	getSearchLocations()
	.forEach((location) -> {
     
		boolean isFolder = location.endsWith("/");
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach(
				(name) -> load(location, name, profile, filterFactory, consumer));
	});
}
private Set<String> getSearchLocations() {
     
// CONFIG_LOCATION_PROPERTY = "spring.config.location" 如果通过命令行指定该参数,那么将使用该参数指定的配置,否则将到默认位置读取
	if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
     
		return getSearchLocations(CONFIG_LOCATION_PROPERTY);
	}
	Set<String> locations = getSearchLocations(
			CONFIG_ADDITIONAL_LOCATION_PROPERTY);
	// DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/",默认位置
	locations.addAll(
			asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
					DEFAULT_SEARCH_LOCATIONS));
	return locations;
}

从上面来看,配置文件的优先级顺序是:

  1. spring.config.location 指定的配置
  2. file:./config/ 即指项目目录下的config目录(未打包,开发时启动),或是jar包同级目录下的config目录(打过包,jar包启动)
  3. file:./ 即指项目目录,或是jar包同级目录
  4. classpath:/config/ 指classpath目录下的config目录,即maven项目中的resource目录下的config目录,或是jar包中的classes目录下的config目录
  5. classpath:/ 指classpath目录,即maven项目中的resource目录,或是jar包中的classes目录
    以上优先级1>2>3>4>5,优先级高的将覆盖优先级低的相同属性

创建Web Server

创建Web Server是在启动spring ioc过程中调用onRefresh()来创建

// AbstractApplicationContext#refresh()
@Override
public void refresh() throws BeansException, IllegalStateException {
     
	synchronized (this.startupShutdownMonitor) {
     
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
     
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// 这里ReactiveWebServerApplicationContext(RREACTIVE)
			// 和AnnotationConfigServletWebServerApplicationContext(SERVLET)两个子
			// 类均重写了onRefresh()方法. 对于AnnotationConfigServletWebServerApplicationContext,
			//是它的父类ServletWebServerApplicationContext重写的onRefresh()
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// ServletWebServerApplicationContext与ReactiveWebServerApplicationContext
			// 复写了该方法,并且在复写方法中启动Web Server
			finishRefresh();
		}

		catch (BeansException ex) {
     
			if (logger.isWarnEnabled()) {
     
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
     
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

// ServletWebServerApplicationContext#onRefresh()
@Override
protected void onRefresh() {
     
	super.onRefresh();
	try {
     
		// 创建web server
		createWebServer();
	}
	catch (Throwable ex) {
     
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

// 创建 web server 
private void createWebServer() {
     
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
     
		ServletWebServerFactory factory = getWebServerFactory();
		// getSelfInitializer() 创建并初始化webServer, 之后将在 
		this.webServer = factory.getWebServer(getSelfInitializer());
	}
	else if (servletContext != null) {
     
		try {
     
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
     
			throw new ApplicationContextException("Cannot initialize servlet context",
					ex);
		}
	}
	initPropertySources();
}

// ServletWebServerApplicationContext#finishRefresh()
@Override
protected void finishRefresh() {
     
	// 调用父类finishRefresh()
	super.finishRefresh();
	// 启动web serevr
	WebServer webServer = startWebServer();
	if (webServer != null) {
     
		// 发布事件
		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
	}
}

private WebServer startWebServer() {
     
	WebServer webServer = this.webServer;
	if (webServer != null) {
     
		// 启动web server  对于本示例中启动的是tomcat,
		// @see org.springframework.boot.web.embedded.tomcat.TomcatWebServer#start()
		webServer.start();
	}
	return webServer;
}

以上内容通过源码对spring-boot启动流程做了大致的梳理,其中仍有很多细节需要探索

你可能感兴趣的:(spring-boot,spring,boot,java)