Tomcat8源码笔记(七)组件启动Server Service Engine Host启动
一.Tomcat启动的入口
Tomcat初始化简单流程前面博客介绍了一遍,组件除了StandardHost都有博客,欢迎大家指文中错误。Tomcat启动类是Bootstrap,而启动容器启动入口位于 Catalina 的start方法: 因为反射调用Bootstrap的Catalina实例的start方法.
Catalina启动分为两个大阶段,一是启动Server(start方法),二是 Server启动之后等待(await方法)
一.StandardServer#start
按照之前记录 Tomcat8源码笔记(一)Lifecycle接口 Tomcat的初始化 init 、启动 start都是采用模板设计模式, 共性的地方就是触发监听的事件,个性的地方就是 initInternal、startInternal ,
StandardServer初始化默认是六个监听器,Tomcat8源码笔记(四)Server和Service初始化 这六个监听器暂且就不记录, 直接查看StandardServer的 startInternal方法.
StandardServer#startInternal解读:首先触发configure_start监听事件,主要是完成NamingContextListener的相关JNDI操作,这个暂且忽略. 设置StandardServer状态进入启动中LifecycleState.STARTING,同样六个触发监听器中相关事件; globalNamingResources同样是Tomcat组件,实现Lifecycle接口,所以globalNamingResources#start也会按照模板方法来,具体干嘛的还不清楚,后面补充; 最核心的地方看到了,StandardServer的初始化交给了Service的start,遍历Server的Service,分别start.
二.StandardService#start
StandardService默认是没有监听器的,所以start模板方法直接看startInternal即可. Tomcat8源码笔记(四)Server和Service初始化 这里介绍过StandardService的初始化,可以证明.
StandardService的startInternal方法按顺序分为四个步骤:启动容器container(通常就是StandardEngine)、启动线程池executor、启动mapperListener、启动connector连接器.
那我们下面就从container, 也就是StandardEngine 启动开始记录.
三. StandardEngine#start
Tomcat8源码笔记(五)组件Container分析 记录过StandardEngine的实例化包括初始化过程,StandardEngine有一个监听器,是Tomcat为我们注册的 EngineConfig.
EngineConfig的lifecycleEvent方法如下: 可以看到EngineConfig的start时机是容器启动staring状态,而StandardEngine一开始启动进入的状态是before_start.
StandardEngine#startInternal如下: 可以看到除了打印日志Starting Servlet Engine以外,就是调用父类的startInternal方法;而StandardEngine的父类是ContainerBase,不是LifecycleMBean这个啥,所以StandardEngine启动核心在ContainerBase中.
ContainerBase#startInternal方法
ContainerBase#startInternal方法,Cluster默认的server.xml中没有配置为空,Realm默认的server.xml中配置了一个LockOutRealm,Realm作为一个组件,start方法又是一遍Tomcat的流程,这里重点不是记这个; StandardEngine的children也就是StandardHost,startStopExecutor在之前介绍的Tomcat8源码笔记(五)组件Container分析 中初始化过了,初始化方法位于ContainerBase#initInternal。 这里可以看到使用线程池执行了StartChild实例,StartChild持有当前StandardHost对象,下面程序会阻塞着,直到StartChild的call执行完毕,程序执行完StartChild,还会调用Pipeline的start,启动自身的后台线程。 startInternal执行逻辑有点多,我们先从StartChild看起!
三.一 StartChild#call
因为StartChild是通过线程池来执行的,我们可以通过IDEA很方便的进行多线程DEBUG,这点我以前用Eclipse时候没发现这种优点!
多线程DEBUG方式,在StartChild#call处打断点,上面Suspend(挂起)地方勾选Thread,具体作用对我而言最明显的就是,有时候多线程DEBUG,不选Thread,线程切换时候IDEA界面在那里抽筋一样多个代码地方跳跃,很影响感受,F6单步调试按一下,屏幕狂跳一下.
切换线程的窗口位于DEBUG视图,下面红框的地方可以选择要调试哪个线程。 记住: StartChild执行的那个线程池出来的线程名字叫Catalina-startStop-,而1代表了线程的编号;另外也可以发现executor.submit(new StartChild())方法,得到的Future,在调用get方法之后会一直堵塞知道方法结束,下图中的main线程就在WAIT等待状态.
跑偏了,可以看到上面StartChild,因为StartChild持有StandardHost实例,call方法也是调用StandardHost#start方法! 而StandardHost组件初始化工作都没有做过,组件状态还是NEW的状态,所以按照Tomcat的start模板方法,会先init初始化,然后再start! StandardHost实例持有一个LifecycleListener :HostConfig,不用说肯定是Tomcat替我们注册的,就像StandardEngine有一个EngineConfig的监听器。
那下面的逻辑清楚了,StandardHost#start,先是触发HostConfig初始化事件监听---->StandardHost#initInternal—>HostConfig初始化结束事件监听---->HostConfig启动前事件监听--->
StandardHost#startInternal---->HostConfig启动结束事件监听.
三.二 HostConfig监听触发事件
可以看到只是给HostConfig设置四个 StandardHost的属性:copyXML、unpackWARs、deployXML、contextClass,另外触发事件只在before_start、start等情况下才有逻辑处理,所以before_init只是设置了上面四个属性而已。
按照上面流程,该是 StandardHost#initInternal:
转念一想,StandardHost继承自ContainerBase,initInternal方法应该也是初始化线程池,StandardEngine初始化的线程池产生的线程名字叫Catalina-startStop-,而StandardHost初始化的线程池产生线程名字类似 localhost-startStop-,此外的super.initInternal肯定就是JMX注册功能了.
继承走流程,该是HostConfig触发初始化结束after_init事件,但是上面有HostConfig触发代码,没有after_init的逻辑处理,所以呢?又重复设置了上面四个属性;
继续流程,HostConfig触发启动之前before_start事件,会调用HostConfig#beforeStart:
HostConfig#beforeStart
host#getCreateDirs默认返回true,host#getAppBaseFile获取到catalina-home/webapps目录,并赋值给StandardHost的appBaseFile,
host#getConfigBaseFile获取到catalina-home/conf/Catalina/localhost目录,并赋值给StandardHost的hostConfigBase,
之后校验这两层目录是否存在、是否是目录,属于校验目录是否存在为下面项目作保障。
继承走流程,StandardHost#startInternal, StandardHost在此之前Pipeline有两个Valve阀门,first是AccessLogValve,basic阀门是StandardHostValve,执行之后会添加一个阀门到first的next,这个阀门就是ErrorReportValve; 此外还会调用父类ContainerBase的startInternal方法. 父类ContainerBase#startInternal方法比较冗长,因为StandardHost没有子容器了,所以startInternal主要从Pipeline的start,到设置组件状态为STARTING,到启动自身守护线程;这里我们重点先关注,设置StandardHost状态为STARTING,这就又触发HostConfig的监听事件了!
HostConfig触发start监听事件,执行的方法是start,以下代码JMX忽略,那就是判断之前的appBaseFile是不是目录,deployOnStartup属性默认true,部署项目在TOMCAT启动的时候,那deployApps肯定就是部署项目了呗!先说好,按照上面流程分析,部署完项目之后呢,HostConfig的start就完事了,StandardHost的startInternal也就剩下启动自身守护线程没记录了,然后最后一步呢触发HostConfig启动完成监听,启动完成也没这种监听啊!所以当下只有deployApps和startThread了!
HostConfig#deployApps
deployApps除了前两行,我觉得每一个方法都是部署项目有关的,就是部署各种各样类型的项目呗!再来回忆杀下,appBase是catalina-home/webapps,configBase是catalina-home/conf/Catalina/localhost,至于这几个路径咋拼成的呢? catalina是StandardEngine的name,localhost是StandardHost的name.
filterAppPaths
unfilteredAppPaths就是webapps下所有文件名集合,说来也巧合我们StandardHost的deployIgnore,没设置过就是null,默认啥都不校验忽略;也告诉我们可以在server.xml中的Host元素里配置deployIgnore=“….”,来完成正则表达式匹配达到不部署某些项目的目的,学到了! 果然一个方法就是一个知识点啊!
deployDescriptors
deployDescriptors方法暂时不知道作用,后续补充!
deployWARs
代码过于长,这里不占用篇幅贴图了,主要适用于部署webapps下的 .war项目,后面会尝试部署一个war项目,暂时留作不记录,代码位置位于 HostConfig#deployWARs !
deployDirectories
这个适合我们分析,因为部署的都是文件夹型的项目,实例化了ContextName类,并且部署项目方式是线程池来执行DeployDirectory,并且会一直阻塞着直到webapps下目录项目都部署完毕,我们又可以使用IDEA多线程方式DEBUG
DeployDirectory类定义如下,我们在run方法中打上断点即可进入DEBUG,另外值得考虑的一点是?万一项目多,为啥Tomcatmore不是采用线程池,扩大一点线程数呢?我们可以看到StandardHost的初始化线程和最大线程数都是一致的,1个? 这是为什么呢?多线程可以加快部署项目速度吗?
在DEBUG之前,在补充一个类ContextName,baseName就是项目文件夹名字,path和name一般都是/文件夹名字,比如 /docs 、 /SpringWeb等等,而Root项目的path是“”,TOMCAT也是根据path来将我们的请求转发到对应项目中。
正式进入项目部署的环节,deployDirectory
代码比较冗长,这里只记录下过程,代码位置在:HostConfig#deployDirectory.
webapps下某个项目为例,假设WEB-INF下不存在content.xml,实例化一个StandardContext,代表一个项目,给StandardContext添加ContextConfig,并且给StandardContext设置ContextName的四个属性值,path、name、baseName、version等等,再将StandardContext加入到StandardHost; 部署完成之后就是存储在HostConfig的deployed中,稍后专门写一篇博客记录部署项目。
Tomcat部署项目打印日志如下:
部署完成webapps下的项目,程序会阻塞着直到项目都部署完成,至此HostConfig#start完成,StandardHost#start还剩下 threadStart方法
由于backgroudProcessorDelay默认设置为-1,所以并没有启动这个ContainerBackgroudProcessor!
至此,StandardHost启动完毕,再次回顾下,StandardHost的启动是通过线程池(线程初始化、最大个数都为1个的池子)来启动的,StandardHost可以说完成了webapps下项目的部署,
StandardEngine启动了StandardHost,创建了一堆StandardContext对应每一个项目,而ContainerBase的initInternal方法还没看完,还剩下三步:管道pipeline启动,设置状态为start触发相应监听,启动自身守护线程,会启动一个ContainerBackgroudProcessor线程!
至此StandardServer包括StandardService的启动完毕,也就意味着是Container的启动完毕,而Tomcat还没启动的就是mapperListener、Connector了,大体记录了一遍流程,篇幅原因还有的细节后面补充.
Tomcat8源码笔记(七)组件启动Server Service Engine Host启动的更多相关文章
- Tomcat8源码笔记(五)组件Container分析
Tomcat8源码笔记(四)Server和Service初始化 介绍过Tomcat中Service的初始化 最先初始化就是Container,而Container初始化过程是咋样的? 说到Contai ...
- Tomcat8源码笔记(九)组件StandardContext启动流程--未完待续
StandardContext代表的是webapps下项目,一个项目就是一个StandardContext,作为Tomcat组件的一部分,就会实现Lifecycle接口,被Tomcat管理着生命周期, ...
- Tomcat8源码笔记(八)明白Tomcat怎么部署webapps下项目
以前没想过这么个问题:Tomcat怎么处理webapps下项目,并且我访问浏览器ip: port/项目名/请求路径,以SSM为例,Tomcat怎么就能将请求找到项目呢,项目还是个文件夹类型的? Tom ...
- Tomcat8源码笔记(六)连接器Connector分析
根据 Tomcat8源码笔记(五)组件Container分析 前文分析,StandardService的初始化重心由 StandardEngine转移到了Connector的初始化,本篇记录下Conn ...
- Tomcat8源码笔记(四)Server和Service初始化
上一章 简单说明下Tomcat各个组件: Server:服务器,Tomcat服务器,一个Tomcat只有一个Server组件; Service:业务层,是Server下最大的子容器,一个Server可 ...
- Tomcat8源码笔记(三)Catalina加载过程
之前介绍过 Catalina加载过程是Bootstrap的load调用的 Tomcat8源码笔记(二)Bootstrap启动 按照Catalina的load过程,大致如下: 接下来一步步分析加载过程 ...
- Tomcat 启动时 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context}
在Eclipse 中,启动Tomcat 时,出现: 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting proper ...
- 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to
警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 警告: [SetPro ...
- 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} 解决方法
Tomcat启动时出现红色警告内容 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'sour ...
随机推荐
- [译]使用explain API摆脱ElasticSearch集群RED苦恼(转)
"哔...哔...哗",PagerDuty的报警通知又来了. 可能是因为你又遭遇了节点宕机, 或者服务器机架不可用, 或者整个ElasticSearch集群重启了. 不管哪种情况, ...
- drools规则引擎中易混淆语法分析_相互触发导致死循环分析
整理了下最近在项目中使用drools出现的问题,幸好都在开发与测试阶段解决了,未波及到prod. 首先看这样两条规则: /** * 规则1_set默认利率a */ rule "rate_de ...
- DOM函数和jQuery函数的覆盖与执行顺序
<script type="text/javascript"> window.onload = function () { alert(); } window.onlo ...
- robotframework 远程连接数据库问题
今天在使用RF远程连接数据库时出现报错的问题,因为我发现只安装databaselibrary跟PyMsql的话,连接本地的数据库是OK的,但是如果我们的测试机性能有限,那么既要编写代码.运行测试.还有 ...
- ServiceStack.Redis记录
一.事务 (一)AcquireLock方法 1.说明:申请对一个Key加锁(期间其他对象不能访问). 2.带using的使用,或带过期时间参数,否则锁不会自动释放. using (RedisCache ...
- 3-Fiddler修改请求或响应内容
1.修改请求内容 方法一:设置请求前断点,修改请求后发送 1)设置断点 2)选中请求,在inspectors下修改请求内容 3)修改请求后,点击Break on Response按钮,进行请求的发送 ...
- hadoop安装笔记
环境是ubuntu java啥的有yum apt-get install default-jdk update-alternatives --display Java hadoop解压缩就行 tar ...
- python yield 关键字
最近看代码看到python里面的yield关键字,和我之前接触的语言好像都没有来着,我就查了查它的含义,大概理解如下: >>> def createGenerator(): ... ...
- Android JNI 学习(七):Accessing Fields Api
一.GetFieldID jfieldIDGetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 返回类的实 ...
- Javascript高级编程学习笔记(76)—— 表单(4)选择文本
文本框脚本 在HTML中文本框有两种实现方式: <input> <textarea> 这两种实现方式虽然在多数情况下表现一致,但是两者之间仍存在许多重要区别 对于<inp ...