前言 在Spring的时期如果我们想部署一个JAVA服务,通常是在服务器上安装一个Tomcat服务,然后将JAVA项目打好的war包丢到相应位置就好,麻烦且容易出错。所以Spring Boot中简化了这个操作,可以直接启动一个项目而不用单独部署Tomcat,那么Spring Boot是如何实现这一切的呢?
自动引入Tomcat依赖 Tomcat也是基于JAVA开发的,它本质上也是一个jar包,所以Spring Boot为它添加了默认依赖,当我们创建一个web项目时会自动将tomcat的依赖引入进来。
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
然后点进去spring-boot-starter-web,可以看到它引入了spring-boot-starter-tomcat依赖,所以后面也就可以使用Tomcat的能力了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <version > 3.2.1</version > <scope > compile</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-json</artifactId > <version > 3.2.1</version > <scope > compile</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <version > 3.2.1</version > <scope > compile</scope > </dependency > *****省略****** </dependencies >
判断应用类型 在Spring Boot启动过程中会获取应用类型,然后根据这个应用类型自动配置合适的Web环境,这在前面的文章中有提及,我们本次看下具体细节。
1 2 3 4 5 6 public SpringApplication (ResourceLoader resourceLoader, Class<?>... primarySources) { ***省略前面代码*** this .webApplicationType = WebApplicationType.deduceFromClasspath(); ***省略后面代码*** }
可以看到系统启动时判断启动的系统属于REACTIVE项目、SERVLET项目还是NONE,我们是默认启动的,也就是SERVLET,Spring Boot就会给我们配置SERVLET环境。
1 2 3 4 5 6 7 8 9 10 11 12 static WebApplicationType deduceFromClasspath () { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null ) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null )) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null )) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
读取配置 我们会有一些配置项,比如说server.port=8090,这些配置项会包含在一个TomcatServletWebServerFactory的Bean中,那么它是如何被加载进来的?
简言之,在项目启动时Spring Boot的自动装配机制会装配一个ServletWebServerFactoryAutoConfiguration类,这个类维护在org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,它引入的EmbeddedTomcat类会构建TomcatServletWebServerFactory。
1 2 3 org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
可以看到ServletWebServerFactoryAutoConfiguration上通过@Import注解引入了四个类,容器类的分别是EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow,我们只需要看EmbeddedUndertow即可。
1 2 3 4 5 6 7 8 9 10 @AutoConfiguration(after = SslAutoConfiguration.class) @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 {}
继续看EmbeddedTomcat类,通过@ConditionalOnClass和@ConditionalOnMissingBean两个注解可以看出,这个配置会在满足以下条件时执行:
当前的classpath中包含Servlet、Tomcat和UpgradeProtocol这几个类(这通常意味着你的项目中包含了Tomcat的依赖)。
当前的Spring容器中还没有ServletWebServerFactory类型的Bean。
当这个配置方法执行时,它会创建一个新的TomcatServletWebServerFactory实例,并将其注册到Spring容器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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().toList()); factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList()); factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().toList()); return factory; } }
run()方法中的逻辑 接着就要到AbstractApplicationContext#refresh方法中的逻辑了,在这个方法中会调用onRefresh()方法,这个方法会有子类实现,在这里是ServletWebServerApplicationContext类。
ServletWebServerApplicationContext 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 @Override protected void onRefresh () { super .onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException ("Unable to start web server" , ex); } } private void createWebServer () { WebServer webServer = this .webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null ) { StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create" ); ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory" , factory.getClass().toString()); this .webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); 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(); }
继续看getWebServer方法,这里会声明Tomcat实例,并创建connector、配置引擎、配置Context等,最后会在TomcatWebServer#initialize方法中执行this.tomcat.start()启动Tomcat。
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 84 85 86 87 88 89 @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()); for (LifecycleListener listener : this .serverLifecycleListeners) { tomcat.getServer().addLifecycleListener(listener); } 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); } protected TomcatWebServer getTomcatWebServer (Tomcat tomcat) { return new TomcatWebServer (tomcat, getPort() >= 0 , getShutdown()); } public TomcatWebServer (Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null" ); this .tomcat = tomcat; this .autoStart = autoStart; this .gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown (tomcat) : null ; initialize(); } private void initialize () throws WebServerException { logger.info("Tomcat initialized with " + getPortsDescription(false )); synchronized (this .monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { removeServiceConnectors(); } }); disableBindOnInit(); this .tomcat.start(); rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } startNonDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException ("Unable to start embedded Tomcat" , ex); } } }
总结 简单来说大概可以分为以下几步
确认应用类型SERVLET,加载SERVELETweb环境
createWebServer()中生成TomcatServletWebServerFactoryBean实例
调用TomcatServletWebServerFactoryBean的getWebServer()生成并启动WebServer。
创建Tomcat实例,然后配置Connector、引擎、Context等
TomcatWebServer#initialize方法中执行this.tomcat.start()启动Tomcat