前言
Spring Boot 版本 2.1.7.RELEASE
本文大致跟踪了一遍Tomcat的启动流程和http请求处理流程。
请边debug源代码边看本文,因为很多变量细节在debug时才能清晰的观察到,而本文是无法全部涵盖所有细节的。
文中如有错误遗漏,请留言指正。
相关文章
正文
1 第一部分的调用栈如下
createWebServer:180, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
onRefresh:153, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:543, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx) //自定义的函数入口
这一部分主要工作是:
- 实例化
org.apache.catalina.startup.Tomcat
- 将
Tomcat
实例作为构造函数的入参,实例化org.springframework.boot.web.embedded.tomcat.TomcatWebServer
- 在
TomcatWebServer
的构造函数执行的末尾处,调用Tomcat.start()方法
启动Tomcat
大致流程如下:
1.1
首先在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer()
方法中,
ServletWebServerFactory factory = getWebServerFactory();
语句通过beanFactory构造出TomcatWebServer
的factory实例。
1.2
然后this.webServer = factory.getWebServer(getSelfInitializer());
语句通过TomcatWebServer
的factory构造TomcatWebServer
实例。Tomcat
的实例化及启动流程就在此函数中完成。
代码实现在org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
类的getWebServer()
方法中。
1.2.1
getWebServer()
方法的前半部分都是在实例化Tomcat
类。其中包括实例化Connector
,StandardServer
,StandardService
,StandardEngine
等类,最终组合成Tomcat
类。
1.2.2
getWebServer()
方法的最后一条语句getTomcatWebServer(tomcat);
,将Tomcat
实例作为构造函数的参数,构造了TomcatWebServer
。而在TomcatWebServer
的构造函数的末尾处,initialize();
方法会启动Tomcat
。
initialize();
方法主要做了两件事
-
this.tomcat.start();
启动tomcat -
startDaemonAwaitThread();
启动一条非daemon线程来执行TomcatWebServer.this.tomcat.getServer().await();
用途如源代码中的注释所写。// Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown
先来看一下this.tomcat.start();
tomcat.start();
内部会初始化并启动Server
,Service
,Engine
,Connector
,Http11NioProtocol
,NioEndpoint
等
这个过程中会多次发布Lifecycle
中的事件,但默认只有StandardContext
的lifecycleListeners
中有事件监听者。
StandardContext.startInternal()
方法中会调用initializers
的onStartup()
方法,其中包括了ServletWebServerApplicationContext.selfInitialize()
方法。
1.2.3 selfInitialize()
方法
1.2.3.1
selfInitialize()
方法首先将ServletWebServerApplicationContext
以attribute的方式注入到当前的ServletContext
中。
ServletWebServerApplicationContext
功能齐全,可以获取bean,可以获取environment,进而获取property。
所以一种通过静态方法获取ApplicationContext
的方式是
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
ServletContext servletContext = servletRequestAttributes.getRequest().getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
}
注
有时候会需要在自定义的Filter中注入Spring维护的bean。如果是使用当前这种通过springboot的方式启动服务,直接通过注解注入是没有问题的。
但如果是通过tomcat方式启动服务,Filter初始化的时候是没有在Spring的上下文中的。这时候是无法通过注解注入Spring bean依赖的,可以尝试使用DelegatingFilterProxy
这个类来实现。
1.2.3.2
接下来会调动ServletContextInitializerBeans
类的构造函数。
在这个构造函数中加载了Servlet
和Filter
。
加载的方式有两种
a) addServletContextInitializerBeans
方法
从beanFactory中加载ServletContextInitializer
接口的类。FilterRegistrationBean
等类就实现了这一接口,这种filter的声明方式会在这个阶段被加载进来。
b) addAdaptableBeans()
方法
从beanFactory中加载Servlet
和Filter
接口的类。
代码如下
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
代码中的adapter会将Servlet
和Filter
封装成RegistrationBean
,这样就与a方式相同了。
2 第二部分的调用栈如下
这一部分主要工作是:调用TomcatWebServer.start()方法启动TomcatWebServer
start:195, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
startWebServer:297, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
finishRefresh:163, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:552, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:743, SpringApplication (org.springframework.boot)
refreshContext:390, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1214, SpringApplication (org.springframework.boot)
run:1203, SpringApplication (org.springframework.boot)
main:13, XXXXXApplication (com.xxx.xxx) //自定义的函数入口
大致流程如下:
2.1 addPreviouslyRemovedConnectors();
在 1.2.2 的TomcatWebServer.initialize()
方法中有这样一段代码:
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
这段代码的意思是在StandardContext
中添加Lifecycle
事件监听器,监听Lifecycle.START_EVENT
事件。当接收到此事件时,执行removeServiceConnectors();
,执行的目的如注释所示。
因此,本节的addPreviouslyRemovedConnectors();
的目的就是把之前移除的connecters再添加回来。
2.1.1 addPreviouslyRemovedConnectors();
方法中使用service.addConnector(connector);
方法添加connector。
这一方法曾经在 1.2.1 的TomcatServletWebServerFactory.getWebServer()
方法中被调用过一次,调用处的语句为tomcat.getService().addConnector(connector);
。
不过在TomcatServletWebServerFactory.getWebServer()
方法中被调用时,由于getState().isAvailable()
== false,没能继续执行后半部分,此时StandardService.state
== LifecycleState.NEW
;而在本节的addPreviouslyRemovedConnectors();
方法中调用时,StandardService.state
== LifecycleState.STARTED
,可以继续执行connector.start();
2.1.2 connector.start();
方法中会连续调用,Connector.startInternal()
,Http11NioProtocol.start()
,NioEndpoint.start()
。而主要工作在NioEndpoint.start()
中执行,这里面包含了两个重要语句
bindWithCleanup();
startInternal();
2.1.2.1 NioEndpoint.bindWithCleanup()
- 创建并配置
ServerSocketChannel
- Initialize SSL if needed
-
selectorPool.open(getName());
NioSelectorPool.sharedSelector = Selector.open();
NioSelectorPool.blockingSelector = new NioBlockingSelector();
NioSelectorPool.blockingSelector.poller = new BlockPoller();
- 启动"BlockPoller"线程,执行
org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run()
2.1.2.2 NioEndpoint.startInternal()
创建executor
启动"ClientPoller"线程,执行org.apache.tomcat.util.net.NioEndpoint.Poller.run()
启动"Acceptor"线程,执行org.apache.tomcat.util.net.Acceptor.run()
2.1.2 续
至此,启动了一些线程总结如下
线程名 | 执行方法 |
---|---|
http-nio-8080-Acceptor | org.apache.tomcat.util.net.Acceptor.run() |
http-nio-8080-BlockPoller | org.apache.tomcat.util.net.NioBlockingSelector.BlockPoller.run() |
http-nio-8080-ClientPoller | org.apache.tomcat.util.net.NioEndpoint.Poller.run() |
http-nio-8080-exec-1 | org.apache.tomcat.util.threads.ThreadPoolExecutor (这是executor的worker线程,没有具体的执行任务) |
http-nio-8080-exec-2 | 同上,共有10条线程 |
2.2 performDeferredLoadOnStartup();
这里会调用javax.servlet.GenericServlet.init()
方法,对org.apache.catalina.servlets.DefaultServlet
进行初始化。
而org.springframework.web.servlet.DispatcherServlet
是延迟初始化的,它会在将来有网络请求到来时才调用init()
。
3 tomcat处理网络请求
3.1 首先总结一下基本信息
3.1.1 线程信息如 2.1.2 续 所述
3.1.2 类信息摘要
其中
NioEndpoint.poller != NioBlockingSelector.poller
NioSelectorPool.sharedSelector == NioBlockingSelector.sharedSelector == BlockPoller.selector (图中蓝色框的Selector为同一个对象)
NioEndpoint$Poller.selector != NioSelectorPool.sharedSelector (图中红色框的Selector与蓝色框的Selector不是同一个对象)
3.2 Accept线程
3.2.1
"Acceptor"线程中的org.apache.tomcat.util.net.Acceptor.run()
方法在执行的时候,会阻塞在socket = endpoint.serverSocketAccept();
语句处。
当接受到新的网络请求时,此语句解除阻塞,socket
得到赋值,继续执行。
3.2.2
socket
获得赋值后,会来到如下代码段
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
其中endpoint.setSocketOptions(socket)
语句内内部,会将socket
封装成NioChannel
类,NioChannel
类又被进一步封装成NioSocketWrapper
类。
不过NioChannel
类和NioSocketWrapper
类二者内部都有着对方的引用。代码摘要如下
NioChannel channel = new NioChannel(socket, bufhandler);
NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);
channel.setSocketWrapper(socketWrapper);
接下来org.apache.tomcat.util.net.NioEndpoint.Poller.register()
方法将其注册到Poller
中。
Poller.register()
方法中又将NioChannel
进一步封装成PollerEvent
,然后添加到Poller.events
队列中,队列的类型是SynchronizedQueue<PollerEvent> events
。
3.3 ClientPoller线程
此线程主体是一个while(true)轮询逻辑。
3.3.1
首先调用events();
函数处理events
队列中的PollerEvent
,当 3.2.2 的末尾处有新PollerEvent
加入队列后,events();
方法内部会对其处理,过程如下
3.3.1.1
PollerEvent.run()
方法内部,3.2.2 末尾处注册时的赋值,致使interestOps == OP_REGISTER
,进而执行SocketChannel.register()
方法,将这个socket
注册到Poller.selector
上,监听SelectionKey.OP_READ
,attachment是NioSocketWrapper
。
3.3.1.2
PollerEvent.reset()
方法重置PollerEvent
对象。
eventCache.push(pe);
将重置后的PollerEvent
对象放入cache,方便以后重用。
3.3.2
events();
调用完成后,会调用selector.selectedKeys().iterator()
。3.3.1.1 中注册到selector
中的socket
会被select出来。processKey(sk, socketWrapper);
方法会对这个socket
做进一步处理,其中socketWrapper
是当时注册时的attachment。
processKey(sk, socketWrapper);
方法内部会将socketWrapper
封装成NioEndpoint.SocketProcessor
类,交给NioEndpointEndpoint.executor
去执行,也就是交给worker线程了。
3.4 worker线程
3.4.1
接 3.3.2 的末尾,worker线程接到新的任务后,执行的是SocketProcessor.doRun()
方法。
其中关键语句是state = getHandler().process(socketWrapper, event);
,即ConnectionHandler.process()
方法,这个方法超长。
这个方法内部处理语句是state = processor.process(wrapper, status);
,即Http11Processor.process()
。然后是层层调用如下,直到执行filter。
doFilter:166, ApplicationFilterChain (org.apache.catalina.core) [1]
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:490, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:408, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:853, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1587, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1130, ThreadPoolExecutor (java.util.concurrent)
run:630, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:832, Thread (java.lang)
在org.apache.catalina.core.StandardWrapperValve.invoke()
方法中,
servlet = wrapper.allocate();
获取DispatcherServlet
;
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
获取filterChain。
org.apache.catalina.core.StandardContext.filterMaps
中记录了所有filter。
filterChain.doFilter(request.getRequest(), response.getResponse());
执行filter。
3.4.2 filter的执行
ApplicationFilterChain.doFilter()
-> ApplicationFilterChain.internalDoFilter()
-> if (pos < n)
-> Filter.doFilter()
-> ApplicationFilterChain.doFilter()
上述是链式的执行所有filter。
当filter全部执行完毕后,if (pos < n)
将为false,继续执行ApplicationFilterChain.internalDoFilter()
的后半部分。
最终执行到servlet.service(request, response);
,servlet
是DispatcherServlet
。
3.4.3 DispatcherServlet.doDispatch()
DispatcherServlet.getHandler()
会根据请求url寻找对应controller。
AbstractHandlerMapping.getHandlerExecutionChain()
会添加intercepter。
最终返回HandlerExecutionChain
。
HandlerExecutionChain
的执行顺序如下:
-
mappedHandler.applyPreHandle()
调用HandlerInterceptor.preHandle()
。 -
ha.handle()
调用controller内的执行方法,这个方法是被@Aspect修饰过的。 -
mappedHandler.applyPostHandle()
调用HandlerInterceptor.postHandle()
。 -
mappedHandler.triggerAfterCompletion()
调用HandlerInterceptor.afterCompletion()
。
3.4.3 ha.handle()
中会调用ServletInvocableHandlerMethod.invokeAndHandle()
方法。
该方法中的Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
会最终调用controller的方法,并获得返回值。
然后this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
会将controller的返回值转化成response body,并分片发送response。
分片发送依次调用
CoyoteOutputStream.flush()
org.apache.coyote.Response.action()
actionCode == ActionCode.CLIENT_FLUSH
Http11Processor.action()
NioEndpoint.NioSocketWrapper.doWrite()
NioSelectorPool.write()
NioBlockingSelector.write()
然后再经过层层filter以及其他操作,最终回到了org.apache.catalina.connector.CoyoteAdapter.service()
方法,该方法中会执行以下语句
request.finishRequest();
response.finishResponse();
org.apache.catalina.connector.Response.finishResponse()
中会调用coyoteResponse.action(ActionCode.CLOSE, null);
-> org.apache.coyote.http11.Http11Processor.finishResponse()
发送结束分片。http response至此结束。
4 Tomcat 线程池
Tomcat基于jdk中提供的线程池,实现了自己的线程池。
4.1
首先梳理一下jdk中的线程池运行方式。
java.util.concurrent.ThreadPoolExecutor
的构造方式中有3个重要参数 corePoolSize
, maximumPoolSize
, workQueue
。
我们以java.util.concurrent.LinkedBlockingQueue
类作为workQueue
举例说明。
此线程池构造完毕后,线程池内初始有 0 条线程。然后开始提交任务。
如果 当前线程数 < corePoolSize
,建立一条新线程执行任务。
如果 当前线程数 == corePoolSize
,将任务放入workQueue
队列等待执行。
如果 当前线程数 == corePoolSize
且 workQueue
满了,建立一条新线程执行任务。
如果 workQueue
满了 且 当前线程数 == maximumPoolSize
,执行reject()
方法,拒绝新任务,拒绝新任务的处理方式默认是抛出RejectedExecutionException
异常。
4.2
Tomcat的线程池是在org.apache.tomcat.util.net.AbstractEndpoint.createExecutor()
方法中执行创建的。
org.apache.tomcat.util.threads.ThreadPoolExecutor
类继承了java.util.concurrent.ThreadPoolExecutor
。
Tomcat 使用的队列类为 org.apache.tomcat.util.threads.TaskQueue
,该类继承了java.util.concurrent.LinkedBlockingQueue<Runnable>
,且capacity == Integer.MAX_VALUE
,即队列承载力极大。
他们组合使用构成的线程池的运行方式如下:
此线程池构造完毕后,线程池内初始有 corePoolSize
条线程。然后开始提交任务。
由于初始化时就建立了一些线程,所以最初提交的几个任务可以直接被执行。
如果 当前线程数 == corePoolSize
且 没有空闲线程,建立一条新线程执行任务。
如果 当前线程数 == maximumPoolSize
,将任务放入workQueue
队列等待执行。
如果 workQueue
满了 且 当前线程数 == maximumPoolSize
,执行reject()
方法,拒绝新任务,拒绝新任务的处理方式默认是抛出RejectedExecutionException
异常。
4.3
对比上述二者的执行方式,可知其间的差异。
jdk自带的线程池,初始化时不会启动新线程,当有任务提交时才会启动线程。当活跃线程达到corePoolSize
时,会优先将任务放入队列等待,当等待队列满时,才会再次新增线程数量,直到达到maximumPoolSize
。
而Tomcat使用的线程池,初始化时就会启动corePoolSize
条线程。如果线程都在忙于处理任务时,再提交新任务,会继续创建新线程,直到达到maximumPoolSize
条线程。如果线程都在忙于处理任务,且线程数量达到了上限时,再提交新任务,才会将其放入等待队列。
可见,二者的显著区分处有三点。
- jdk线程池初始化时不会启动任何线程,第一个任务提交时才会创建线程。
而Tomcat线程池初始化就会启动corePoolSize
条线程。 - 在活跃线程达到
corePoolSize
,且所有线程都在忙碌中(即都在执行任务)时,再次提交新任务时,二者对新任务的处理方式的优先级不同。
jdk线程池会优先将新任务放入任务队列中等待执行,当任务队列满了才会再次新建线程。
而Tomcat线程池会优先创建线程执行任务,但线程数量达到上限且都在忙碌执行时,才会将任务放入等待队列中。 - jdk线程池中的任务队列可自由使用任意类。
而Tomcat线程池需要搭配使用TaskQueue
类,该类的capacity == Integer.MAX_VALUE
。
所以Tomcat的线程池的策略是尽可能用线程立即执行到来的新任务,达到线程数量上限才会让任务排队等待。Tomcat的任务队列的承载能力又是极大的,capacity == Integer.MAX_VALUE
。
4.4
tomcat线程池创建的调用栈
<init>:77, ThreadPoolExecutor (org.apache.tomcat.util.threads)
createExecutor:987, AbstractEndpoint (org.apache.tomcat.util.net)
startInternal:331, NioEndpoint (org.apache.tomcat.util.net)
start:1293, AbstractEndpoint (org.apache.tomcat.util.net)
start:614, AbstractProtocol (org.apache.coyote)
startInternal:1072, Connector (org.apache.catalina.connector)
start:183, LifecycleBase (org.apache.catalina.util)
addConnector:239, StandardService (org.apache.catalina.core)
addPreviouslyRemovedConnectors:282, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:213, TomcatWebServer (org.springframework.boot.web.embedded.tomcat)
start:43, WebServerStartStopLifecycle (org.springframework.boot.web.servlet.context)
doStart:182, DefaultLifecycleProcessor (org.springframework.context.support)
access$200:53, DefaultLifecycleProcessor (org.springframework.context.support)
start:360, DefaultLifecycleProcessor$LifecycleGroup (org.springframework.context.support)
startBeans:158, DefaultLifecycleProcessor (org.springframework.context.support)
onRefresh:122, DefaultLifecycleProcessor (org.springframework.context.support)
finishRefresh:895, AbstractApplicationContext (org.springframework.context.support)
refresh:554, AbstractApplicationContext (org.springframework.context.support)
refresh:143, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:755, SpringApplication (org.springframework.boot)
refresh:747, SpringApplication (org.springframework.boot)
refreshContext:402, SpringApplication (org.springframework.boot)
run:312, SpringApplication (org.springframework.boot)
run:1247, SpringApplication (org.springframework.boot)
run:1236, SpringApplication (org.springframework.boot)
main:18, Application (com.jindi.search.platform)
5 其他
5.1 http status code
在HttpServletResponse
类中,定义了多个http状态码的常量,可以用来反向查找,查看在哪些情况下会返回哪种状态码。