SpringBootApplication是如何启动Tomcat的? | 破解SpringBoot Tomcat启动之谜 !

前言

我们都知道,SpringBoot内置了容器Tomcat,可以直接启动WebServletServer,那么SpringBoot是如何启动Tomcat的?
本文从Main方法入手,从SpringApplication.run跟到ServletWebServerApplicationContext 再到TomcatServletWebServerFactory,破解SpringBoot Tomcat启动之谜 !!!

  • springboot版本 : 2.0.5.RELEASE
  • 追踪工具 : IDEA

Main方法

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

Run方法

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 * 解释 by zhengkai.blog.csdn.net
	 */
	public ConfigurableApplicationContext run(String... args) {
	    //zhengkai.blog.csdn.net
		//定义个StopWatch,Wathch是表,定义个表来监控一下运行的性能,用了多少ms
		StopWatch stopWatch = new StopWatch();
		//开始计时
		stopWatch.start();
		//定义一个可配置的上下文,ConfigurableApplicationContext接口extends了ApplicationContext+Lifecycle+Closeable,可以被大多数的应用上下文实现,为配置应用上下文提供便利.
		ConfigurableApplicationContext context = null;
		//ExceptionReporter明显是一个错误报告,for SpringApplication的starup error,基本只是启动的报错(不包括启动后的报错)
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		//RunListeners,运行监听器SpringApplicationRunListeners=List<SpringApplicationRunListener>,是个集合来的
		//包括starting()environmentPrepared()contextPrepared()contextLoaded()started()running()failed()事件的监听
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//进入监听器的staring阶段
		listeners.starting();
		try {
		    //获取SpringBootApplication的运行参数,如果你是java -jar xxx.jar --server.context-path=/mypath,则可以动态获取
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			//开始准备环境,传入监听器和运行参数,获得环境变量,可以getSystemEnvironment和getSystemProperties,可以setActiveProfiles设置激活的配置
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
		    //从spring.beaninfo.ignore获取需要忽然的bean列表
			configureIgnoreBeanInfo(environment);
			//打印banner,控制台输出,Mode有三种:OFF/console/log file
			Banner printedBanner = printBanner(environment);
			//***开始创建上下文,这个比较重点,下面列出来单独说
			//根据webApplicationType,获取对应的CONTEXT_CLASS,分SERVLET(Web)/REACTIVE(webflux)/default(spring).开始创建上下文
			context = createApplicationContext();
			//里面实际上创建了SpringFactoriesInstances
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//这里才是真正创建上下文的地方:
			//context.setEnvironment设置环境变量.setResourceLoader设置资源加载器,用context来初始化所有initializer
			//开始logStartupInfo输出日志,registerArguments注册参数,registerBanner注册控制台输出
			//createBeanDefinitionLoader创建bean定义加载器,最后load到所有监听器listener上
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//这里为context注册一个ShutdownHooK,也就是关掉应用的时候要做什么
			refreshContext(context);
			//这是一个空方法,可供改造???
			afterRefresh(context, applicationArguments);
			//好了,计时停止,看下启动用了多少ms
			stopWatch.stop();
			if (this.logStartupInfo) {
				//输出SpringBoot那些启动信息,日志,用了多少ms等,你平常看到的那堆
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			//进入监听器的启动完成事件
			listeners.started(context);
			//分ApplicationRunner和CommandLineRunner,callback run
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
		    //万一启动过程中有报错,就handleExitCode然后reportFailure,然后重新抛出RuntimeException
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
		    //进入监听器的running运行中阶段
			listeners.running(context);
		}
		catch (Throwable ex) {
		    //万一有报错怎么办,跟上面一样......
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		//搞定,,,返回上下文!
		return context;
	}

抓住createApplicationContext

大致过了一遍之后,我觉得我们应该关注这个方法,createApplicationContext()

	/**
	 * Strategy method used to create the {@link ApplicationContext}. By default this
	 * method will respect any explicitly set application context or application context
	 * class before falling back to a suitable default.
	 * @return the application context (not yet refreshed)
	 * @see #setApplicationContextClass(Class)
	 */
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					//DEFAULT_WEB_CONTEXT_CLASS=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					//org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					//org.springframework.context.annotation.AnnotationConfigApplicationContext
					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);
	}

CONTEXT_CLASS分析

DEFAULT_WEB_CONTEXT_CLASS=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

DEFAULT_WEB_CONTEXT_CLASS一路跟踪到extends的类,直到发现新大陆:

//没什么可以看
public class AnnotationConfigServletWebServerApplicationContext
		extends ServletWebServerApplicationContext implements AnnotationConfigRegistry{
}		
//重点: 整理by zhengkai.blog.csdn.net
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext{
		//放眼一望,这个里面也相当丰富,很多内容
		//发现WebServer
		private volatile WebServer webServer;
		//发现ServletConfig
		private ServletConfig servletConfig;
		//发现创建WebServer的方法
		private void createWebServer() {
			WebServer webServer = this.webServer;
			//Servlet上下文
			ServletContext servletContext = getServletContext();
			//有server和上下文,则不需要创建,直接从ServletWebServer工厂里面拿
			if (webServer == null && servletContext == null) {
				//ServletWebServerFactory工厂,应该有很多东西,提到下面来分析
				ServletWebServerFactory factory = getWebServerFactory();
				this.webServer = factory.getWebServer(getSelfInitializer());
			}
			else if (servletContext != null) {
				try {
				   //没有话,从ServletContextInitializer开始一个吧
					getSelfInitializer().onStartup(servletContext);
				}
				catch (ServletException ex) {
					throw new ApplicationContextException("Cannot initialize servlet context",
							ex);
				}
		}
		initPropertySources();
	}
}	

//没啥子
public class GenericWebApplicationContext extends GenericApplicationContext
		implements ConfigurableWebApplicationContext, ThemeSource {
}	

//没啥子
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
}

//AbstractApplicationContext 这个类也还是挺大的,虽然是抽象类,但是很多方法,超级丰富,值得关注
public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {   
}

ServletWebServerFactory工厂

IDEA下ctrl+alt+B,直接查看ServletWebServerFactory工厂的实现,发现:
在这里插入图片描述
Tomcat启动之谜

看到上面几个工厂类的时候,这个谜题已经破解了。同时Jetty,Undertow的启动之谜也揭晓了。

	/**
	 * 创建并获取TomcatWebServer,TomcatServletWebServerFactory,整理 by zhengkai.blog.csdn.net
	 */
	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory
				: createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		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);
		//getTomcatWebServer=return new TomcatWebServer(tomcat, getPort() >= 0);
		return getTomcatWebServer(tomcat);
	}
	/**
	 * 创建并获取JettyWebServer,JettyServletWebServerFactory,整理 by zhengkai.blog.csdn.net
	 */
	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		JettyEmbeddedWebAppContext context = new JettyEmbeddedWebAppContext();
		int port = (getPort() >= 0) ? getPort() : 0;
		InetSocketAddress address = new InetSocketAddress(getAddress(), port);
		Server server = createServer(address);
		configureWebAppContext(context, initializers);
		server.setHandler(addHandlerWrappers(context));
		this.logger.info("Server initialized with port: " + port);
		if (getSsl() != null && getSsl().isEnabled()) {
			customizeSsl(server, address);
		}
		for (JettyServerCustomizer customizer : getServerCustomizers()) {
			customizer.customize(server);
		}
		if (this.useForwardHeaders) {
			new ForwardHeadersCustomizer().customize(server);
		}
		//getJettyWebServer=return new JettyWebServer(server, getPort() >= 0);
		return getJettyWebServer(server);
	}
	/**
	 * 创建并获取UndertowWebServer,UndertowServletWebServerFactory,整理 by zhengkai.blog.csdn.net
	 */
	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		DeploymentManager manager = createDeploymentManager(initializers);
		int port = getPort();
		Builder builder = createBuilder(port);
		//getUndertowWebServer=return new UndertowServletWebServer(builder, manager, getContextPath(),isUseForwardHeaders(), port >= 0, getCompression(), getServerHeader());
		return getUndertowWebServer(builder, manager, port);
	}
	//Undertow做多了一层Builder的封装
	private Builder createBuilder(int port) {
		Builder builder = Undertow.builder();
		if (this.bufferSize != null) {
			builder.setBufferSize(this.bufferSize);
		}
		if (this.ioThreads != null) {
			builder.setIoThreads(this.ioThreads);
		}
		if (this.workerThreads != null) {
			builder.setWorkerThreads(this.workerThreads);
		}
		if (this.directBuffers != null) {
			builder.setDirectBuffers(this.directBuffers);
		}
		if (getSsl() != null && getSsl().isEnabled()) {
			customizeSsl(builder);
		}
		else {
			builder.addHttpListener(port, getListenAddress());
		}
		for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
			customizer.customize(builder);
		}
		return builder;
	}

Tomcat vs Jetty vs Undertow

每个Web容器都有自己的设计和组件:

  • Tomcat是Connector
  • Jetty是Handler
  • Undertow是Builder

单纯比较 Tomcat 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。

而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。

另外 Jetty 默认使用的是 NIO 技术,在处理 I/O 请求上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源时,Tomcat 的性能不如 Jetty。

=。=Undertow可能大家陌生有点,是一个Java开发的灵活的高性能Web服务器,提供包括阻塞和基于NIO的非阻塞机制。Undertow是红帽公司的开源产品,是Wildfly默认的Web服务器。SpringBoot2中可以将Web服务器切换到Undertow来提高应用性能。

Undertow认为它的运用场景是在IO密集型的系统应用中,简单点的讲,就是结合了Tomcat和Jetty的优点,类似Netty的强大

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页