SpringBoot内置Tomcat启动方式
一、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启动流程
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
最新评论