前言
当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.java
public 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.java
public interface WebServerFactory {
}
由 代码清单2 可知,WebServerFactory接口没有定义任何方法,仅为标记性接口。
WebServerFactory是所有具备创建WebServer能力的工厂对象的根接口。
代码清单3:ConfigurableWebServerFactory.java
public 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.java
public 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.java
public 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) {
// 获取WebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 创建WebServer
this.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.java
public 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);
// 创建TomcatWebServer
return getTomcatWebServer(tomcat);
}
由 代码清单7 可知,获取到WebServerFactory后,下一步会执行getWebServer
方法创建嵌入式Tomcat。
该方法核心步骤大致分为三步:创建Tomcat对象,并初始化基础的Connector和Engine;prepareContext
方法初始化Context,构建应用上下文;getTomcatWebServer
方法创建最终的TomcatWebServer对象。
8.3.1.1 prepareContext
代码清单8:TomcatServletWebServerFactory.java
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
// 创建Context
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
// ...
// 配置生命周期监听器等
// ...
// 应用ServletContextInitializer
ServletContextInitializer[] 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.java
protected 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.java
class TomcatStarter implements ServletContainerInitializer {
private final ServletContextInitializer[] initializers;
@Override
public 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.java
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
代码清单12:TomcatWebServer.java
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();
}
由 代码清单11、12 可知,TomcatWebServer的初始化逻辑在其initialize
方法中。
(1)获取Context
代码清单13:TomcatWebServer.java
private void initialize() throws WebServerException {
// ...
//(1)获取第一个可用的Context
Context 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.java
private void initialize() throws WebServerException {
// ...
//(2)添加LifecycleListener,移除Connector
context.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.java
private void initialize() throws WebServerException {
// ...
//(3)启动Tomcat
this.tomcat.start();
// ...
}
由 代码清单15 可知,initialize
方法的第三个核心动作是启动Tomcat。
代码清单16:Tomcat.java
public void start() throws LifecycleException {
getServer();
server.start();
}
public Server getServer() {
// ...
server = new StandardServer();
// ...
// 端口设置为-1,代表这是嵌入式Tomcat
server.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.java
private void initialize() throws WebServerException {
// ...
//(4)阻止Tomcat结束
startDaemonAwaitThread();
// ...
}
private void startDaemonAwaitThread() {
// 创建一个新线程
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public 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.java
public void await() {
// -2 时的处理 ...
// 如果关闭Tomcat的端口是-1,代表是嵌入式Tomcat
if (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.java
class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
private final WebServer webServer;
WebServerGracefulShutdownLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public 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.java
class WebServerStartStopLifecycle implements SmartLifecycle {
private final WebServer webServer;
WebServerStartStopLifecycle(WebServer webServer) {
this.webServer = webServer;
}
@Override
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
@Override
public 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.java
void start() {
this.handler.initializeHandler();
this.webServer.start();
this.applicationContext
.publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext));
}
代码清单25:TomcatWebServer.java
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
// 还原、启动Connector
addPreviouslyRemovedConnectors();
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) {
// 添加并启动Connector
service.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源码解读与原理分析文章来源:https://www.toymoban.com/news/detail-835221.html
第9章主要梳理:AOP模块的生命周期。主要内容包括:文章来源地址https://www.toymoban.com/news/detail-835221.html
- AOP的核心后置处理器AnnotationAwareAspectJAutoProxyCreator;
- AOP底层收集切面类的机制;
- Bean被AOP代理的过程原理;
- 代理对象执行的全流程分析。
到了这里,关于SpringBoot源码解读与原理分析(二十七)嵌入式Tomcat的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!