Tomcat核心组件及源码执行流程

Tomcat 目录结构

  • bin / :存放Tomcat的启动、停止等批处理脚本文件

    • startup.bat ,startup.sh:用于在windows和linux下的启动脚本
    • shutdown.bat ,shutdown.sh:用于在windows和linux下的停止脚本
  • conf /: 用于存放Tomcat的相关配置文件

    • Catalina:用于存储针对每个虚拟机的Context配置
    • context.xml:用于定义所有web应用均需加载的Context配置,如果web应用指定了自己context.xml,该文件将被覆盖
    • catalina.properties:Tomcat 的环境变量配置
    • catalina.policy :Tomcat 运行的安全策略配置
    • logging.propertiesTomcat 的日志配置文件, 可以通过该文件修改Tomcat 的日志级别及日志路径等
    • server.xml :Tomcat 服务器的核心配置文件
    • tomcat-users.xml :定义Tomcat默认的用户及角色映射信息配置
    • web.xml:Tomcat 中所有应用默认的部署描述文件, 主要定义了基础Servlet和MIME映射。
  • lib /: Tomcat 服务器的依赖包

  • logs /: Tomcat 默认的日志存放目录

  • webapps /: Tomcat 默认的Web应用部署目录

  • work /: Web 应用JSP代码生成和编译的临时目录

Tomcat 分层示意图

在这里插入图片描述

Tomcat核心模块-----连接器、容器

Tomcat核心功能

  • 处理Socket连接,负责网络字节流与Request和Response对象的转化。
  • 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。
在这里插入图片描述

连接器 - Connector

  • Coyote 是Tomcat的连接器(Connector)的框架 , 是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应 。
  • Coyote 封装了底层的网络通信(Socket 请求及响应处理),为Catalina 容器提供了统一的接口,使Catalina 容器与具体的请求协议及IO操作方式完全解耦。Coyote 将Socket 输入转换封装为 Request 对象,交由Catalina 容器进行处理,处理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写入输出流 。
  • Coyote 作为独立的模块,只负责具体协议和IO(在8.0之前,Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO)的相关操作, 与Servlet 规范实现没有直接关系,因此即便是 Request 和 Response 对象也并未实现Servlet规范对应的接口, 而是在Catalina 中将他们进一步封装为ServletRequest 和 ServletResponse 。
Connector结构

在这里插入图片描述

  • EndPoint

    • EndPoint : Coyote 通信端点,即通信监听的接口,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。
    • Tomcat 并没有EndPoint 接口,而是提供了一个抽象类AbstractEndpoint , 里面定义了两个内部类:Acceptor和SocketProcessor。
      • Acceptor用于监听Socket连接请求。
      • SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理。SocketProcessor被提交到线程池(Executor)来执行。
  • Processor

    • Coyote 协议处理接口 ,Processor是对应用层协议的抽象,Processor用来实现HTTP协议
    • Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理
  • ProtocolHandler

    • Coyote 协议接口, 通过Endpoint 和 Processor , 实现针对具体协议的处理能力。Tomcat 按照协议和I/O 提供了6个实现类 :
      • AjpNioProtocol
      • AjpAprProtocol
      • AjpNio2Protocol
      • Http11NioProtocol
      • Http11Nio2Protocol
      • Http11AprProtocol
    • 配置tomcat/conf/server.xml 时 , 至少要指定具体的ProtocolHandler , 当然也可以指定协议名称, 如 : HTTP/1.1 ,如果安装了APR,那么将使用Http11AprProtocol , 否则使用 Http11NioProtocol 。
  • Adapter

    • 由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。
    • ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,不能用TomcatRequest作为参数来调用容器。
    • 连接器调用CoyoteAdapter的Sevice方法,传入的是TomcatRequest对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法

容器 - Container

  • Catalina是Tomcat的servlet容器(Container)
  • Catalina包含所有的容器组件,以及安全、会话、集群、管理等Servlet 容器架构的各个方面。它通过松耦合的方式集成Coyote,以完成按照请求协议进行数据读写。同时,它还包括启动入口、Shell程序等。
  • Catalina是 Tomcat 的核心,其他模块都是为Catalina 提供支撑的。 比如 : 通过Coyote 模块提供链接通信,Jasper 模块提供JSP引擎,Naming 提供JNDI 服务,Juli 提供日志服务。
Catalina 结构

说明:

  • 每个服务都包含:多个连接器组件Connector(Coyote 实现)和对应一个容器组件Container。在Tomcat 启动的时候, 会初始化一个Catalina的实例。
  • Catalina负责管理Server,而Server表示着整个服务器。Server下面有多个服务Service

Catalina 各个组件

  • Catalina:负责解析Tomcat的配置文件 , 以此来创建服务器Server组件,并根据命令来对其进行管理
  • Server:服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎,Tomcat连接器。Server通过实现Lifecycle接口,提供了一种优雅的启动和关闭整个系统的方式
  • Service:服务是Server内部的组件,一个Server包含多个Service。它将若干个Connector组件绑定到一个Container(Engine)上
  • Connector:连接器,处理与客户端的通信,它负责接收客户请求,然后转给相关的容器处理,最后向客户返回响应结果
  • Container 容器,负责处理用户的servlet请求,并返回对象给web用户的模块
Container 结构

在这里插入图片描述
各个组件

  • Engine:表示整个Catalina的Servlet引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine,但是一个引擎可包含多个Host
  • Host:代表一个虚拟主机,或者一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context
  • Context :表示一个Web应用程序, 一个Context 可包含多个Wrapper
  • Wrapper: 表示一个Servlet,Wrapper 作为容器中的最底层,不能包含子容器

这些容器具有父子关系,形成一个树形结构,使用了设计模式中的组合模式,所有容器组件都实现了Container接口,组合对象是上面的Context、Host或者Engine。

Lifecycle
	|- Container
		|- Engine、Context、Wrapper、Host
			|- StandarEngine、StandarContext、StandarWrapper、StandarHost
  • Container接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期

  • Container通过 getParent、SetParent、addChild和removeChild等方法实现组合

Tomcat 启动流程

在这里插入图片描述

步骤:

  • 启动tomcat , 需要调用 bin/startup.bat (在linux 目录下 , 需要调用 bin/startup.sh), 在startup.bat 脚本中, 调用了catalina.bat。
  • 在catalina.bat 脚本文件中,调用了BootStrap 中的main方法。
  • 在BootStrap 的main 方法中调用了 init 方法 , 来创建Catalina 及 初始化类加载器。
  • 在BootStrap 的main 方法中调用了 load 方法 , 在其中又调用了Catalina的load方法。
  • 在Catalina 的load 方法中 , 需要进行一些初始化的工作, 并需要构造Digester 对象, 用于解析 XML。
  • 然后在调用后续组件的初始化操作 。。。加载Tomcat的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求

总结:

  • Tomcat的启动过程非常标准化, 统一按照生命周期管理接口Lifecycle的定义进行启动
  • 首先调用init() 方法进行组件的逐级初始化操作,然后再调用start()方法进行启动
  • 每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法
  • 组件与组件之间是松耦合的,可以很容易的通过配置文件进行修改和替换

Tomcat 请求处理流程

Mapper组件

Tomcat用Mapper组件来确定每一个请求应该由哪个Wrapper容器里的Servlet来处理

工作原理

  • Mapper组件里保存了Web应用的配置信息(其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径),这些配置信息就是一个多层次的Map。
  • 当一个请求到来时, Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet

Tomcat处理请求责任链

在这里插入图片描述

  • Tomcat中定义了PipelineValve 两个接口,Pipeline 用于构建责任链, 后者代表责任链上的每个处理器。
  • Pipiline通过获得首个Valve来启动整合链条的执行 。
  • Pipeline 中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行),封装了具体的请求处理和输出响应的过程。
  • 调用addValve()方法, 为Pipeline 添加其他的Valve, 后添加的Valve 位于基础的Valve之前,并按照添加顺序执行。

Tomcat的处理请求时序图

在这里插入图片描述

步骤如下 :

  • Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。
  • 将连接交给线程池Executor处理,开始执行请求响应任务。
  • Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
  • Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、 Wrappe容器处理请求。
  • CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应对象Response传递到Engine容器中,调用 Pipeline。
  • Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的 Valve–StandardEngineValve,负责调用Host容器的Pipeline。
  • Host容器的管道开始处理,流程类似,最后执行 Context容器的Pipeline。
  • Context容器的管道开始处理,流程类似,最后执行 Wrapper容器的Pipeline。
  • Wrapper容器的管道开始处理,流程类似,最后执行 Wrapper容器对应的Servlet对象的处理方法。

Jasper

Jasper模块是Tomcat的JSP核心引擎,JSP本质上是一个Servlet。Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果接响应在浏览器端 。另外,在运行的时候,Jasper还会检测JSP文件是否修改,如果修改,则会重新编译JSP文件。

Jsp文件编译

编译后的class文件

  • 其类名为 原类名_jsp , 继承自 org.apache.jasper.runtime.HttpJspBase , 该类是HttpServlet 的子类 , 所以jsp 本质就是一个Servlet 。
  • 通过属性 _jspx_dependants 保存了当前JSP页面依赖的资源, 包含引入的外部的JSP页面、导入的标签、标签所在的jar包等,便于后续处理过程中使用(如重新编译检测,因此它以Map形式保存了每个资源的上次修改时间)。
  • 通过属性 _jspx_imports_packages 存放导入的 java 包, 默认导入 javax.servlet ,javax.servlet.http, javax.servlet.jsp 。
  • 通过属性 _jspx_imports_classes 存放导入的类, 通过import 指令导入的DateFormat 、SimpleDateFormat 、Date 都会包含在该集合中。_jspx_imports_packages 和 _jspx_imports_classes 属性主要用于配置 EL 引擎上下文。
  • 请求处理由方法 _jspService 完成 , 而在父类 HttpJspBase 中的service 方法通过模板方法模式 , 调用了子类的 _jspService 方法。
  • _jspService 方法中定义了几个重要的局部变量 : pageContext 、Session、application、config、out、page。由于整个页面的输出有 _jspService 方法完成,因此这些变量和参数会对整个JSP页面生效。 这也是我们为什么可以在JSP页面使用这些变量的原因。
  • 指定文档类型的指令 (page) 最终转换为 response.setContentType() 方法调用。
  • 对于每一行的静态内容(HTML) , 调用 out.write 输出。
  • 对于 <% … %> 中的java 代码 , 将直接转换为 Servlet 类中的代码。 如果在 Java代码中嵌入了静态文件, 则同样调用 out.write 输出。

编译流程
在这里插入图片描述
Compiler 编译工作主要包含代码生成 和 编译两部分 :

  • 代码生成
    • Compiler 通过一个PageInfo 对象保存JSP 页面编译过程中的各种配置,这些配置可能来源于 Web 应用初始化参数, 也可能来源于JSP页面的指令配置(如 page ,include)。
    • 调用ParserController 解析指令节点, 验证其是否合法,同时将配置信息保存到PageInfo 中, 用于控制代码生成。
    • 调用ParserController 解析整个页面, 由于 JSP 是逐行解析, 所以对于每一行会创建一个具体的Node 对象。如 静态文本(TemplateText)、Java代码(Scriptlet)、定制标签(CustomTag)、Include指令(IncludeDirective)。
    • 验证除指令外其他所有节点的合法性, 如 脚本、定制标签、EL表达式等。
    • 收集除指令外其他节点的页面配置信息。
    • 编译并加载当前 JSP 页面依赖的标签
    • 对于JSP页面的EL表达式,生成对应的映射函数。
    • 生成JSP页面对应的Servlet 类源代码
  • 编译
    • 代码生成完成后, Compiler 生成 SMAP 信息。 如果配置生成 SMAP 信息,Compiler 则会在编译阶段将SMAP 信息写到class 文件中 。
    • 在编译阶段, Compiler 的两个实现 AntCompiler 和 JDTCompiler 分别调用先关框架的API 进行源代码编译。
      • 对于 AntCompiler 来说, 构造一个 Ant 的javac 的任务完成编译。
      • 对于 JDTCompiler 来说, 调用 org.eclipse.jdt.internal.compiler.Compiler 完成编译。

处理Jsp请求

Tomcat 在默认的web.xml 中配置了一个org.apache.jasper.servlet.JspServlet,用于处
理所有的.jsp 或 .jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。

  • tomcat/conf/web.xml
    <servlet>
    	<servlet-name>jsp</servlet-name>
    	<servlet-class>org.apache. jasper.servlet.Jspservlet</ servlet-class>
    	<init-param>
    		<param-name> fork</param-name>
    		<param-value> false</ param-value>
    	</init-param>
    	<init-param>
    		<param-name>xpoweredBy</param- name>
    		<param-value> false</param-value>
    	</init-param>
    	<load-on-startup>3</load-on-startup>
    </servlet>
    <servlet-mapping>
    	<servlet-name>jsp</servlet-name>
    	<url-pattern>*.jsp</url-pattern>
    	<url-pattern>*.jspx</url-pattern>
    </servlet-mapping>
    

JspServlet 处理流程图:
在这里插入图片描述
编译结果

  • 在 tomcat/conf/web.xml 中配置了参数scratchdir , 则jsp编译后的结果,就会存储在该目录下 。
    	<init-param>
    		<param-name>scratchdir</param-name>
    		<param-value>C:/tmp/jsp</ param-value>
    	</init-param>
    
  • 如果没有配置该选项, 则会将编译后的结果,存储在Tomcat安装目录下的:work/Catalina(Engine名称)/localhost(Host名称)/Context名称 。
  • 如果使用的是 IDEA 开发工具集成Tomcat 访问web工程中的jsp , 编译后的结果,
    存放在 :C:\Users\Administrator.IntelliJIdea2019.1\system\tomcat_project_tomcat\w
    ork\Catalina\localhost\jsp_demo_01_war_exploded\org\apache\jsp
相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页