SpringApplication启动

176 阅读2分钟

一、SpringApplication的启动

启动过程主要包含以下几件事

  1. spring容器的初始化
  2. 属性的自动装配
  3. spring内置web容器的启动

1、Spring启动类

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
	    // 调用run方法进行启动springboot
		SpringApplication.run(Application.class, args);
	}

}

2、SpringApplication.run

// 以默认的配置去运行SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 调用SpringApplication启动
	return new SpringApplication(primarySources).run(args);
}

2.1 SpringApplication初始化

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// 这里返回的类型是SERVLET,标识需要运行一个内置容器;根据这个类型创建不同的applicationContext
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 获取当前运行主类,这里返回的就是Application
	this.mainApplicationClass = deduceMainApplicationClass();
}

2.2 run

  1. 通过StopWatch来记录Springboot的启动时间,用于后面的日志输出
  2. 开启对启动过程的监听,可以继承监听接口来进行扩展
  3. 打印banner,初始化Environment
  4. 初始化Context,这里逻辑最复杂,也是最重要的部分,下面具体分析这块内容
public ConfigurableApplicationContext run(String... args) {
    // 1.用于记录阶段时间
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	
	// 2.用来记录异常
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	
	configureHeadlessProperty();
	
	// 3. 开启对启动过程的监听,这个可以作为扩展,spring只有一个实现类EventPublishingRunListener
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	
	try {
	    // 4. 初始化Environment
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		
		// 5.打印banner
		Banner printedBanner = printBanner(environment);
		
		// 6.创建context,根据类型创建的是AnnotationConfigServletWebServerApplicationContext
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
				
		// 7.初始化context
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		
		// 8.打印日志
		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;
}

3、SpringBoot的context初始化

  1. spring容器的初始化,这里就不展开细说
  2. 内置容器的初始化、启动

3.1 内置容器的初始化

  1. 这里以tomcat为例来进行解析,内置容器还包括jetty、undertow
  2. 他是对ApplicationContext的onRefresh方法进行扩展来进行内置容器的启动的

3.1.1 ServletWebServerApplicationContext.onRefresh

@Override
protected void onRefresh() {
    // 调用父类的方法
	super.onRefresh();
	try {
	    // 启动内置web容器
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

3.1.2 createWebServer

private void createWebServer() {
	WebServer webServer = this.webServer;
	// 没有配置web.xml,这个servletContext对象为空
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

3.1.3 getWebServerFactory

  1. 获取当前spring容器下有多少个ServletWebServerFactory的实现类(tomcat、jetty、undertow)
  2. 当实现类不为1时无法启动内置容器,也就是说spring的容器中只能有一个容器类型
protected ServletWebServerFactory getWebServerFactory() {
	// Use bean names so that we don't consider the hierarchy
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
				+ "ServletWebServerFactory bean.");
	}
	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
				+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}
	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

3.1.4 ServletWebServerFactory.getWebServer

  1. 通过api的方式初始化Tomcat
  2. 启动tomcat
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
	if (this.disableMBeanRegistry) {
		Registry.disableRegistry();
	}
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	connector.setThrowOnFailure(true);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}