文章目录
- 概述
- `TomcatEmbeddedServletContainerFactory`作为bean定义注册到容器
- 内置`Tomcat servlet`容器的创建和初始化
- 内置`Tomcat servlet`容器的启动
- 参考文章
该分析基于
Springboot 1.5.8 RELEASE
概述
独立部署的tomcat服务器的启动过程
传统意义上一个独立部署和运行的tomcat服务器的启动可以理解成两个阶段 :
-
tomcat容器本身的启动; -
tomcat容器中所部署的web app的启动;
完成了以上两个阶段,我们才能访问到我们所开发的业务逻辑。在这种情况下,web app的部署动作,通常是由系统部署人员通过某种方式在启动服务器前完成的。
spring boot web应用启动过程和独立部署的tomcat服务器启动过程的不同点
相对于一个独立部署和运行的tomcat服务器,一个缺省配置的spring boot web应用,情况有些不同 :
-
tomcat不再是独立存在的,是被内嵌到应用中的; -
web app的部署不是系统部署人员部署的,是spring boot应用按照某种约定运行时组装和部署的;
在启动spring boot web应用之前,开发人员需要按照 spring boot的规范编码,实现特定的类,打包部署该spring boot web应用,然后才能启动该应用并提供相应的服务。
本文中介绍的 spring boot web应用的启动过程,指的是从输入spring boot web应用的启动命令开始到开发人员所实现web app的启动完成。
spring boot web应用启动过程概述
前提 :
-
缺省情况下web应用引用了依赖
spring-boot-starter-tomcat; -
TomcatEmbeddedServletContainerFactory是spring-boot-autoconfigure创建内置tomcat servlet容器的工厂类;
启动过程 :
-
在自动配置工具
spring-boot-autoconfigure执行的自动配置阶段,EmbeddedServletContainerAutoConfiguration会因为spring-boot-starter-tomcat的存在而注册bean定义TomcatEmbeddedServletContainerFactory; -
然后在
spring boot启动web容器的阶段,应用上下文application context会使用注册的该bean定义TomcatEmbeddedServletContainerFactory创建并启动一个内置的tomcat servlet容器
TomcatEmbeddedServletContainer
该过程中
spring boot会将以bean定义形式注册到bean容器的的Servlet,Filter,Event Listener,Web Controller等web app元素关联到tomcat形成一个web app,然后对外提供服务
TomcatEmbeddedServletContainerFactory作为bean定义注册到容器
EmbeddedServletContainerAutoConfiguration
在spring boot自动配置工具spring-boot-autoconfigure的元数据文件META-INF/spring.factories中
定义自动配置属性org.springframework.boot.autoconfigure.EnableAutoConfiguration时,该属性
的值包含了如下值 :
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
EmbeddedServletContainerAutoConfiguration 的实现(部分):
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
// 这里 Servlet, Tomcat 是spring-boot-starter-tomcat提供的,
// 当 spring-boot-starter-tomcat 被引入到 classpath 时,以下@ConditionalOnClass
// 的条件会被满足,从而应用该配置类,注册bean定义 TomcatEmbeddedServletContainerFactory,
// 也就是整个web应用启动web servlet容器时所要使用的servlet容器工厂类
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,
search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory()
{
return new TomcatEmbeddedServletContainerFactory();
}
}
//...
}
内置Tomcat servlet容器的创建和初始化
调用入口点 :
// 由此调用链可以看出,内置Tomcat servlet容器的创建和初始化实在Spring ApplicationContext
// 容器的refresh()过程中执行的。
SpringApplication.run()
=>refresh(ApplicationContext applicationContext)
=>EmbeddedWebApplicationContext.refresh()
=> super.refresh()
=>EmbeddedWebApplicationContext.onRefresh()
=>createEmbeddedServletContainer()
EmbeddedWebApplicationContext的方法createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
// 获取属性中记录的嵌入式servlet容器,spring boot web 应用启动时这里一定是 null
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
// 获取属性中记录的嵌入式servlet上下文,spring boot web 应用启动时这里一定是 null
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
// spring boot web 应用启动时流程会走到这里
// 获取ApplicationContext bean定义注册阶段注册的EmbeddedServletContainerFactory
// 对于使用缺省配置的spring boot web 应用,这里实际上是
// TomcatEmbeddedServletContainerFactory
// 该bean实例化过程中已经通过BeanPostProcessor应用了各种
// EmbeddedServletContainerCustomizer,
// 比如 bean ServerProperties,DuplicateServerPropertiesDetector 等
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// 创建相应的嵌入式 servlet 容器并完成配置和初始化
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
TomcatEmbeddedServletContainerFactory的方法getEmbeddedServletContainer
// 所在包 : package org.springframework.boot.context.embedded.tomcat;
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// 创建内置tomcat servlet 容器的启动器
// tomcat有多种配置和启动方式,最常见的方式是基于server.xml配置的启动器
// org.apache.catalina.startup.Bootstrap, 而这里的Tomcat是一个内嵌tomcat的启动器
// 这个Tomcat启动器缺省会 :
// 1. 创建一个Tomcat Server,
// 2. 创建并关联到这个 Tomcat Server上一个 Tomcat Service:
// 1 Tomcat Service = 1 Tomcat Engine + N Tomcat Connector
// 每个Service只能包含一个Servlet引擎(Engine), 表示一个特定Service的请求处理流水线。
// 做为一个Service可以有多个连接器,引擎从连接器接收和处理所有的请求,将响应返回给
// 适合的连接器,通过连接器传输给用户。
// 用户可以通过实现Engine接口提供自定义引擎,但通常不需要这么做。
// 3. 在所创建的那一个 Tomcat Engine 上创建了一个 Tomcat Host 。
// 每个 Virtual Host 虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个
// 或者多个Web App,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,
// 将把该请求匹配到某个Context上。 一个 Tomcat Engine 上面可以有多个 Tomcat Host。
Tomcat tomcat = new Tomcat();
// 如果设置了baseDirectory则使用之,否则,在文件系统当前用户的临时目录下创建基础工作目录
// 缺省情况下,baseDirectory 是没有被设置的,新建的临时目录类似于 :
// C:\Users\ZHANGSAN\AppData\Local\Temp\tomcat.6659819252528658851.8080
File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat"));
// 设置tomcat的基础工作目录
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建tomcat的Connector,缺省协议为org.apache.coyote.http11.Http11NioProtocol,
// 表示处理 http v1.1 协议
Connector connector = new Connector(this.protocol);
// 这里 getService()方法内部会调用 getServer()方法,该方法真正创建 Tomcat 的 Server 对象实例,
// 其实现类是 org.apache.catalina.core.StandardServer
tomcat.getService().addConnector(connector);
// 根据配置参数定制 connector :端口,uri encoding字符集,是否启用SSL, 是否使用压缩等
// 缺省情况下端口是 8080, uri encoding 是 utf-8
customizeConnector(connector);
tomcat.setConnector(connector);
// 关闭应用的自动部署
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
// 如果指定了更多附加的Tomcat Connector,也添加进来
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 准备Tomcat StandardContext,对应一个webapp,并将其通过host关联到tomcat
// 参考下面方法prepareContext的注释
prepareContext(tomcat.getHost(), initializers);
// 创建 TomcatEmbeddedServletContainer 并初始化
// 其中包括调用 tomcat.start()
return getTomcatEmbeddedServletContainer(tomcat);
}
/**
* 纯程序方式创建并准备Tomcat StandardContext,它对应一个web应用,把它绑定到host上。
* 参数initializers是上面步骤提供的SCI,将它关联到Tomcat StandardContext。
**/
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
// 准备Host的docBase,
File docBase = getValidDocumentRoot();
// spring boot web应用启动过程中上面获得docBase会为null,实际会采用下面的
// 创建临时目录动作完成 docBase的创建
// 新建的 docBase的目录类似于 :
// C:\Users\ZHANGSAN\AppData\Local\Temp\tomcat-docbase.5929365937314033823.8080
// !!! 注意这里的 docBase 和上面提到的 baseDir 不同
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
// 创建StandardContext,这是Tomcat的标准概念,用来对应表示一个web应用,
// 这里使用实现类TomcatEmbeddedContext,由 spring boot 提供。
// 以下创建和初始化一个TomcatEmbeddedContext的过程,可以认为是往tomcat servlet
// 容器中部署和启动一个web应用的过程,只不过在传统方式下,一个web应用部署到tomcat使用
// war包的方式,而这里是完全程序化的方式。
final TomcatEmbeddedContext context = new TomcatEmbeddedContext();
// 设置StandardContext的名字,使用 context path,这个路径以/开始,没有/结尾;如果是根
// StandardContext,这个 context path 为空字符串(0长度字符串);
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader(
this.resourceLoader != null ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setUseRelativeRedirects(false);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.0.30. Continue
}
SkipPatternJarScanner.apply(context, this.tldSkipPatterns);
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
// 缺省情况下,会注册 Tomcat 的 DefaultServlet,
// DefaultServlet是Tomcat缺省的资源服务Servlet,用来服务HTML,图片等静态资源
// 配置信息 :
// servletClass : org.apache.catalina.servlets.DefaultServlet
// name : default
// overridable : true
// initParameter : debug, 0
// initParameter : listings, false
// loadOnStartup : 1
// servletMapping : / , default
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
// Spring boot 提供了一个工具类 org.springframework.boot.context.embedded.JspServlet
// 检测类 org.apache.jasper.servlet.JspServlet 是否存在于 classpath 中,如果存在,
// 则认为应该注册JSP Servlet。
// 缺省情况下,不注册(换句话讲,Springboot web应用缺省不支持JSP)
// 注意 !!! 这一点和使用Tomcat充当外部容器的情况是不一样的,
// 使用Tomcat作为外部容器的时候,JSP Servlet 缺省是被注册的。
// 如果想在 Spring boot中支持JSP,则需要将 tomcat-embed-jasper 包加入 classpath 中。
// 配置信息 :
// servletClass : org.apache.jasper.servlet.JspServlet
// name : jsp
// initParameter : fork, false
// initParameter : development, false
// loadOnStartup : 3
// servletMapping : *.jsp , jsp
// servletMapping : *.jspx , jsp
addJspServlet(context);
// Jasper 把JSP文件解析成java文件,然后编译成JVM可以使用的class文件。
// 有很多的JSP解析引擎,Tomcat中使用的是Jasper。
// 参考资料 : http://tomcat.apache.org/tomcat-8.0-doc/jasper-howto.html
// 下面添加的 Jasper initializer 用于初始化 jasper。
addJasperInitializer(context);
context.addLifecycleListener(new StoreMergedWebXmlListener());
}
context.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
TomcatResources.get(context)
.addResourceJars(getUrlsOfJarsWithMetaInfResources());
}
}
});
// 合并参数提供的Spring SCI : EmbeddedWebApplicationContext$1,
// 这是一个匿名内部类,封装的逻辑来自方法 selfInitialize()
// 和当前servlet容器在bean创建时通过EmbeddedServletContainerCustomizer
// ServerProperties添加进来的两个Spring SCI :
// ServerProperties$SessionConfiguringInitializer
// InitParameterConfiguringServletContextInitializer
// 注意这里的SCI接口由spring定义,tomcat jar中也包含了一个servlet API规范
// 定义的SCI接口,这是定义相同的两个接口而非同一个,最终实现了Spring SCI接口的
// 类的逻辑必须通过某种方式封装成实现了servlet API规范定义的SCI的逻辑才能被
// 执行
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
// 配置context,
// 1.将Spring提供的SCI封装成Servlet API标准SCI配置到context中去,
// 通过一个实现了Servlet API标准SCI接口的spring类 TomcatStarter
// 2.将spring领域的MIME映射配置设置到context中去,
// 3.将spring领域的session配置设置到context中去,比如 sessionTimeout
configureContext(context, initializersToUse);
// 将该context关联到host上去
host.addChild(context);
// 内部实现为空
postProcessContext(context);
}
// 创建TomcatEmbeddedServletContainer并初始化
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
// 第二个参数要填充目标实例的属性autoStart,
缺省端口为8080,所以getPort() >=0 为 true,也就是传递 autoStart为true
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
TomcatEmbeddedServletContainer的创建和初始化
// TomcatEmbeddedServletContainer有一个成员变量started,初始化值为 false,
// 用来表示容器是否处于已经启动状态
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;//设置是否要自动启动标志
// 初始化当前TomcatEmbeddedServletContainer,
// 主要是调用 tomcat.start(),该初始化过程会初始化和启动tomcat的server,service,engine,
// 会初始化相应的 connector, 但是并不会启动该 connector, 而是将其删除,在随后该
// TomcatEmbeddedServletContainer 的 start() 启动阶段,再将该 connector 添加到
// tomcat 的 service 中,然后启动该 connector。
initialize();
}
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
// 往引擎名字中增加instance Id信息,如果 instance id为0,则不修改引擎名字
addInstanceIdToEngineName();
try {
// Remove service connectors to that protocol binding doesn't happen
// yet
// 注意,从容器中把Connector删掉,这样下面随后马上执行的start()动作
// 只会启动容器中除了Connector之外的其他部分
// 注意,在更新版本的embedded Tomcat中,这里的逻辑变化了,变成了
// 在 service 启动后 protocal binding 尚未发生之前执行删除 service 中
// connector 的逻辑。
removeServiceConnectors();
// Start the server to trigger initialization listeners
// 1.触发启动Tomcat容器中除了Connector之外的其他部分,
// Connector此处没被启动意味着该启动过程完成后,服务器还是不能接受来自
// 网络的请求,因为Connector才是真正负责接受网络请求的入口。
// 2. 这里Tomcat启动的主要是
// StandardServer[1实例,Tomcat Lifecycle] =>
// StandardService[1实例,Tomcat Lifecycle] =>
// StandardEngine[1实例,Tomcat Container] =异步startStopExecutor =>
// StandardHost[1实例,Tomcat Container] =异步startStopExecutor =>
// TomcatEmbeddedContext[1实例,Springboot实现的Tomcat Container] =>
// StandardWrapper[1实例,Tomcat Container]。
// 这里StandardWrapper对应的Servlet是Spring MVC的DispatchServlet。
// 上面Tomcat Container父容器启动子容器都是通过线程池异步方式启动的。
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
Context context = findContext();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
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
// tomcat 自身所有的线程都是daemon线程。这里spring创建了一个非daemon线程用来
// 阻塞整个应用,避免刚启动就马上结束的情况。
startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
Tomcat.start()
/**
* Start the server.
*
* @throws LifecycleException Start error
*/
public void start() throws LifecycleException {
//创建 tomcat StandardServer, 初始化基础目录,
// 创建 tomcat StandardService并关联到 tomcat StandardServer。
getServer();
// 获取 tomcat StandardService上是否已经绑定Connector,如果没有,
// 创建一个支持 HTTP/1.1的Connector,设置到执行端口,然后将该Connector
// 绑定到 tomcat StandardService。
getConnector();
// 启动 tomcat StandardServer。这里会对相应的 server,service,engine,
// 分别进行初始化和启动,也就是调用他们的init()方法和 start()方法。
// 而 connector只执行了初始化 init(),然后被从 service 中删掉。
// 该被删除的 connector 随后会被添加回来,然后再调用其启动start()方法。
server.start();
}
/**
* Get the server object. You can add listeners and few more
* customizations. JNDI is disabled by default.
* @return The Server
*/
public Server getServer() {
if (server != null) {
return server;
}
System.setProperty("catalina.useNaming", "false");
server = new StandardServer();
initBaseDir();
server.setPort( -1 );
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
/**
* Get the default http connector. You can set more
* parameters - the port is already initialized.
*
* <p>
* Alternatively, you can construct a Connector and set any params,
* then call addConnector(Connector)
*
* @return A connector object that can be customized
*/
public Connector getConnector() {
Service service = getService();
if (service.findConnectors().length > 0) {
return service.findConnectors()[0];
}
if (defaultConnectorCreated) {
return null;
}
// The same as in standard Tomcat configuration.
// This creates an APR HTTP connector if AprLifecycleListener has been
// configured (created) and Tomcat Native library is available.
// Otherwise it creates a NIO HTTP connector.
Connector connector = new Connector("HTTP/1.1");
connector.setPort(port);
service.addConnector(connector);
defaultConnectorCreated = true;
return connector;
}
Tomcat StandardContxt启动过程中SCI的应用
上面过程创建的StandardContext实例,也就是所对应的webapp,已经关联但是尚未应用spring提供的SCI。这些SCI的应用是在StandardContext.startInternal()中应用SCI阶段进行的。
// 调用 ServletContainerInitializer(备注 : 缩写为SCI)
// 这里的SCI接口是Servlet API标准SCI接口,而不是Spring定义的SCI接口。上面的流程分析中已经讲到,
// prepareContext()过程中已经将Spring准备的多个Spring SCI封装成一个Servlet API规范SCI
// 实现TomcatStarter关联到了tomcat容器,这里的initializers 就是这些封装后的Servlet API
// SCI实例。在使用缺省配置的Springboot Web应用中,以下for循环中的initializers其实只有一个,
// 就是前面提到的Spring提供的实现了Servlet API标准SCI接口TomcatStarter。
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
// 调用SCI的onStartup()方法,getServletContext()是基于当前StandardContext创建
// 的ServletContext的facade
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
这里对Tomcat标准SCI TomcatStarter 的调用,最终还是调用了Spring提供的3个SCI :
- EmbeddedWebApplicationContext匿名内部类,封装的逻辑来自方法 selfInitialize()
- ServerProperties$SessionConfiguringInitializer
- InitParameterConfiguringServletContextInitializer
EmbeddedWebApplicationContext的selfInitialize
/**
* servletContext是一个Java Servlet规范里面的概念,这里其实现由Tomcat提供
*
**/
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 1.将当前spring application context作为属性设置到servletContext :
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
// 随后应用启动完在提供Web服务的时候,Spring MVC dispatchServlet的应用上下文
// 的双亲就会使用这个根Web应用上下文
// 2.将servletContext记录到当前web application context,也就是当前
// EmbeddedWebApplicationContext对象的属性servletContext中
prepareEmbeddedWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 获取用户自定义webapp作用域
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
//注册标准webapp作用域
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
// 重新注册用户自定义webapp作用域
existingScopes.restore();
// 注册webapp相关环境参数bean : contextParameters,contextAttributes
// WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME
// WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME
// WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
// !!! 到目前为止,尚未初始化webapp中的Servlet,Filter 和 EventListener
// 从Bean容器中找到所有的SCI bean,并调用其 onStartup()回调方法
// getServletContextInitializerBeans()会从bean容器中找到所有的SCI bean,
// 将bean容器中所有Servlet, Filter 和EventListener bean转换成SCI 然后返回
// 给该for循环然后逐一调用其 onStartup() 回调。
// !!! 这里是真正的Servlet, Filter 和EventListener注入到ServletContext的触发点
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
// 到此为止,一个完整的符合开发者设计目的的WebApp才算是被启动和被完整设置
}
/**
* Returns ServletContextInitializers that should be used with the embedded
* Servlet context. By default this method will first attempt to find
* ServletContextInitializer, Servlet, Filter and certain
* EventListener beans.
*
* 返回所有需要被应用到Servlet context上的ServletContextInitializer。
*
* 缺省情况下:
* 1.该方法会首先尝试bean容器中注册的 ServletContextInitializer bean,
* 2.然后是各种适配bean,比如 Servlet, Filter 和EventListener bean,
* 将它们封装成实现了接口 ServletContextInitializer 的 RegistrationBean 。
* 3.最后将上面所有找到的 ServletContextInitializer bean 和封装的 RegistrationBean
* 返回给调用者。
*
*
* @return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
/**
* Prepare the WebApplicationContext with the given fully loaded
* ServletContext. This method is usually called from
* ServletContextInitializer#onStartup(ServletContext) and is similar to the
* functionality usually provided by a ContextLoaderListener.
* @param servletContext the operational servlet context
*/
protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) {
// 从Servlet Context上面读取属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,根Web应用上下文
Object rootContext = servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (rootContext != null) {
// 如果在 Servlet Context上面已经设置了属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
// 这块逻辑表明根Web应用上下文只能初始化一次并绑定到Servlet Context的这个属性上
if (rootContext == this) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
return;
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
// 将this对象,也就是当前EmbeddedWebApplicationContext Web应用上下文对象设置为
// 当前ServletContext上下文的属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
// 随后应用启动完在提供Web服务的时候,Spring MVC dispatchServlet的应用上下文
// 的双亲就会使用这个根Web应用上下文
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug(
"Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
+ "]");
}
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in "
+ elapsedTime + " ms");
}
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
缺省spring boot web应用中ServletContextInitializerBeans从beanFactory所获得的SCI有 :
| Bean | 类型 |
|---|---|
| dispatcherServlet | ServletRegistrationBean |
| characterEncodingFilter | FilterRegistrationBean |
| hiddenHttpMethodFilter | FilterRegistrationBean |
| httpPutFormContentFilter | FilterRegistrationBean |
| requestContextFilter | FilterRegistrationBean |
想了解以上各个bean都是什么用途,请参考 :
缺省配置Springboot Web应用启动过程中Bean定义的登记
ServerProperties内部类SessionConfiguringInitializer
设置SessionCookieConfig
InitParameterConfiguringServletContextInitializer
将init参数设置到ServletContext上的一个SCI
内置Tomcat servlet容器的启动
在整个上面的启动过程中,虽然调用 Tomcat 实例的启动方法,但是整个容器tomcatEmbeddedServletContainer只能算是启动了一部分,也就是其中除了Tomcat Connector 之外的其他部分。
此时tomcatEmbeddedServletContainer实例的属性 started仍然为false,表示整个容器处于尚未启动的状态。所以上的逻辑更像是容器创建和初始化的过程。
Tomcat Connector是用来接收来自网络的请求的关键组件。这一部分组件启动之后,整个容器tomcatEmbeddedServletContainer才能接受和处理来自网络的请求从而为用户提供服务,所以所有这些的启动都结束,才能算是整个容器的启动完成,此时started属性才能设置为true。
该小节容器的启动,主要就是讲容器中Tomcat Connector的启动。
启动入口点
SpringApplication.run()
=>refresh(ApplicationContext applicationContext)
=>EmbeddedWebApplicationContext.refresh()
=>EmbeddedWebApplicationContext.finishRefresh()
=>startEmbeddedServletContainer()
启动入口点代码分析
// 类EmbeddedWebApplicationContext的方法finishRefresh()
@Override
protected void finishRefresh() {
//先调用父类AbstractApplicationContext的finishRefresh()
super.finishRefresh();
// 启动内置Servlet容器
EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
if (localContainer != null) {
// 容器已经启动,可以接受来自外部的HTTP请求了,发布事件 :
// EmbeddedServletContainerInitializedEvent
publishEvent(
new EmbeddedServletContainerInitializedEvent(this, localContainer));
}
}
// 类EmbeddedWebApplicationContext的方法startEmbeddedServletContainer()
private EmbeddedServletContainer startEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if (localContainer != null) {
// 调用内置servlet容器TomcatEmbeddedServletContainer实例上的start()方法
localContainer.start();
}
return localContainer;
}
内置servlet容器的启动过程
// TomcatEmbeddedServletContainer 的start方法
@Override
public void start() throws EmbeddedServletContainerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
// 将内置容器创建和初始化阶段删除的Connector再添加到容器,将Connector添加回容器(实际上是添加到容器的Service),
// 因为相应的Service已经处于启动状态,所以Connector在添加回来之后马上会被启动
addPreviouslyRemovedConnectors();
// 获得tomcat的Connector,如果不为空并且设置为自动启动,则启动之。缺省配置下,
// 这里 autoStart 为 true
// 连接器 Connector 主要是接收用户的请求,然后封装请求传递给容器处理,tomcat中默认的连接器是Coyote。
// 连接器表示Tomcat将会在哪个端口使用哪种协议提供服务。
// 在配置文件中,我们经常会见到这样的配置 :
// <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
// redirectPort="8443" URIEncoding="utf-8"/>
// <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
startConnector(connector);
}
// 检查确保Connector已经启动,如果没启动,抛出异常
checkThatConnectorsHaveStarted();
this.started = true;
TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true));
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat servlet container", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
}
}
// TomcatEmbeddedServletContainer 的 addPreviouslyRemovedConnectors 方法
private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
// 此时service已经处于启动状态,因此重新添加进来的connector
// 也没马上被执行启动动作
service.addConnector(connector);
if (!this.autoStart) {
stopProtocolHandler(connector);
}
}
this.serviceConnectors.remove(service);
}
}
}
Tomcat Connector的启动
启动入口点
Service.addConnector()
=> Connector.start()
启动入口点逻辑分析
//StandardService 的方法 addConnector
//StandardService 是tomcat提供的类
@Override
public void addConnector(Connector connector) {
synchronized (connectorsLock) {
// 将connector关联到当前 service
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
// 如果当前服务的状态是 available,则在将 connector 增加到service时
// 直接启动 connector
if (getState().isAvailable()) {
try {
// 启动新增进来的 connector
connector.start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
}
Connector启动过程分析
// Connector是tomcat提供的类
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
// Connector启动的核心动作是协议处理器的启动
// 对于缺省配置的springboot web应用,它会在8080端口提供 HTTP 服务,
// 所以这里是一个处理http协议请求的 Http11NioProtocol 实例,使用 nio 方式处理 http 协议,
// Connector 对HTTP请求的接收和处理并不是亲自完成的,而是交给该 Http11NioProtocol
// protocolHandler 完成,而 protocolHandler 又进一步将请求处理工作交给 NioEndpoint 完成。
protocolHandler.start();
} catch (Exception e) {
String errPrefix = "";
if(this.service != null) {
errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
}
throw new LifecycleException
(errPrefix + " " + sm.getString
("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
Http11NioProtocol启动过程分析
// 调用链 :
// Connector.start()
// => startInternal()
// => Http11NioProtocol protocolHandler.start();
//
// Http11NioProtocol 的 start方法,由基类 AbstractProtocol 提供实现
// Http11NioProtocol ,AbstractProtocol 都是tomcat提供的类
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.start",
getName()));
try {
// 启动了成员变量endpoint,一个 NioEndpoint 实例
// Http11NioProtocol 类实例自身并不最终处理请求,具体这些请求的处理
// 都是交给了 NioEndpint endpoint 来完成,这里启动该 endpoint。
endpoint.start();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.startError",
getName()), ex);
throw ex;
}
// Start async timeout thread
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
// 调用链 :
// Connector.start()
// => startInternal()
// => Http11NioProtocol protocolHandler.start();
// => NioEndpoint endpoint.start()
//
// NioEndpoint的start()方法,在其基类AbstractEndpoint中实现
// NioEndpoint,AbstractEndpoint都是tomcat提供的类
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
// 如果端口绑定状态为未绑定,这里执行端口绑定逻辑
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
//AbstractEndpoint的bind()方法实现
@Override
public void bind() throws Exception {
// 建立服务套接字,并绑定到指定的端口,
// 缺省配置的spring-boot web应用,这里的端口是 8080
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getAcceptCount());
// 设置 serverSock 为阻塞模式
serverSock.configureBlocking(true); //mimic APR behavior
// Initialize thread count defaults for acceptor, poller
// 初始化 acceptoer thread 的数量,默认为1
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
// 设置 pooler thread 的个数 pollerThreadCount
// 注意变量 pollerThreadCount 在定义时已经默认初始化为2和CPU核数量二者的最小值,
// 单核CPU的话pollerThreadCount默认初始值为1,多核CPU的话pollerThreadCount默认初始值总是为2
// private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
// 这里只是做一下检查,确保其最少为1
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
// 如果配置了SSL的话,对其进行初始化
initialiseSsl();
selectorPool.open();
}
//AbstractEndpoint的startInternal()方法实现
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
// 设置运行状态
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
// NioChannel 实现了 NIO ByteChannel 接口
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
// 创建工作线程池,Tomcat自己封装了一个 ThreadPoolExecutor
if ( getExecutor() == null ) {
// 如果尚未指定 executor, 则创建内部的任务执行线程
// 缺省配置的 spring boot web 应用没有外部指定的 executor
// 创建的线程名称前缀为 http-nio-8080-exec-
createExecutor();
}
// 创建一个LimitLatch,用于控制最大连接数,缺省值10000
initializeConnectionLatch();
// 创建和启动 Poller 线程
// 1. Poller类是一个Runnable.每个Poller对象会保持一个Java NIO Selector
// 和一个PollerEvent队列SynchronizedQueue.
// 2. Poller 线程数量会使用当前计算机CPU processor数量和2的最小值,
// 多核CPU的话 pollerThreadCount默认为2,单核CPU的话默认为1
// 3. 每个Poller线程都是 daemon 线程
// 4. Poller线程名称前缀 : http-nio-8080-ClientPoller-
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 创建和启动请求接收线程
// 1. 接收线程缺省只有1个
// 2. 接收线程名称前缀 : http-nio-8080-Acceptor-
startAcceptorThreads();
}
}
//AbstractEndpoint的createExecutor()方法实现
public void createExecutor() {
internalExecutor = true;
// 创建基于ThreadPoolExecutor的任务执行队列
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
// 创建的线程名称前缀为 http-nio-8080-exec-
// corePoolSize:10
// maximunPoolSize:200
// keepAliveTime : 60s
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
//AbstractEndpoint的startAcceptorThreads()方法实现
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount(); // 上面已经分析过,这里的数量初始化为1
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
// 线程名称 : http-nio-8080-Acceptor-0
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
// NioEndpoint 类的重写实现,创建一个AbstractEndpoint.Acceptor,实现了 Runnable接口
// 这里的类 Acceptor 是一个 NioEndpoint 内部类,用于接收套接字并交给合适的处理器
@Override
protected AbstractEndpoint.Acceptor createAcceptor() {
return new Acceptor();
}

本文详细解析SpringBoot应用中Tomcat服务器的启动流程,包括TomcatEmbeddedServletContainerFactory的作用、TomcatEmbeddedServletContainer的创建与初始化过程,以及TomcatConnector启动的关键步骤。
1241

被折叠的 条评论
为什么被折叠?



