tomcat60源码
‘壹’ Tomcat篇02-整体架构和I/O模型
本文主要包括tomcat服务器的目录结构、工作模式、整体架构、I/O模型以及NIO、NIO2、APR三者的对比介绍。
我们先来看一下tomcat8.5和tomcat9中的home目录中的文件:
可以看到除掉一些说明文件之后,还有7个目录:
实际上除了主目录里有lib目录,在webapps目录下的web应用中的WEB-INF目录下也存在一个lib目录:
两者的区别在于:
●Tomcat主目录下的lib目录:存放的JAR文件 不仅能被Tomcat访问,还能被所有在Tomcat中发布的java Web应用访问
●webapps目录下的Java Web应用的lib目录:存放的JAR文件 只能被当前Java Web应用访问
既然有多个lib目录,那么肯定就有使用的优先顺序,Tomcat类加载器的目录加载优先顺序如下:
Tomcat的类加载器负责为Tomcat本身以及Java Web应用加载相关的类。假如Tomcat的类加载器要为一个Java Web应用加载一个类,类加载器会按照以下优先顺序到各个目录中去查找该类的.class文件,直到找到为止,如果所有目录中都不存在该类的.class文件,则会抛出异常:
Tomcat不仅可以单独运行,还可以与其他的Web服务器集成,作为其他Web服务器的进程内或进程外的servlet容器。集成的意义在于:对于不支持运行Java Servlet的其他Web服务器,可通过集成Tomcat来提供运行Servlet的功能。
Tomcat有三种工作模式:
我们先从tomcat的源码目录来分析一下tomcat的整体架构,前面我们配置jsvc运行tomcat的时候,我们知道tomcat中启动运行的最主要的类是 org.apache.catalina.startup.Bootstrap ,那么我们在tomcat的源码中的java目录下的org目录的apache目录可以找到主要的源码的相对应的类。
图中的目录如果画成架构图,可以这样表示:
Tomcat 本质上就是一款Servlet 容器,因此 catalina 才是Tomcat的核心 ,其他模块都是为 catalina 提供支撑的。
单线程阻塞I/O模型是最简单的一种服务器I/O模型,单线程即同时只能处理一个客户端的请求,阻塞即该线程会一直等待,直到处理完成为止。对于多个客户端访问,必须要等到前一个客户端访问结束才能进行下一个访问的处理,请求一个一个排队,只提供一问一答服务。
如上图所示:这是一个同步阻塞服务器响应客户端访问的时间节点图。
这种模型的特点在于单线程和阻塞I/O。 单线程即服务器端只有一个线程处理客户端的所有请求,客户端连接与服务器端的处理线程比是 n:1 ,它无法同时处理多个连接,只能串行处理连接。而阻塞I/O是指服务器在读写数据时是阻塞的,读取客户端数据时要等待客户端发送数据并且把操作系统内核复制到用户进程中,这时才解除阻塞状态。写数据回客户端时要等待用户进程将数据写入内核并发送到客户端后才解除阻塞状态。 这种阻塞带来了一个问题,服务器必须要等到客户端成功接收才能继续往下处理另外一个客户端的请求,在此期间线程将无法响应任何客户端请求。
该模型的特点:它是最简单的服务器模型,整个运行过程都只有一个线程,只能支持同时处理一个客户端的请求(如果有多个客户端访问,就必须排队等待), 服务器系统资源消耗较小,但并发能力低,容错能力差。
多线程阻塞I/O模型在单线程阻塞I/O模型的基础上对其进行改进,加入多线程,提高并发能力,使其能够同时对多个客户端进行响应,多线程的核心就是利用多线程机制为每个客户端分配一个线程。
如上图所示,服务器端开始监听客户端的访问,假如有两个客户端同时发送请求过来,服务器端在接收到客户端请求后分别创建两个线程对它们进行处理,每条线程负责一个客户端连接,直到响应完成。 期间两个线程并发地为各自对应的客户端处理请求 ,包括读取客户端数据、处理客户端数据、写数据回客户端等操作。
这种模型的I/O操作也是阻塞的 ,因为每个线程执行到读取或写入操作时都将进入阻塞状态,直到读取到客户端的数据或数据成功写入客户端后才解除阻塞状态。尽管I/O操作阻塞,但这种模式比单线程处理的性能明显高了,它不用等到第一个请求处理完才处理第二个,而是并发地处理客户端请求,客户端连接与服务器端处理线程的比例是 1:1 。
多线程阻塞I/O模型的特点:支持对多个客户端并发响应,处理能力得到大幅提高,有较大的并发量,但服务器系统资源消耗量较大,而且如果线程数过多,多线程之间会产生较大的线程切换成本,同时拥有较复杂的结构。
在探讨单线程非阻塞I/O模型前必须要先了解非阻塞情况下套接字事件的检测机制,因为对于单线程非阻塞模型最重要的事情是检测哪些连接有感兴趣的事件发生。一般会有如下三种检测方式。
当多个客户端向服务器请求时,服务器端会保存一个套接字连接列表中,应用层线程对套接字列表轮询尝试读取或写入。如果成功则进行处理,如果失败则下次继续。这样不管有多少个套接字连接,它们都可以被一个线程管理,这很好地利用了阻塞的时间,处理能力得到提升。
但这种模型需要在应用程序中遍历所有的套接字列表,同时需要处理数据的拼接,连接空闲时可能也会占用较多CPU资源,不适合实际使用。
这种方式将套接字的遍历工作交给了操作系统内核,把对套接字遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层,它们需要处理的对象就是这些事件,这是一种事件驱动的非阻塞方式。
服务器端有多个客户端连接,应用层向内核请求读写事件列表。内核遍历所有套接字并生成对应的可读列表readList和可写列表writeList。readList和writeList则标明了每个套接字是否可读/可写。应用层遍历读写事件列表readList和writeList,做相应的读写操作。
内核遍历套接字时已经不用在应用层对所有套接字进行遍历,将遍历工作下移到内核层,这种方式有助于提高检测效率。 然而,它需要将所有连接的可读事件列表和可写事件列表传到应用层,假如套接字连接数量变大,列表从内核复制到应用层也是不小的开销。 另外,当活跃连接较少时, 内核与应用层之间存在很多无效的数据副本 ,因为它将活跃和不活跃的连接状态都复制到应用层中。
通过遍历的方式检测套接字是否可读可写是一种效率比较低的方式,不管是在应用层中遍历还是在内核中遍历。所以需要另外一种机制来优化遍历的方式,那就是 回调函数 。内核中的套接字都对应一个回调函数,当客户端往套接字发送数据时,内核从网卡接收数据后就会调用回调函数,在回调函数中维护事件列表,应用层获取此事件列表即可得到所有感兴趣的事件。
内核基于回调的事件检测方式有两种
第一种是用 可读列表readList 和 可写列表writeList 标记读写事件, 套接字的数量与 readList 和 writeList 两个列表的长度一样 。
上面两种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新事件列表,而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接,进而对这些连接进行读写操作,极大提高了检测效率,自然处理能力也更强。
单线程非阻塞I/O模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态。虽然只有一个线程,但是它通过把非阻塞读写操作与上面几种检测机制配合就可以实现对多个连接的及时处理,而不会因为某个连接的阻塞操作导致其他连接无法处理。在客户端连接大多数都保持活跃的情况下,这个线程会一直循环处理这些连接,它很好地利用了阻塞的时间,大大提高了这个线程的执行效率。
单线程非阻塞I/O模型的主要优势体现在对多个连接的管理,一般在同时需要处理多个连接的发场景中会使用非阻塞NIO模式,此模型下只通过一个线程去维护和处理连接,这样大大提高了机器的效率。一般服务器端才会使用NIO模式,而对于客户端,出于方便及习惯,可使用阻塞模式的套接字进行通信。
在多核的机器上可以通过多线程继续提高机器效率。最朴实、最自然的做法就是将客户端连接按组分配给若干线程,每个线程负责处理对应组内的连接。比如有4个客户端访问服务器,服务器将套接字1和套接字2交由线程1管理,而线程2则管理套接字3和套接字4,通过事件检测及非阻塞读写就可以让每个线程都能高效处理。
多线程非阻塞I/O模式让服务器端处理能力得到很大提高,它充分利用机器的CPU,适合用于处理高并发的场景,但它也让程序更复杂,更容易出现问题(死锁、数据不一致等经典并发问题)。
最经典的多线程非阻塞I/O模型方式是Reactor模式。首先看单线程下的Reactor,Reactor将服务器端的整个处理过程分成若干个事件,例如分为接收事件、读事件、写事件、执行事件等。Reactor通过事件检测机制将这些事件分发给不同处理器去处理。在整个过程中只要有待处理的事件存在,即可以让Reactor线程不断往下执行,而不会阻塞在某处,所以处理效率很高。
基于单线程Reactor模型,根据实际使用场景,把它改进成多线程模式。常见的有两种方式:一种是在耗时的process处理器中引入多线程,如使用线程池;另一种是直接使用多个Reactor实例,每个Reactor实例对应一个线程。
Reactor模式的一种改进方式如下图所示。其整体结构基本上与单线程的Reactor类似,只是引入了一个线程池。由于对连接的接收、对数据的读取和对数据的写入等操作基本上都耗时较少,因此把它们都放到Reactor线程中处理。然而,对于逻辑处理可能比较耗时的工作,可以在process处理器中引入线程池,process处理器自己不执行任务,而是交给线程池,从而在Reactor线程中避免了耗时的操作。将耗时的操作转移到线程池中后,尽管Reactor只有一个线程,它也能保证Reactor的高效。
Reactor模式的另一种改进方式如下图所示。其中有多个Reactor实例,每个Reactor实例对应一个线程。因为接收事件是相对于服务器端而言的,所以客户端的连接接收工作统一由一个accept处理器负责,accept处理器会将接收的客户端连接均匀分配给所有Reactor实例,每个Reactor实例负责处理分配到该Reactor上的客户端连接,包括连接的读数据、写数据和逻辑处理。这就是多Reactor实例的原理。
Tomcat支持的I/O模型如下表(自8.5/9.0 版本起,Tomcat移除了对BIO的支持),在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO。 无论 NIO、NIO2 还是 APR, 在性能方面均优于以往的BIO。
Tomcat中的NIO模型是使用的JAVA的NIO类库,其内部的IO实现是同步的(也就是在用户态和内核态之间的数据交换上是同步机制),采用基于selector实现的异步事件驱动机制(这里的异步指的是selector这个实现模型是使用的异步机制)。 而对于Java来说,非阻塞I/O的实现完全是基于操作系统内核的非阻塞I/O,它将操作系统的非阻塞I/O的差异屏蔽并提供统一的API,让我们不必关心操作系统。JDK会帮我们选择非阻塞I/O的实现方式。
NIO2和前者相比的最大不同就在于引入了异步通道来实现异步IO操作,因此也叫AIO(Asynchronous I/O)。NIO.2 的异步通道 APIs 提供方便的、平台独立的执行异步操作的标准方法。这使得应用程序开发人员能够以更清晰的方式来编写程序,而不必定义自己的 Java 线程,此外,还可通过使用底层 OS 所支持的异步功能来提高性能。如同其他 Java API 一样,API 可利用的 OS 自有异步功能的数量取决于其对该平台的支持程度。
异步通道提供支持连接、读取、以及写入之类非锁定操作的连接,并提供对已启动操作的控制机制。Java 7 中用于 Java Platform(NIO.2)的 More New I/O APIs,通过在 java.nio.channels 包中增加四个异步通道类,从而增强了 Java 1.4 中的 New I/O APIs(NIO),这些类在风格上与 NIO 通道 API 很相似。他们共享相同的方法与参数结构体,并且大多数对于 NIO 通道类可用的参数,对于新的异步版本仍然可用。主要区别在于新通道可使一些操作异步执行。
异步通道 API 提供两种对已启动异步操作的监测与控制机制。第一种是通过返回一个 java.util.concurrent.Future 对象来实现,它将会建模一个挂起操作,并可用于查询其状态以及获取结果。第二种是通过传递给操作一个新类的对象, java.nio.channels.CompletionHandler ,来完成,它会定义在操作完毕后所执行的处理程序方法。每个异步通道类为每个操作定义 API 副本,这样可采用任一机制。
Apache可移植运行时(Apache Portable Runtime,APR) 是Apache HTTP服务器的支持库,最初,APR是作为Apache HTTP服务器的一部分而存在的,后来成为一个单独的项目。其他的应用程序可以使用APR来实现平台无关性(跨平台)。APR提供了一组映射到下层操作系统的API,如果操作系统不支持某个特定的功能,APR将提供一个模拟的实现。这样程序员使用APR编写真正可在不同平台上移植的程序。
顺利安装完成后会显示apr的lib库路径,一般都是 /usr/local/apr/lib
安装完成之后我们还需要修改环境变量和配置参数
这里我们使用的是systemd调用jsvc来启动tomcat,所以我们直接在systemd对应的tomcat的unit文件中的 ExecStart 中添加一个路径参数 -Djava.library.path=/usr/local/apr/lib 指向apr库的路径:
然后我们在tomcat的home目录下的conf子目录中对server.xml文件进行修改
把8080端口对应的配置修改成apr:(其他端口配置也类似)
重启tomcat服务我们从tomcat的日志中就可以看到协议已经从默认的NIO变成了apr。
NIO性能是最差的这是毋庸置疑的,如果是考虑到高并发的情况,显然异步非阻塞I/O模式的NIO2和APR库在性能上更有优势,实际上NIO2的性能表现也和APR不相上下,但是NIO2要求Tomcat的版本要在8.0以上,而APR只需要5.5以上即可,但是APR需要额外配置库环境,相对于内置集成的NIO2来说APR这个操作比较麻烦,两者各有优劣。具体使用哪个还是需要结合实际业务需求和环境进行测试才能决定。
‘贰’ 如何学习Tomcat的源码
方法/步骤
第一步 下载安装fiddler,fiddler2和4的版本对电脑里面net版本有要求。
第二步 打开fiddler 2,fiddler会及时抓取正在进行网络通讯的所以信息
第三步 因为网页后台会有自动刷新的进程,所以再抓取特顶网页COOKIE时候,先按住CTRL+X清楚现在记录的网页
第四步 以小米网站为例,刷新该网站。然后就会看见fiddler已经在记录一系列的网页,选定小米官网
第五步 然后在右侧的属性详情栏,即可以找到【cookie】
‘叁’ Tomcat源码解析-容器组件之StandardHost
Container容器用来表示tomcat中servlet容器,负责servelt的加载和管理,处理请求ServletRequest,并返回标准的 ServletResponse 对象给连接器。
Container容器组件
tomcat 将Container容器按功能分为4个组件,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。
Lifecycle接口定义tomcat中所有组件的生命周期相关接口方法。Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现。而子类就负责实现自己的初始化、启动和停止等模板方法。
详见 Tomcat架构设计-组件生命周期 Lifecycle
Container接口定义tomcat中所有容器组件的通用接口方法。Tomcat 定义一个基类ContainerBase 来实现Container 接口,把一些公共的逻辑放到基类中实现。
详见 Tomcat架构设计-容器组件基类 ContainerBase
在tomcat中最核心功能就是将一个静态资源目录或一个应用程序部署到容器中。而这个容器就是指得Host容器组件。而静态资源或一个应用程序通过Context容器组件来表示。所谓部署就是加载到Host容器的子组件中。当然虚拟主机除了部署外还又其他功能,包括热部署,懒加载,别名等。
如果想要将一个静态资源目录部署到Tomcat服务器上,tomcat提供了多种部署方式
在server.xml中配置
path表示Context根路径,docBase表示映射静态资源目录
在xmlBase路径下配置xml文件
在$CATALINA_BASE/xmlBase 路径下创建 JavaWebApp.xml,xmlBase配置在Host标签属性中
文件名称表示Context根路径,docBase表示映射静态资源目录
将资源文件拷贝到appBase路径下
appBase路径在Host标签属性中定义,文件名称表示Context根路径。
部署应用程序到appBase目录
appBase是在server.xml文件Host标签appBase属性来定义,appBase可以填写相对路径或者绝对路径,如果是相对路径那么完整路径为 CATALINA_BASE表示tomcat的工作目录
虚拟主机Host可以在设置在使用时在部署静态资源或应用程序。
虚拟主机Host会定期检查appBase和xmlBase目录下新Web应用程序或静态资源,如果发生更新则会触发对应context组件的重新加载
虚拟主机Host可以定义别名。
StandardHost并管理子容器Context组件,以及从父类ContainerBase,LifecycleBase 继承的通用组件。
StandardHost实现了Host接口,在了解StandardHost功能之前我们需要了解
Host接口.
Host接口
StandardHost实现Host接口,Host接口用来对Tomcat中虚拟主机功能配置提供了访问方法。
StandardHost只对虚拟机功能配置做了定义 ,其具体实现由HostConfig来实现。同时负责管理子容器Context组件(下图蓝色),以及从父类ContainerBase(下图红色),LifecycleBase(下图黄色) 继承的通用组件。
tomcat中所有组件都需要经历如下流程。
Tomcat使用Digester解析server.xml,Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。相对于SAX,Digester可以针对每一个xml标签设置对应的解析规则。详见 Tomcat相关技术-Digester(二)
Tomcat在Catalina组件初始化阶段调用createStartDigester()创建Digester对象,Digester对象内包含解析server.xml规则,接着通过Digester对象解析server.xml实例化StandardHost,并对部分属性设置值.
server.xml配置
解析<Host>标签及子标签tomcat使用规则组HostRuleSet,其中定义了解析规则。
CopyParentClassLoaderRule规则
CopyParentClassLoaderRule规则,负责调用次栈顶对象getParentClassLoader获取父类加载,设置到栈顶对象parentClassLoader属性上
LifecycleListenerRule规则
LifecycleListenerRule 规则负责给栈顶对象添加一个生命周期监听器.
接下来初始化开始则进入tomcat组件的生命周期,对于tomcat中所有组件都必须实现Lifecycle,Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等模板方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。
StandardHost父类对容器的初始化、启动和停止等模板方法进行的了默认实现。子类容器只需要重写父类实现即可实现扩展。
StandardEngine其他生命周期实现均从父类ContainerBase继承。
为添加的子容器设置生命周期监听器MemoryLeakTrackingListener
每一个容器组件都有一个 Pipeline 对象,Pipeline 中维护了 Valve 链表,默认时每一个Pipeline存放了一个默认的BasicValue,
这里每一个Value表示一个处理点,当调用addValve 方法时会将添加Vaule添加到链表头部,Pipeline 中没有 invoke
方法,请求处理时Pipeline只需要获取链表中第一个Valve调用incoke去执行,执行完毕后当前Value会调用
getNext.invoke() 来触发下一个 Valve 调用
每一个容器在执行到最后一个默认BasicValue时,会负责调用下层容器的 Pipeline 里的第一个 Valve
对于StandardHost容器来说默认情况存在三个Value(阀门),分别是 AccessLogValve (构建时读取server.xml时), StandardHostValve (构建实例化时), ErrorReportValve (启动时)。
记录访问日志,这里是一个通用组件,后续会由专题讲解
‘肆’ 怎么看J2EE的源码
sun的jdk中并没有包含servlet源代码,也就是你关联了jdk中的src后还是不能够查看servlet的源代码的。servlet是在tomcat有实现的,所以我们只要下载tomcat的源码就可以查看servlet的源码了。
下面是步骤:
1. 进入tomcat官方的下载tomcat源码,一般对应你的版本,例如我的版本是tomcat 6.X,那么我现在tomcat 6.x的src包下载.
2. 进入官网下载自己配套的src源码
3. 进入到你自己写的servlet的程序代码中
4. 按住crtl, 然后用鼠标点击servlet的类的方法,关联下那个源代码就可以了。那个源代码就是你下的那个tomcat源码
‘伍’ Tomcat之NioEndpoint源码分析
NioEndpoint涉及tomcat服务端是如何处理客户端的连接及数据处理的模型
endpoint#start方法启动调用链
org.apache.catalina.startup.Bootstrap#main
org.apache.catalina.startup.Bootstrap#start
org.apache.catalina.startup.Catalina#start
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.core.StandardServer#startInternal
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.core.StandardService#startInternal
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.connector.Connector#startInternal
org.apache.coyote.AbstractProtocol#start
org.apache.tomcat.util.net.AbstractEndpoint#start
开始启动NIO的endpoint
①、创建executor执行器线程池
corePoolSize核心大小是10,最大maximumPoolSize是200的ThreadPoolExecutor
while (endpoint.isRunning()) {}
一直循环直到接收到一个shutdown 命令(Loop until we receive a shutdown command)
1、获取客户端连接的socket对象
2、把socket对象设置到endpoint中
根据NioChannel创建newWrapper对象
重置NioChannel中的SocketChannel sc和socketWrapper 参数
把当前连接缓存到connections集合中
socketWrapper赋值为newWrapper对象
设置当前socket为非阻塞
③、为socketWrapper设置参数
socketWrapper就是对当前连接的socket的一个封装
3、把socketWrapper注册到Poller中
org.apache.tomcat.util.net.NioEndpoint.Poller#register
创建Poller对象,会创建选择器selector =WindowsSelectorImpl
这个线程启动也是一个死循环
1、一直循环遍历events队列中是否有连接
2、有新连接便会调用processKey处理新连接
创建SocketProcessorBase对象,这个对象也是一个实现了Runnable的线程。
sc任务放到线程池execute执行,调用到SocketProcessorBase的run方法
NioEndpoint的启动,启动了三个线程和一个执行器线程池。
Acceptor线程处理客户端的连接,并把连接封装添加到Poller线程中的events中。
Poller线程把连接socket添加到events中,在处理socket时,会启动一个处理连接的ConnectionHandler线程。
ConnectionHandler线程把处理的socket交给Http11Processor进行协议的处理。
ConnectionHandler线程是在线程池中执行。
‘陆’ 在TOMCAT中怎么找到源代码的位置
在server.xml下面有段上下文配置,也就是<context></context>标签,用来指定你的web应用程序放的位置及相关属性配置
‘柒’ Tomcat源码解析-组件之StandardServer
StandardServer是tomcat容器的最高层的组件,职责如下:
实现Tomcat一键启动关闭,管理全局 JDNI资源,管理子组件,阻塞tomcat主线程。
StandardServer子组件
Bootstarp作为tomcat启动类,JVM会调用main函数完成tomcat启动。在其内部流程如下:
核心方法功能
Tomcat使用Digester解析server.xml,Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。相对于SAX可以为xml中每一个标签设置对应的解析规则。详见 Tomcat相关技术-Digester(二)
这里通过解析server.xml实例化StandardServer,并设置server.xml文件中定义的属性初始化。
server.xml配置
<Server>标签用来表示当前StandardServer组件
StandardServer构造函数
将<server>标签属性映射到StandardServer对象属性中
<GlobalNamingResources>标签中定义了全局JNDI资源,
<Listener>标签中定义StandardServer组件中需要的LifecycleListener监听器。<Server>标签内可以设置多个<Listener>。
Server中定义的Listener
添加LifecycleListener监听器
<Service>标签中定义StandardServer组件中子组件Service。<Server>标签内可以设置多个<Service>。
添加Service子组件
StandardServer作为tomcat最上层的组件,和其他所有组件一样都实现了Lifecycle 接口。
Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等模板方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。
其他模板方法
详见 Tomcat架构设计-组件生命周期
StandardServer组件初始化的核心是调用所有Service子组件初始化方法init。
详细流程如下:
启动start的核心是调用所有Service子组件初始化方法start。
详细流程如下:
启动stop的核心是调用所有Service子组件初始化方法stop。
详细流程如下:
销毁destroy的核心是调用所有Service子组件初始化方法destroy。
1 调用所有Service子组件启动方法destroy
2 销毁JND全局资源
3 jmx bean注销MBeanFactory
4 jmx bean注销StringCache
5 调用LifecycleMBeanBase.destroyInternal 将当前组件对象从jmx 注销
在这样的设计中,在父组件的 init 方法里需要创建子组件并调用子组件的 init 方法。同样,在父组件的 start 方法里也需要调用子组件的 start 方法。只要调用最顶层组件StandardServer的 init 和 start 方法,整个 Tomcat 就被启动起来了。只要调用最顶层组件StandardServer的 destroy 和 stop 方法,整个 Tomcat 就被关闭。
添加已在解析server.xml初始化设置调用
阻塞tomcat主线程,只要stopAwait不为true, tomcat主线程在此无限循环.监听到客户端发起SHUTDOWN命令后退出
‘捌’ tomcat的classloader的源码解析
相关源码如下:
common,catalina,shared三个classloader没有违背双亲加载原则
WebappClassLoader违背了双亲委派机制 因为重写了loadclass
在context启动的时候其会启动一个webappLoader
而webappLoader会创建一个WebappClassLoader,设置一个source(对应咱们webapps下面的一个项目)
设置delegate=false 代表不需要遵循双亲委派机制,然后启动WebappClassLoader,
然后这个context的加载的会使用自己的context里面含有的WebappClassLoader去加载bean
当webapps下面有多个项目就建立多个standardContext
每个standardContext 设有一个对应的webappLoader和webappClassLoader
‘玖’ 如何调试tomcat源码实现一次http请求
上文我们说到endpoint是底层处理I/O具体实现类,那么一次HTTP首先也要从这个类中开始。还是以NIOEndPoint实现类为例子。在NIOEndPoint类中有一个名为Acceptor内部类。该内部类负责接收即将到来的TCP/IP连接,并将它们分配给合适的processor处理。
HTTP底层是TCP协议,Java实现TCP协议的具体的方式就是Socket连接。我相信只要了解一点Java网络编程方面的知识都会了解的,不在累述。另一个比较重要的方法是setSocketOptions(),该方法是将socket连接添加到一个缓存队列里面。这里使用的是生产者和消费者模式,如果对该模式有不了解,请看我的另一个博文,Java并发之生产者和消费者模型
‘拾’ eclipse怎么导入tomcat源码
在你要看代码的地方,ctrl+方法名(或者类名),会弹出一个页面,在这个页面上,你关联一下你下载的Tomcat源码包。
关闭之后,在ctrl+方法名(或者类名)就可以看到源码了。