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


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

    2019年08月14日 11:58:33 Moshow郑锴 阅读数 140更多

    所属专栏: SpringBoot2启示录

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    本文链接:https://blog.csdn.net/moshowgame/article/details/99545081

    前言

    我们都知道,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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    抓住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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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 {   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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的强大

  • 相关阅读:
    css grid 随笔
    网页“console”输出图文信息
    2017
    自适应css 框架 PURE
    获取去除参数url地址
    微信分享
    video 播放
    手机端 默认字体
    video 手机全屏自动播放
    jquery 获取元素背景图片backgroungImage的url
  • 原文地址:https://www.cnblogs.com/grj001/p/12224354.html
Copyright © 2020-2023  润新知