SpringBoot内置Tomcat启动方式

 更新时间:2024年12月10日 16:27:48   作者:孙振宁1999  
Spring Boot通过启动类上的@EnableAutoConfiguration注解,自动生成并加载ServletWebServerFactoryAutoConfiguration类,该类通过@Import注解导入TomcatServletWebServerFactory类,该类在getWebServer()方法中创建并启动TomcatServletWebServer对象

一、Tomcat相关配置类如何加载的?

在springboot项目中,我们只需要引入spring-boot-starter-web依赖,启动服务成功,我们一个web服务就搭建好了,没有明显的看到tomcat。

其实打开spring-boot-starter-web依赖,我们可以看到:依赖了tomcat。

1.进入Springboot启动类

我们加入Springboot最核心的注解@SpringBootApplication,源码如下图:重点看注解@EnableAutoConfiguration,

2.进入注解@EnableAutoConfiguration

如下图:该注解通过@Import注解导入了AutoConfigurationImportSelector类。

其实这个类,就是导入通过加载配置文件,加载了很多工厂方法的配置类。

3.进入AutoConfigurationImportSelector类

首先调用selectImport()方法,在该方法中调用了 getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()方法, getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类。

详细讲解如下:也就是下图的,方法1调用方法2,方法2调用方法3:

到了这里加载了 META-INF/spring.factories文件:

4.我们看到

加载了ServletWebServerFactoryAutoConfiguration这个配置类,web工厂配置类。

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
  ...
}

从这个配置工厂类,我们看出通过@Import注解加载了tomcat,jetty,undertow三个web服务器的配置类。

由于没有导入jetty和undertow的相关jar包,这两个类实例的不会真正的加载。

5.进入EmbeddedTomcat类

创建了TomcatServletWebServerFactory类的对象。

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}
	}

6.进入TomcatServletWebServerFactory类

关注getWebServer()方法:

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		//实例化一个Tomcat
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		//设置Tomcat的工作临时目录
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		//默认使用Http11NioProtocal实例化Connector
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		//给Service添加Connector
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		//关闭热部署
		tomcat.getHost().setAutoDeploy(false);
		//配置Engine
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		// 实例化TomcatWebServer时会将DispatcherServlet以及一些Filter添加到Tomcat中
		return getTomcatWebServer(tomcat);
	}

getWebServer()方法在当前类,调用了getTomcatWebServer()方法,其实又new TomcatWebServer()对象:

	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

7.进入TomcatWebServer类

这个类才是真正的做tomcat启动的类:

(1)构造方法:调用了initialize()方法

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

(2)进入initialize()方法,这个方法:this.tomcat.start(),启动tomcat容器了

private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Tomcat在这里启动了
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

二、getWebServer()的调用分析,也就是tomcat何时启动的

上面分析了tomcat的配置到启动的方法,我们现在来分析,tomcat是何时启动的。

1.首先进入SpringBoot启动类的run方法

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

最终调用了本类的一个同名方法:

public ConfigurableApplicationContext run(String... args) {
		//记录程序运行时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// ConfigurableApplicationContext Spring 的上下文
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		//【1、获取并启动监听器】
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//【2、构造应用上下文环境】
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//处理需要忽略的Bean
			configureIgnoreBeanInfo(environment);
			//打印banner
			Banner printedBanner = printBanner(environment);
			///【3、初始化应用上下文】
			context = createApplicationContext();
			//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//【4、刷新应用上下文前的准备阶段】
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//【5、刷新应用上下文】
			refreshContext(context);
			//【6、刷新应用上下文后的扩展接口】
			afterRefresh(context, applicationArguments);
			//时间记录停止
			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;
	}

这个方法大概做了以下几件事:

  • 1)获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作
  • 2)构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
  • 3)创建容器
  • 4)实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
  • 5)准备容器
  • 6) 刷新容器
  • 7)刷新容器后的扩展接口

2.那么内置tomcat启动源码

就是隐藏在上面第六步:refreshContext方法里面,该方法最终会调 用到AbstractApplicationContext类的refresh()方法,进入refreshContext()方法,如图:

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

refreshContext()调用了refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

refresh()方法调用了this.onRefresh():

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			//核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

如下面的代码:createWebServer() 方法调用了一个factory.getWebServer()。

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			//先获取嵌入式Servlet容器工厂
			ServletWebServerFactory factory = getWebServerFactory();
			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();
	}

到了这里getWebServer()方法,下一步就是创建TomcatWebServer对象,创建该对象,就在构造方法启动了Tomcat。详细代码在第一部分有。

总结

tomcat启动流程

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java用重定向方法从文件中读入或写入数据

    java用重定向方法从文件中读入或写入数据

    这篇文章主要为大家详细介绍了用重定向方法从文件中读入或写入数据,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • MyBatis动态Sql之if标签的用法详解

    MyBatis动态Sql之if标签的用法详解

    这篇文章主要介绍了MyBatis动态Sql之if标签的用法,本文给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-07-07
  • Spring与Spring boot的区别介绍

    Spring与Spring boot的区别介绍

    Spring Boot框架的核心就是自动配置,只要存在相应的jar包,Spring就帮我们自动配置。接下来通过本文给大家介绍Spring与Spring boot的区别介绍,非常不错,需要的朋友参考下吧
    2017-04-04
  • 基于Java设计一个高并发的秒杀系统

    基于Java设计一个高并发的秒杀系统

    这篇文章主要为大家详细介绍了如何基于Java设计一个高并发的秒杀系统,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考下
    2023-10-10
  • Java设计模式之适配器模式简介

    Java设计模式之适配器模式简介

    这篇文章主要介绍了Java设计模式之适配器模式,需要的朋友可以参考下
    2014-07-07
  • java读取大文件简单实例

    java读取大文件简单实例

    这篇文章主要介绍了java读取大文件简单实例,有需要的朋友可以参考一下
    2013-12-12
  • JavaWeb实现文件上传与下载的方法

    JavaWeb实现文件上传与下载的方法

    这篇文章主要介绍了JavaWeb实现文件上传与下载的方法的相关资料,需要的朋友可以参考下
    2016-01-01
  • SpringMVC详解如何映射请求数据

    SpringMVC详解如何映射请求数据

    这篇文章主要给大家介绍了关于SpringMvc映射请求数据的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-06-06
  • Java实现简易的洗牌和发牌功能

    Java实现简易的洗牌和发牌功能

    本文主要介绍了Java实现简易的洗牌和发牌功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • jar中VO的探究

    jar中VO的探究

    这篇文章主要介绍了jar中VO的探究的相关资料,需要的朋友可以参考下
    2023-11-11

最新评论