<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency></dependencies>复制代码

Spring Boot 默认的 web 做事容器是 tomcat ,如果想利用 Jetty 等来更换 Tomcat ,可以自行参考官方文档来办理。

web、webmvc、tomcat 等供应了 web 运用的运行环境,那 spring-boot-starter 则是让这些运行环境事情的开关(由于 spring-boot-starter 中会间接引入 spring-boot-autoconfigure )。

WebServer 自动配置

jettyembeddedjspSpringBoot 中内嵌 Tomcat 的实现道理解析你懂得吗 jQuery

在 spring-boot-autoconfigure 模块中,有处理关于 WebServer 的自动配置类 ServletWebServerFactoryAutoConfiguration 。

ServletWebServerFactoryAutoConfiguration

代码片段如下:

@Configuration@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复制代码

两个 Condition 表示当前运行环境是基于 servlet 标准规范的 web 做事:

ConditionalOnClass(ServletRequest.class) : 表示当前必须有 servlet-api 依赖存在ConditionalOnWebApplication(type = Type.SERVLET) :仅基于servlet的Web运用程序

@EnableConfigurationProperties(ServerProperties.class):ServerProperties 配置中包括了常见的 server.port 等配置属性。

通过 @Import 导入嵌入式容器干系的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。

综合来看,ServletWebServerFactoryAutoConfiguration 自动配置类中紧张做了以下几件事情:

导入了内部类 BeanPostProcessorsRegistrar,它实现了 ImportBeanDefinitionRegistrar,可以实现ImportBeanDefinitionRegistrar 来注册额外的 BeanDefinition。
导入了 ServletWebServerFactoryConfiguration.EmbeddedTomcat 等嵌入容器先关配置(我们紧张关注tomcat 干系的配置)。
注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer 两个WebServerFactoryCustomizer 类型的 bean。

下面就针对这几个点,做下详细的剖析。

BeanPostProcessorsRegistrar

BeanPostProcessorsRegistrar 这个内部类的代码如下(省略了部分代码):

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { // 省略代码 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } // 注册 WebServerFactoryCustomizerBeanPostProcessor registerSyntheticBeanIfMissing(registry, \公众webServerFactoryCustomizerBeanPostProcessor\"大众, WebServerFactoryCustomizerBeanPostProcessor.class); // 注册 errorPageRegistrarBeanPostProcessor registerSyntheticBeanIfMissing(registry, \"大众errorPageRegistrarBeanPostProcessor\公众, ErrorPageRegistrarBeanPostProcessor.class); } // 省略代码}复制代码

上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,浸染是在 bean 初始化前后加一些自己的逻辑处理。

WebServerFactoryCustomizerBeanPostProcessor:浸染是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 方法来 处理 WebServerFactory。
errorPageRegistrarBeanPostProcessor:和上面的浸染差不多,不过这个是处理 ErrorPageRegistrar 的。

下面大略看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:

public class WebServerFactoryCustomizerBeanPostProcessorimplements BeanPostProcessor, BeanFactoryAware { // 省略部分代码 // 在 postProcessBeforeInitialization 方法中,如果当前 bean 是 WebServerFactory,则进行 // 一些后置处理 @Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {if (bean instanceof WebServerFactory) {postProcessBeforeInitialization((WebServerFactory) bean);}return bean;} // 这段代码便是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法 private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),webServerFactory).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class).invoke((customizer) -> customizer.customize(webServerFactory));} // 省略部分代码}复制代码

自动配置类中注册的两个 Customizer Bean

这两个 Customizer 实际上便是去处理一些配置值,然后绑定到 各自的工厂类的。

WebServerFactoryCustomizer

将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 工具实例上。

@Overridepublic void customize(ConfigurableServletWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); // 端口 map.from(this.serverProperties::getPort).to(factory::setPort); // address map.from(this.serverProperties::getAddress).to(factory::setAddress); // ContextPath map.from(this.serverProperties.getServlet()::getContextPath) .to(factory::setContextPath); // displayName map.from(this.serverProperties.getServlet()::getApplicationDisplayName) .to(factory::setDisplayName); // session 配置 map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession); // ssl map.from(this.serverProperties::getSsl).to(factory::setSsl); // jsp map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp); // 压缩配置策略实现 map.from(this.serverProperties::getCompression).to(factory::setCompression); // http2 map.from(this.serverProperties::getHttp2).to(factory::setHttp2); // serverHeader map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader); // contextParameters map.from(this.serverProperties.getServlet()::getContextParameters) .to(factory::setInitParameters);}复制代码

TomcatServletWebServerFactoryCustomizer

比较于上面那个,这个 customizer 紧张处理 Tomcat 干系的配置值

@Overridepublic void customize(TomcatServletWebServerFactory factory) { // 拿到 tomcat 干系的配置 ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat(); // server.tomcat.additional-tld-skip-patterns if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) { factory.getTldSkipPatterns() .addAll(tomcatProperties.getAdditionalTldSkipPatterns()); } // server.redirectContextRoot if (tomcatProperties.getRedirectContextRoot() != null) { customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot()); } // server.useRelativeRedirects if (tomcatProperties.getUseRelativeRedirects() != null) { customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects()); }}复制代码

WebServerFactory

用于创建 WebServer 的工厂的标记接口。

类体系构造

上图为 WebServerFactory -> TomcatServletWebServerFactory 的全体类构造关系。

TomcatServletWebServerFactory

TomcatServletWebServerFactory 是用于获取 Tomcat 作为 WebServer 的工厂类实现,个中最核心的方法便是 getWebServer,获取一个 WebServer 工具实例。

@Overridepublic WebServer getWebServer(ServletContextInitializer... initializers) { // 创建一个 Tomcat 实例 Tomcat tomcat = new Tomcat(); // 创建一个 Tomcat 实例事情空间目录 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(\公众tomcat\公众); tomcat.setBaseDir(baseDir.getAbsolutePath()); // 创建连接工具 Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); // 1 customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); // 配置 Engine,没有什么本色性的操作,可忽略 configureEngine(tomcat.getEngine()); // 一些附加链接,默认是 0 个 for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } // 2 prepareContext(tomcat.getHost(), initializers); // 返回 webServer return getTomcatWebServer(tomcat);}复制代码1、customizeConnector : 给 Connector 设置 port、protocolHandler、uriEncoding 等。
Connector 布局的逻辑紧张是在NIO和APR选择中选择一个协议,然后反射创建实例并强转为 ProtocolHandler2、prepareContext 这里并不是说准备当前 Tomcat 运行环境的高下文信息,而是准备一个 StandardContext ,也便是准备一个 web app。

准备 Web App Context 容器

对付 Tomcat 来说,每个 context 便是映射到 一个 web app 的,以是 prepareContext 做的事情便是将 web 运用映射到一个 TomcatEmbeddedContext ,然后加入到 Host 中。

protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); // 创建一个 TomcatEmbeddedContext 工具 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } // 设置描述此容器的名称字符串。
在属于特定父项的子容器集内,容器名称必须唯一。
context.setName(getContextPath()); // 设置此Web运用程序的显示名称。
context.setDisplayName(getDisplayName()); // 设置 webContextPath 默认是 / context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir(\公众tomcat-docbase\"大众); context.setDocBase(docBase.getAbsolutePath()); // 注册一个FixContextListener监听,这个监听用于设置context的配置状态以及是否加入登录验证的逻辑 context.addLifecycleListener(new FixContextListener()); // 设置 父 ClassLoader context.setParentClassLoader( (this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); // 覆盖Tomcat的默认措辞环境映射以与其他做事器对齐。
resetDefaultLocaleMapping(context); // 添加区域设置编码映射(请参阅Servlet规范2.4的5.4节) addLocaleMappings(context); // 设置是否利用相对地址重定向 context.setUseRelativeRedirects(false); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldSkipPatterns(context); // 设置 WebappLoader ,并且将 父 classLoader 作为构建参数 WebappLoader loader = new WebappLoader(context.getParentClassLoader()); // 设置 WebappLoader 的 loaderClass 值 loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); // 会将加载类向上委托 loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } // 是否注册 jspServlet if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); // 在 host 中 加入一个 context 容器 // add时给context注册了个内存泄露跟踪的监听MemoryLeakTrackingListener,详见 addChild 方法 host.addChild(context); //对context做了些设置事情,包括TomcatStarter(实例化并set给context), // LifecycleListener,contextValue,errorpage,Mime,session超时持久化等以及一些自定义事情 configureContext(context, initializersToUse); // postProcessContext 方法是空的,留给子类重写用的 postProcessContext(context);}复制代码

从上面可以看下,WebappLoader 可以通过 setLoaderClass 和 getLoaderClass 这两个方法可以变动loaderClass 的值。
以是也就意味着,我们可以自己定义一个继续 webappClassLoader 的类,来改换系统自带的默认实现。

初始化 TomcatWebServer

在 getWebServer 方法的末了便是构建一个 TomcatWebServer。

// org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactoryprotected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { // new 一个 TomcatWebServer return new TomcatWebServer(tomcat, getPort() >= 0);}// org.springframework.boot.web.embedded.tomcat.TomcatWebServerpublic TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, \公众Tomcat Server must not be null\"大众); this.tomcat = tomcat; this.autoStart = autoStart; // 初始化 initialize();}复制代码

这里紧张是 initialize 这个方法,这个方法中将会启动 tomcat 做事

private void initialize() throws WebServerException { logger.info(\公众Tomcat initialized with port(s): \"大众 + getPortsDescription(false)); synchronized (this.monitor) { try { // 对全局原子变量 containerCounter+1,由于初始值是-1, // 以是 addInstanceIdToEngineName 方法内后续的获取引擎并设置名字的逻辑不会实行 addInstanceIdToEngineName();// 获取 Context Context context = findContext(); // 给 Context 工具实例生命周期监听器 context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // 将上面new的connection以service(这里是StandardService[Tomcat])做key保存到 // serviceConnectors中,并将 StandardService 中的connectors 与 service 解绑(connector.setService((Service)null);), // 解绑后下面利用LifecycleBase启动容器就不会启动到Connector了 removeServiceConnectors(); } }); // 启动做事器以触发初始化监听器 this.tomcat.start(); // 这个方法检讨初始化过程中的非常,如果有直接在主线程抛出, // 检讨方法是TomcatStarter中的 startUpException,这个值是在 Context 启动过程中记录的 rethrowDeferredStartupExceptions(); try { // 绑定命名的高下文和classloader, ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // 设置失落败不须要关心 }// :与Jetty不同,Tomcat所有的线程都是守护线程,以是创建一个非守护线程 // (例:Thread[container-0,5,main])来避免做事到这就shutdown了 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException(\"大众Unable to start embedded Tomcat\"大众, ex); } }}复制代码

查找 Context ,实际上便是查找一个Tomcat 中的一个 web 运用,SpringBoot 中默认启动一个 Tomcat ,并且一个 Tomcat 中只有一个 Web 运用(FATJAR 模式下,运用与 Tomcat 是 1:1 关系),所有在遍历 Host 下的 Container 时,如果 Container 类型是 Context ,就直接返回了。

private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException(\公众The host does not contain a Context\公众);}复制代码

Tomcat 启动过程

在 TomcatWebServer 的 initialize 方法中会实行 tomcat 的启动。

// Start the server to trigger initialization listenersthis.tomcat.start();复制代码

org.apache.catalina.startup.Tomcat 的 start 方法:

public void start() throws LifecycleException { // 初始化 server getServer(); // 启动 server server.start();}复制代码

初始化 Server

初始化 server 实际上便是构建一个 StandardServer 工具实例,关于 Tomcat 中的 Server 可以参考附件中的解释。

public Server getServer() {// 如果已经存在的话就直接返回 if (server != null) { return server; }// 设置系统属性 catalina.useNaming System.setProperty(\"大众catalina.useNaming\"大众, \"大众false\"大众);// 直接 new 一个 StandardServer server = new StandardServer();// 初始化 baseDir (catalina.base、catalina.home、 ~/tomcat.{port}) initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort( -1 ); Service service = new StandardService(); service.setName(\"大众Tomcat\"大众); server.addService(service); return server;}复制代码

总结一下

上面对 SpringBoot 中内嵌 Tomcat 的过程做了剖析,这个过程实际上并不繁芜,便是在刷新 Spring 高下文的过程中将 Tomcat 容器启动起来,并且将当前运用绑定到一个 Context ,然后添加了 Host。
下图是程序的实行堆栈和实行内嵌 Tomcat 初始化和启动的机遇。

下面总结下全体过程:

通过自定配置注册干系的 Bean ,包括一些 Factory 和 后置处理器等高下文刷新阶段,实行创建 WebServer,这里须要用到前一个阶段所注册的 Bean包括创建 ServletContext实例化 webServer创建 Tomcat 实例、创建 Connector 连接器绑定 运用到 ServletContext,并添加干系的生命周期范畴内的监听器,然后将 Context 添加到 host 中实例化 webServer 并且启动 Tomcat 做事

SpringBoot 的 Fatjar 办法没有供应共享 Tomcat 的实现逻辑,便是两个 FATJAT 启动可以只实例化一个 Tomcat 实例(包括 Connector 和 Host ),从前面的剖析知道,每个 web 运用(一个 FATJAT 对应的运用)实例上便是映射到一个 Context ;而对付 war 办法,一个 Host 下面是可以挂载多个 Context 的。

附:Tomcat 组件解释

组件名称 解释 Server 表示全体Servlet 容器,因此 Tomcat 运行环境中只有唯一一个 Server 实例 Service Service 表示一个或者多个 Connector 的凑集,这些 Connector 共享同一个 Container 来处理其要求。
在同一个 Tomcat 实例内可以包含任意多个 Service 实例,他们彼此独立。
Connector Tomcat 连接器,用于监听和转化 Socket 要求,同时将读取的 Socket 要求交由 Container 处理,支持不同协议以及不同的 I/O 办法。
Container Container 表示能够实行客户端要求并返回相应的一类工具,在 Tomcat 中存在不同级别的容器:Engine、Host、Context、Wrapper Engine Engine 表示全体 Servlet 引擎。
在 Tomcat 中,Engine 为最高层级的容器工具,虽然 Engine 不是直接处理要求的容器,确是获取目标容器的入口 Host Host 作为一类容器,表示 Servlet 引擎(即Engine)中的虚拟机,与一个做事器的网络名有关,如域名等。
客户端可以利用这个网络名连接做事器,这个名称必须要在 DNS 做事器上注册 Context Context 作为一类容器,用于表示 ServletContext,在 Servlet 规范中,一个 ServletContext 即表示一个独立的 web 运用 Wrapper Wrapper 作为一类容器,用于表示 Web 运用中定义的 Servlet Executor 表示 Tomcat 组件间可以共享的线程池

小结

欢迎关注头条号:JAVA大飞哥

点击关注评论转发一波~~

私信

末了,每一位读到这里的Java程序猿朋友们,感谢你们能耐心地看完。
希望在成为一名更精良的Java程序猿的道路上,我们可以一起学习、一起进步!
都能赢取白富美,走向架构师的人生顶峰!