2.Tomcat启动阶段
- daemon.start(),tomcat的启动阶段分析
1 | java复制代码# 1.Bootstrap的start()方法 |
- getServer().start(),方法启动Server,源码分析
1 | java复制代码# 1.LifecycleBase的start()方法 |
Server的初始化start()方法,会先调用父类LifecycleBase的start()方法,然后再调用子类Server的startInternal()方法
这个调用的模式:父类start—>子类startInternal,在后面的services,connector,engine初始化是一样的模式。
- services[i].start(),启动services,源码分析
1 | java复制代码# 1.standardService的startInternal()方法 |
从services的启动分析,可以得到services的启动,会启动engine和connector。(executor、mapperListener)
- connector.start(),启动Connector,源码分析
1 | java复制代码# 1.Connector的startInternal()方法 |
从connector的启动分析,可以得到connector的启动,会启动protocolHandler和endpoint
- engine.start(),启动engine,源码分析
1 | java复制代码# 1.StandardEngine的startInternal()方法 |
从standardEngine的启动过程,可以看出,主要是启动standardHost。
然后通过监听器技术,启动HostConfig,进而解析webapps,启动context。
HostConfig的deployDirectory,主要做了几个工作:
1.使用 digester,或者反射实例化 StandardContext
2.实例化ContextConfig,并且为Context 容器注册事件监听器,和 StandardHost 的套路一样,借助 XXXConfig 完成容器的启动、停止工作
3. 将当前 Context 实例作为子容器添加到Host 容器中,添加子容器的逻辑在 ContainerBase 中已经实现了,如果当前 Container 的状态是 STARTING_PREP 并且 startChildren 为 true,则还会启动子容器
- StandardContext的startInternal()方法分析
1 | java复制代码# 1.StandardContext的startInternal()方法 |
StandardContext 和其他 Container 一样,也是重写了 startInternal 方法。由于涉及到 webapp 的启动流程,需要很多准备工作,比如使用 WebResourceRoot 加载资源文件、利用 Loader 加载 class、使用 JarScanner 扫描 jar 包,等等。
因此StandardContext 的启动逻辑比较复杂,这里描述下几个重要的步骤:
- 创建工作目录,比如$CATALINA_HOME\work\Catalina\localhost\examples;实例化 ContextServlet,应用程序拿到的是 ApplicationContext的外观模式
- 实例化 WebResourceRoot,默认实现类是 StandardRoot,用于读取 webapp 的文件资源
- 实例化 Loader 对象,Loader 是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class
- 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是从 webapp 中读取 servlet 相关的 Listener、Servlet、Filter 等
- 实例化 Sesssion 管理器,默认使用 StandardManager
- 调用 listenerStart,实例化 servlet 相关的各种 Listener,并且调用
ServletContextListener- 处理 Filter
- 加载 Servlet
- 触发 CONFIGURE_START_EVENT 事件,触发ContextConfig监听器
ContextConfig 它是一个 LifycycleListener,它在 Context 启动过程中是承担了一个非常重要的角色。StandardContext 会发出 CONFIGURE_START_EVENT 事件,而 ContextConfig 会处理该事件,主要目的是通过 web.xml 或者 Servlet3.0 的注解配置,读取 Servlet 相关的配置信息,比如 Filter、Servlet、Listener 等,其核心逻辑在 ContextConfig#webConfig() 方法中实现。
ContextConfig执行的重要步骤:
- 是通过 WebXmlParser 对 web.xml 进行解析,如果存在 web.xml 文件,则会把文件中定义的 Servlet、Filter、Listener 注册到 WebXml 实例中
- 如果没有 web.xml 文件,tomcat 会先扫描 WEB-INF/classes 目录下面的 class 文件,然后扫描 WEB-INF/lib 目录下面的 jar 包,解析字节码读取 servlet 相关的注解配置类,这里不得不吐槽下 serlvet3.0 注解,对 servlet 注解的处理相当重量级。tomcat 不会预先把该 class 加载到 jvm 中,而是通过解析字节码文件,获取对应类的一些信息,比如注解、实现的接口等
- Step 9:往 Context 中添加子容器 Wrapper
1 | java复制代码# 1.context添加wrapper子节点 |
- 启动 StandardWrapper容器
1 | java复制代码# 1.StandardWrapper的startInternal()方法 |
StandardWrapper 没有子容器,启动逻辑相对比较简单清晰,它重写了 startInternal 方法,主要是完成了 jmx 的事件通知,先后向 jmx 发出 starting、running 事件
由前面对 Context 容器的分析可知,Context 完成 Filter 初始化之后,如果 loadOnStartup >= 0 便会调用 load 方法加载 Wrapper 容器。StandardWrapper 使用 InstanceManager 实例化 Servlet,并且调用 Servlet 的 init 方法进行初始化,传入的 ServletConfig 是 StandardWrapperFacade 对象。
总结:
tomcat 实现了 javax.servlet.ServletContext 接口,在 Context 启动的时候会实例化该对象。由 Context 容器通过 web.xml 或者 扫描 class 字节码读取 servlet3.0 的注解配置,从而加载 webapp 定义的 Listener、Servlet、Filter 等 servlet 组件,但是并不会立即实例化对象。全部加载完毕之后,依次对 Listener、Filter、Servlet 进行实例化、并且调用其初始化方法,比如 ServletContextListener#contextInitialized()、Flter#init() 等
到此tomcat的启动阶段就已经完成了。使用的是责任链模式,一步一步的启动。
组件启动的顺序:
Server–>Service–>Engine–>Host–>Context–>Wrapper
- tomcat初始化和启动流程图:
3.Tomcat的web请求处理阶段
- web请求的总体流程图
由前面的tomcat总体架构可以知道,tomcat是由connector来接收用户的请求,然后再交由container处理。
通过跟踪connector的启动过程,先后启动protocolHandler,然后启动了endpoint,然后启动了Acceptor(用来接收请求)
- 通过查看NioEndpoint的主要结构,可以看到该类有三个重要的内部类:Acceptor,Poller,SocketProcessor。
1 | java复制代码# 1.NioEndpoint的startInternal()方法 |
由上面的代码可以看出NioEndpoint在执行startInternal()方法时候,会启动Acceptor线程。Acceptor继承了Runnable,然后使用Thread启动了Acceptor线程。
- Acceptor run()方法分析
1 | java复制代码# 1.Acceptor 用来接收请求 |
通过查看Acceptor的run方法,可以看到拿到socket对象。(说明tomcat的底层是通过socket通信的)
然后就通过生成poller。Poller线程主要用于以较少的资源轮询已连接套接字以保持连接,当数据可用时转给工作线程。
- Poller 轮询,检验是否有新的请求
1 | java复制代码# 1.Poller的run()方法 |
Poller线程主要用于以较少的资源轮询已连接套接字以保持连接,当数据可用时转给工作线程
通过对Poller的run方法跟踪,可以看到会创建SocketProcessor(worker)对socket的进一步处理。
- SocketProcessor (worker) 处理socket过程。
1 | java复制代码# 1.AbstractEndpoint的processSocket()方法 |
- SocketProcessor 的 doRun()分析。
1 | java复制代码# 1.SocketProcessor的doRun()方法 |
通过上面的代码分析,SocketProcessor 在处理socket对象,最终是通过调用getAdapter().service(request, response)方法处理。
- 下面进入到getAdapter().service(request, response)方法分析。
1 | java复制代码# 1.CoyoteAdapter的service()方法 |
通过对CoyoteAdapter的service()方法分析,可以知道他是一步一步的调用方法。
StandardEngineValue–>StandardHostValue–>StandardContextValue–>StandardWrapperValue的invoke方法。
- web请求的流程图:
- 附件:tomcat源码(有注释)github.com/llsydn/tomc…
- tomcat类关系图
本文转载自: 掘金