缺省配置Springboot Web应用中tomcat的启动过程
概述独立部署的tomcat服务器的启动过程传统意义上一个独立部署和运行的tomcat服务器的启动可以理解成两个阶段 :tomcat 容器本身的启动;tomcat容器中所部署的web app的启动;完成了以上两个阶段,我们才能访问到我们所开发的业务逻辑。在这种情况下,web app的部署动作,通常是由系统部署人员通过某种方式在启动服务器前完成的。spring bo
文章目录
- 概述
- `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();
}
参考文章
更多推荐
所有评论(0)