文章目录
- 前言
- 8.1 嵌入式Tomcat简介
- 8.1.1 嵌入式Tomcat与普通Tomcat
- 8.1.2 Tomcat整体架构
- 8.1.3 Tomcat的核心工作流程
- 8.2 SpringBoot中嵌入式容器的模型
- 8.2.1 WebServer
- 8.2.2 WebServerFactory
- 8.2.3 ServletWebServerFactory和ReactiveWebServerFactory
- 8.2.4 ConfigurableServletWebServerFactory
- 8.3 嵌入式Tomcat的初始化
- 8.3.1 创建TomcatWebServer
- 8.3.1.1 prepareContext
- 8.3.1.2 getTomcatWebServer
- (1)获取Context
- (2)阻止Connector初始化
- (3)启动Tomcat
- (4)阻止Tomcat结束
- 8.3.2 Web容器关闭相关的回调
- 8.3.2.1 WebServerGracefulShutdownLifecycle
- 8.3.2.2 WebServerStartStopLifecycle
- 8.4 嵌入式Tomcat的启动
- 8.5 小结
前言
当Web应用需要部署运行时,传统的做法是将项目打包成war包,然后部署到外置的Web容器中(如最常用的Tomcat容器)。SpringBoot的一大重要特性是支持嵌入式Web容器,基于SpringBoot的Web应用仅凭一个单独的jar包即可独立运行。
8.1 嵌入式Tomcat简介
8.1.1 嵌入式Tomcat与普通Tomcat
嵌入式Tomcat是一种可以嵌入到Web应用中,无需单独部署的Tomcat容器。
普通的外置Tomcat与嵌入式Tomcat从核心、本质上看没有任何区别,都可以承载Web应用的运行。
但SpringBoot整合嵌入式Tomcat容器时在底层设定了一些额外的限制。
- 部署应用的限制:由于嵌入式Tomcat不是独立的Web容器,而是嵌入到特定的Web应用中的,因此该嵌入式Tomcat容器只能部署这一个特定的Web应用。
- web.xml的限制:SpringBoot整合嵌入式Tomcat后不再对web.xml文件予以支持。
- Servlet原生三大组件的限制:原生的基于Servlet 3.0及以上规范的Web项目,其类路径下的Servlet、Filter、Listener可以被自动扫描并注册,而SpringBoot整合嵌入式Tomcat后该特性失效。如果需要开启该特性,需要配合@ServletConponentScan注解使用。
- JSP的限制:SpringBoot整合嵌入式Tomcat后,如果以独立jar包的方式启动项目(
java -jar
),则JSP页面会失效;如果以war包的方式部署到外置的Tomcat容器,则JSP页面可以正常运行。
8.1.2 Tomcat整体架构
图片来源于CSDN文章:Tomcat.02结构图&启动&server.xml&连接器
从架构图可以得出Tomcat的架构设计如下:
- 一个Tomcat服务器是一个Server;
- 一个Server包含多个服务Service,其中提供默认HTTP请求响应服务的是Catalina;
- 一个Service包含多个Connector,用于与客户端交互,实现接收客户端请求并转发到Engine和接收Engine响应结果并响应给客户端;
- 一个Service还包含一个Container Engine,用于真正处理客户端的请求,并响应结果;
- 一个Container Engine包含多个Host,每个Host可以装在多个Web应用;
- 一个Web应用对应一个Context,一个Context包含多个Servlet。
8.1.3 Tomcat的核心工作流程
Tomcat作为一个Web服务器,其核心工作是接收客户端发起的HTTP请求,转发给服务器端的Web应用处理,处理完成后将结果响应给客户端。
其工作流程大致如下:
1、请求进入Tomcat容器,Tomcat容器内部根据请求URL,判断该请求应该由哪个应用来处理,并将请求封装为ServletRequest对象,转发至对应的Web应用中的Context。
2、Context接收到ServletRequest对象后,根据请求URI定位可以接收当前请求的Servlet,并将请求转发给具体的Servlet进行处理。
3、在转发到Servlet之前,容器会检查对应的Servlet是否已加载,如果没有加载,则会利用反射机制创建Servlet对象,并调用其init
方法完成初始化,之后再进行逻辑处理。
4、Servlet处理完成后,将响应结果以ServletResponse对应响应给Service中的Connector,由Connector响应给客户端,至此完成一次请求处理。
8.2 SpringBoot中嵌入式容器的模型
SpringBoot支持的嵌入式容器包括Tomcat、Jetty、Undertow、Netty等。
8.2.1 WebServer
代码清单1:WebServer.java|TomcatWebServer.javapublic interface WebServer {void start() throws WebServerException;void stop() throws WebServerException;// ...
}public class TomcatWebServer implements WebServer {private final Tomcat tomcat;// ...
}
由 代码清单1 可知,WebServer是SpringBoot针对所有嵌入式Web容器制定的顶级接口,定义了嵌入式Web容器的启动和停止动作。
实现WebServer接口的实现类通常会在内部组合一个真正的嵌入式容器,如TomcatWebServer中包含一个Tomcat对象,并重写start
方法实现嵌入式Web容器的启动逻辑,重写stop
方法实现嵌入式Web容器的停止和销毁逻辑。
8.2.2 WebServerFactory
代码清单2:WebServerFactory.javapublic interface WebServerFactory {
}
由 代码清单2 可知,WebServerFactory接口没有定义任何方法,仅为标记性接口。
WebServerFactory是所有具备创建WebServer能力的工厂对象的根接口。
代码清单3:ConfigurableWebServerFactory.javapublic interface ConfigurableWebServerFactory extends WebServerFactory, ErrorPageRegistry {void setPort(int port);void setSsl(Ssl ssl);// ...// 优雅停机default void setShutdown(Shutdown shutdown) {}
}
由 代码清单3 可知,ConfigurableWebServerFactory是WebServerFactory的扩展接口,具备对WebServerFactory的配置能力(包括配置端口、SSL等)。
值得注意的是,嵌入式Web容器具有优雅停机的特性,即容器在关闭时不直接终止进程,而是预留一些时间使容器内部的业务线程全部处理完毕后才关停容器服务。
8.2.3 ServletWebServerFactory和ReactiveWebServerFactory
代码清单4:ServletWebServerFactory.java|ReactiveWebServerFactory.javapublic interface ServletWebServerFactory {WebServer getWebServer(ServletContextInitializer... initializers);
}public interface ReactiveWebServerFactory {WebServer getWebServer(HttpHandler httpHandler);
}
由 代码清单4 可知,这是两个平级接口,分别是Servlet和Reactive场景下的嵌入式容器创建工厂。getWebServer
方法的定义仅是入参不同(不同类型的Web容器在创建时传入的初始化组件不同),但都返回WebServer对象。
8.2.4 ConfigurableServletWebServerFactory
代码清单5:ConfigurableServletWebServerFactory.javapublic interface ConfigurableServletWebServerFactory extends ConfigurableWebServerFactory, ServletWebServerFactory {void setContextPath(String contextPath);void setInitializers(List<? extends ServletContextInitializer> initializers);void addInitializers(ServletContextInitializer... initializers);void setInitParameters(Map<String, String> initParameters);// ...
}
由 代码清单5 可知,ConfigurableServletWebServerFactory是ConfigurableWebServerFactory和ServletWebServerFactory组合后产生的子接口,具备更多能力,如可以设置访问Web应用所需的context-path、设置ServletContextInitializer、设置初始化参数等。
8.3 嵌入式Tomcat的初始化
在 SpringBoot源码解读与原理分析(二十三)IOC容器的刷新(四) 中提到,IOC容器刷新的第9步的onRefresh
方法是一个模板方法,需要子类实现。
ServletWebServerApplicationContext类就是其中一个子类,重写了onRefresh
方法,用于嵌入式Web容器的初始化。
代码清单6:ServletWebServerApplicationContext.java@Override
protected void onRefresh() {super.onRefresh();try {createWebServer();} // catch ...
}private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();// 如果WebServer和ServletContext均为null,则需要创建嵌入式Web容器if (webServer == null && servletContext == null) {// 获取WebServerFactoryServletWebServerFactory factory = getWebServerFactory();// 创建WebServerthis.webServer = factory.getWebServer(getSelfInitializer());// 回调优雅停机的钩子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();
}protected ServletWebServerFactory getWebServerFactory() {String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);// ...return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
由 代码清单6 可知,createWebServer
方法中包含WebServer的创建,还有两个与生命周期回调相关的钩子。
8.3.1 创建TomcatWebServer
由 代码清单6 可知,创建嵌入式Web容器的入口是ServletWebServerFactory,其中嵌入式Tomcat容器的创建在其实现类TomcatServletWebServerFactory的getWebServer
方法中实现。
代码清单7:TomcatServletWebServerFactory.javapublic static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private String protocol = DEFAULT_PROTOCOL;
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {// ...Tomcat tomcat = new Tomcat();// 给嵌入式Tomcat创建一个临时文件夹,用于存放Tomcat运行中需要的文件File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());// Connector中默认放入的protocol为NIO模式Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);// 向Service中添加Connector,并执行定制规则tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);// 关闭热部署(因为嵌入式Tomcat不存在修改web.xml、war包等情况)tomcat.getHost().setAutoDeploy(false);configureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}// 生成TomcatEmbeddedContext上下文prepareContext(tomcat.getHost(), initializers);// 创建TomcatWebServerreturn getTomcatWebServer(tomcat);
}
由 代码清单7 可知,获取到WebServerFactory后,下一步会执行getWebServer
方法创建嵌入式Tomcat。
该方法核心步骤大致分为三步:创建Tomcat对象,并初始化基础的Connector和Engine;prepareContext
方法初始化Context,构建应用上下文;getTomcatWebServer
方法创建最终的TomcatWebServer对象。
8.3.1.1 prepareContext
代码清单8:TomcatServletWebServerFactory.javaprotected void prepareContext(Host host, ServletContextInitializer[] initializers) {File documentRoot = getValidDocumentRoot();// 创建ContextTomcatEmbeddedContext context = new TomcatEmbeddedContext();// ...// 配置生命周期监听器等// ...// 应用ServletContextInitializerServletContextInitializer[] initializersToUse = mergeInitializers(initializers);// Context添加到Host中host.addChild(context);configureContext(context, initializersToUse);postProcessContext(context);
}
由 代码清单8 可知,prepareContext
方法的环节包括创建Tomcat内置上下文对象、配置生命周期监听器、应用ServletContextInitializer等。
- ServletContextInitializer的设计
ServletContextInitializer本身并不是Servlet相关规范中定义的API,它是SpringBoot 1.4.0以后定义的API接口,这也说明ServletContextInitializer与某个具体的Web容器没有任何关系。
借助IDEA,可以发现ServletContextInitializer的其中一个实现类RegistrationBean,而它又是Servlet三大核心组件的注册实现类ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean的父类。
如此来看,Servlet三大核心组件应会通过ServletContextInitializer与嵌入式Tomcat对接。
代码清单9:TomcatServletWebServerFactory.javaprotected void configureContext(Context context, ServletContextInitializer[] initializers) {TomcatStarter starter = new TomcatStarter(initializers);if (context instanceof TomcatEmbeddedContext) {TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;embeddedContext.setStarter(starter);embeddedContext.setFailCtxIfServletStartFails(true);}context.addServletContainerInitializer(starter, NO_CLASSES);// ...
}
由 代码清单8、9 可知,prepareContext
方法倒数第2行有对ServletContextInitializer的配置应用,即调用configureContext
方法,该方法会在内部实例化一个TomcatStarter对象,并加载到Servlet容器中。
代码清单10:TomcatStarter.javaclass TomcatStarter implements ServletContainerInitializer {private final ServletContextInitializer[] initializers;@Overridepublic void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {try {for (ServletContextInitializer initializer : this.initializers) {initializer.onStartup(servletContext);}} // catch ...}
}
由 代码清单10 可知,TomcatStarter对象本身也是一个ServletContainerInitializer,被Servlet容器加载后,调用其onStartup
方法。在该方法内部,会循环调用所有ServletContextInitializer的onStartup
方法,由此完成Servlet原生三大核心组件的注册。
8.3.1.2 getTomcatWebServer
当prepareContext
方法执行完后,会执行getTomcatWebServer
方法,以创建TomcatWebServer对象。
代码清单11:TomcatServletWebServerFactory.javaprotected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
代码清单12:TomcatWebServer.javapublic 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();
}
由 代码清单11、12 可知,TomcatWebServer的初始化逻辑在其initialize
方法中。
(1)获取Context
代码清单13:TomcatWebServer.javaprivate void initialize() throws WebServerException {// ...//(1)获取第一个可用的ContextContext context = findContext();//...
}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");
}
由 代码清单13 可知,initialize
方法的第一个核心动作获取Tomcat中第一个可用的Context,即调用findContext
方法。findContext
方法会从Host中获取第一个类型为Context的子元素并返回。
实际上,这里获取到的就是在prepareContext
方法中创建的TomcatEmbeddedContext。
(2)阻止Connector初始化
代码清单14:TomcatWebServer.javaprivate void initialize() throws WebServerException {// ...//(2)添加LifecycleListener,移除Connectorcontext.addLifecycleListener((event) -> {if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {// javadoc:移除Service中的Connector,那么当服务启动后协议绑定就不会发生。removeServiceConnectors();}});// ...
}
由 代码清单14 可知,initialize
方法的第二个核心动作是添加一个LifecycleListener,该监听器的功能是移除Service中的Connector,即阻止Connector初始化。
在 代码清单7 中,getWebServer
方法创建了Connector并放入了Service中,为什么这里要移除掉呢?
那是因为,创建嵌入式Web容器的时机是在IOC容器刷新的第9步,早于第11步的 初始化所有剩下的单实例finishBeanFactoryInitialization
方法,此时IOC容器中绝大多数单实例bean对象尚未初始化,还不具备提供服务的能力。
Connector的功能是与客户端交互,一旦Connector初始化完成,意味着Tomcat可以对外提供服务,即客户端可以成功访问到Tomcat服务。
为了访问客户端成功访问到暂时无法提供服务的Tomcat服务,需要先将Connector移除。
(3)启动Tomcat
代码清单15:TomcatWebServer.javaprivate void initialize() throws WebServerException {// ...//(3)启动Tomcatthis.tomcat.start();// ...
}
由 代码清单15 可知,initialize
方法的第三个核心动作是启动Tomcat。
代码清单16:Tomcat.javapublic void start() throws LifecycleException {getServer();server.start();
}public Server getServer() {// ...server = new StandardServer();// ...// 端口设置为-1,代表这是嵌入式Tomcatserver.setPort( -1 );Service service = new StandardService();service.setName("Tomcat");server.addService(service);return server;
}
代码清单17:LifecycleBase.java@Override
public final synchronized void start() throws LifecycleException {// 前置判断 ...// 初始化if (state.equals(LifecycleState.NEW)) {init();} // else if ...try {setStateInternal(LifecycleState.STARTING_PREP, null, false);// 启动自身startInternal();// ...} // catch ...
}
由 代码清单16、17 可知,Tomcat引导Server初始化和启动时,会在获取到Server后调用其start
方法,而start
方法定义在所有Tomcat核心组件的共同父类LifecycleBase上。
值得注意的是,获取Server时端口号被设置为-1,代表这是嵌入式Tomcat,方便后续使用。
代码清单18:LifecycleBase.java@Override
public final synchronized void init() throws LifecycleException {if (!state.equals(LifecycleState.NEW)) {invalidTransition(Lifecycle.BEFORE_INIT_EVENT);}try {setStateInternal(LifecycleState.INITIALIZING, null, false);initInternal();setStateInternal(LifecycleState.INITIALIZED, null, false);} // catch ...
}protected abstract void initInternal() throws LifecycleException;
由 代码清单18 可知,init
方法依然由父类LifecycleBase定义,其中间的initInternal
方法是一个模板方法,由子类实现。
代码清单19:StandardServer.java@Override
protected void initInternal() throws LifecycleException {super.initInternal();// ...for (Service service : services) {service.init();}
}
由 代码清单19 可知,StandardServer类实现了initInternal
方法,在该方法中会触发Service的初始化。
通过查阅源码发现,service.init()
内部会触发Connector的初始化,以及后续所有核心组件的初始化逻辑都大致相同。完整的逻辑可以通过下面这张时序图体现出来:
(4)阻止Tomcat结束
代码清单20:TomcatWebServer.javaprivate void initialize() throws WebServerException {// ...//(4)阻止Tomcat结束startDaemonAwaitThread();// ...
}private void startDaemonAwaitThread() {// 创建一个新线程Thread awaitThread = new Thread("container-" + (containerCounter.get())) {@Overridepublic void run() {TomcatWebServer.this.tomcat.getServer().await();}};awaitThread.setContextClassLoader(getClass().getClassLoader());// 将该线程设置为非守护进程awaitThread.setDaemon(false);awaitThread.start();
}
由 代码清单20 可知,initialize
方法的第四个核心动作是启动一个新的awaitThread线程,以阻止Tomcat进程结束,其内部实现的run
方法是回调Server的await
方法。
- Daemon(守护)线程
在一个Java应用中,只要有一个非Daemon线程在运行,Daemon线程就不会停止,整个应用也不会终止。
如果Tomcat需要一直运行以接收客户端请求,就必须让Tomcat内部的Daemon进程都存活,至少需要一个能阻止Tomcat进程停止的非Daemon进程,而这里创建的awaitThread进程,将其Daemon设置为false,就是要负责阻止Tomcat进程停止。
- await方法
代码清单21:StandardServer.javapublic void await() {// -2 时的处理 ...// 如果关闭Tomcat的端口是-1,代表是嵌入式Tomcatif (getPortWithOffset() == -1) {try {awaitThread = Thread.currentThread();while(!stopAwait) {try {Thread.sleep( 10000 );} catch( InterruptedException ex ) {// continue and check the flag}}} finally {awaitThread = null;}return;}// 退出端口的其他处理 ...
}
由 代码清单21 可知,如果关闭Tomcat的端口是-1,代表是嵌入式Tomcat(详见(3)启动Tomcat)。阻塞Tomcat进程结束的方式是每隔10s检查一次stopAwait的值,只要该值一直为false,Tomcat就不会退出。
······
经过上述一系列核心组件的初始化和启动,嵌入式Tomcat容器初始化完成。但此时Tomcat还不能提供服务,因为Connector在该阶段被移除,无法与客户端建立有效连接。
8.3.2 Web容器关闭相关的回调
由 代码清单6 可知,createWebServer
方法中还有两个与生命周期回调相关的钩子。
8.3.2.1 WebServerGracefulShutdownLifecycle
代码清单22:WebServerGracefulShutdownLifecycle.javaclass WebServerGracefulShutdownLifecycle implements SmartLifecycle {private final WebServer webServer;WebServerGracefulShutdownLifecycle(WebServer webServer) {this.webServer = webServer;}@Overridepublic void stop(Runnable callback) {this.running = false;this.webServer.shutDownGracefully((result) -> callback.run());}
}
由 代码清单22 可知,WebServerGracefulShutdownLifecycle用于触发嵌入式Web容器优雅停机的核心生命周期回调,它实现了SmartLifecycle接口,可以在IOC容器销毁阶段回调其stop
方法以触发销毁逻辑。
其stop
方法会回调WebServer的shutDownGracefully
方法实现优雅停机。
8.3.2.2 WebServerStartStopLifecycle
代码清单23:WebServerStartStopLifecycle.javaclass WebServerStartStopLifecycle implements SmartLifecycle {private final WebServer webServer;WebServerStartStopLifecycle(WebServer webServer) {this.webServer = webServer;}@Overridepublic void start() {this.webServer.start();this.running = true;this.applicationContext.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));}@Overridepublic void stop() {this.webServer.stop();}
}
由 代码清单23 可知,WebServerStartStopLifecycle的作用启动和关闭嵌入式Web容器,它会在IOC容器刷新即将完成/销毁时被回调,从而回调WebServer的start/stop
方法,真正启动/关闭嵌入式Web容器。
8.4 嵌入式Tomcat的启动
当onRefresh
方法执行完毕,后续的finishBeanFactoryInitialization
方法执行完毕,IOC容器中所有非延迟加载的单实例bean对象均初始化完毕,此时会执行IOC容器刷新的第12步finishRefresh
方法,该方法中会回调所有的SmartLifeCycle,其中就包括 8.3.2.2 中的WebServerStartStopLifecycle,它会在该阶段回调嵌入式Web容器的start
方法,从而真正启动Web容器。
代码清单24:WebServerManager.javavoid start() {this.handler.initializeHandler();this.webServer.start();this.applicationContext.publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext));
}
代码清单25:TomcatWebServer.javapublic void start() throws WebServerException {synchronized (this.monitor) {if (this.started) {return;}try {// 还原、启动ConnectoraddPreviouslyRemovedConnectors();Connector connector = this.tomcat.getConnector();if (connector != null && this.autoStart) {performDeferredLoadOnStartup();}checkThatConnectorsHaveStarted();this.started = true;logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"+ getContextPath() + "'");} // catch ...}
}private void addPreviouslyRemovedConnectors() {Service[] services = this.tomcat.getServer().findServices();for (Service service : services) {// 之前移除的Connector在serviceConnectors中Connector[] connectors = this.serviceConnectors.get(service);if (connectors != null) {for (Connector connector : connectors) {// 添加并启动Connectorservice.addConnector(connector);if (!this.autoStart) {stopProtocolHandler(connector);}}this.serviceConnectors.remove(service);}}
}
由 代码清单24、25 可知,进入TomcatWebServer的start
方法后,会调用addPreviouslyRemovedConnectors
方法以还原并启动之前被移除掉的Connector(启动逻辑也在addConnector
方法中)。
至此,嵌入式Tomcat完整启动。
8.5 小结
第8章到此就梳理完毕了,本章的主题是:嵌入式Web容器。回顾一下本章的梳理的内容:
(二十七)嵌入式Tomcat容器
更多内容请查阅分类专栏:SpringBoot源码解读与原理分析
第9章主要梳理:AOP模块的生命周期。主要内容包括:
- AOP的核心后置处理器AnnotationAwareAspectJAutoProxyCreator;
- AOP底层收集切面类的机制;
- Bean被AOP代理的过程原理;
- 代理对象执行的全流程分析。