Tomcat学习笔记(自用)
该笔记由黑马“Tomcat核心原理解析”整理而成,若有侵权,请联系作者删除
1.简介
Servlet规范把能够发布和运行Javaweb应用的web服务器称为’Servlet容器’,因此,可以理解为Tomcat就是一个servlet容器。
常见的web服务器(servlet容器):
webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费
webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费
JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费
Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范 servlet/jsp。开源,免费
功能:就是负责接收和解析来自客户的请求,同时把客户的请求传送给响应的servlet,并把servlet的响应结果返回给客户
流程:
1.客户发出访问特定Servlet的请求
2.Servlet容器接收客户请求,解析请求
3.Servlet容器创建一个ServletRequest对象:包含客户所有请求信息。(如请求头,请求正文,客户端IP地址等)
4.Servlet容器创建一个ServletResponse对象
5.Servlet容器调用客户请求的Servlet的service()服务方法,并且把ServletRequest对象和ServletResponse对象作为参数传给该方法。
6.Servlet从ServletRequest对象获取客户的请求信息
7.Servlet利用ServletResponse对象生成响应结果
8.Servlet容器把Servlet生成的响应结果发送给客户
目录结构:
bin 存放tomcat启动停止等脚本
conf 相关配置文件
lib tomcat依赖包
logs 日志
webapps 默认应用部署目录
work web应用jsp代码生成和编译
处理http服务请求:
servlet容器以及servlet接口,是用来实现http服务器和业务类之间的解耦。(servlet接口和servlet容器这一套规范叫做servlet规范)
tomcat按照servlet规范的要求实现了servlet容器,具有HTTP服务器的功能,提供http访问的统一入口。
先建立TCP连接,然后再发送HTTP格式的数据包
两个核心组件:
连接器(对外)、容器(对内)
2. coyote连接器
coyote是tomcat连接器框架的名称,是tomcat提供的供客户端访问的外部接口,客户端通过coyote与服务器建立连接,发送请求并接收响应 。
coyote底层封装了底层的网络通信(支持socket请求以及响应的处理),为catalina容器提供了统一的接口,是的catalina容器与具体的请求协议和IO操作方式完全解耦。
coyote将socket输入转换封装成request对象,交给catalina处理,处理完成后,通过coyote提供的response对象将结果写入输出流。
coyote作为独立的模块,只负责具体的协议和IO相关的操作,和servlet规范的实现没有直接关系,coyote生成的response和request 是通过CoyoteAdapter的service方法进一步封装servletReuqest 和 servletResponse
IO模型:默认协议为NIO(非阻塞I/O)
还可支持NIO2(异步IO)、APR(采用Apache可移植运行库实现)
应用层协议:HTTP/2
还可支持HTTP/1.1(访问协议)、AJP(和web服务器集成)
tomcat为了实现支持多种IO模型和应用层协议,一个容器可以对应多个连接器,单独的容器或者连接器不能对外提供服务,需要组装起来才可以工作,组装后的整体叫做service组件。
tomcat支持配置多个service,这样可以实现通过不同端口号来访问同一台机器上部署的不同应用。
组件:
endpoint — processor — adapter
TCP/IP HTTP
1、endpoint:通信端点,就是监听通信的端口,是具体的socket接收和发送处理器,endpoint用来实现TCP/IP协议。 Tomcat 并没有EndPoint 接口,而是提供了一个抽象类AbstractEndpoint (NioEndpoint ,Nio2Endpoint ,AprEndpoint 都继承了AbstractEndpoint)
在AbstractEndpoint类中,重点关注两个内部类 :
1、Acceptor(tomcat10.1.1中已不是内部类)
Acceptor用于监听socket请求,代码在run()方法中
2、SocketProcessor
SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,
在run方法里调用协议处理组件Processor进行处理。
为了提高处理能力,SocketProcessor被提交到线程池来执行,这个线程池叫作执行器(Executor)
2、processer: coyote协议处理接口
Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,
并通过Adapter将其提交到容器处理, Processor是对应用层协议的抽象。
3、ProtocolHandler : Coyote 协议接口
( 通过Endpoint 和 Processor , 实现针对具体协议的处理能力)
可通过Service标签中连接器Connector 的属性 protocol="HTTP/1.1 指定协议名称
4、Adapter:将request,response 转换为 servletRequest, servletResponse
调用的是CoyoteAdapter中的service方法,对请求进行解析适配后,调用容器,其中getcontianer()返回的就是Engine对象
3.catalina(tomcat容器)
Tomcat 本质上就是一款 Servlet容器, 因此Catalina 才是 Tomcat 的核心 , 其他模块都是为Catalina 提供支撑的
- 通过Coyote 模块提供链接通信
- Jasper 模块提供JSP引擎
- Naming 提供JNDI 服务
- Juli 提供日志服务。
负责管理Server,而Server表示着整个服务器。
Server下面有多个服务Service,每个服务都包含着多个连接器组件ConnectorCoyote 实现)和一个容器组件Container。
在Tomcat 启动的时候, 会初始化一个Catalina的实例:
组件 | 职责 |
---|---|
Catalina | 解析tomcat配置文件,以此创建Server组件,并根据命令进行管理 |
Server | 表示整个Catalina Servlet容器以及其他组件(负责组装并启动Servlet引擎,tomcat连接器)(Lifecycle接口:提供更好的启动关闭系统的方式) |
Service | Server内部组件(将若干个Conncetor组件绑定到一个Container上) |
Connector | 连接器(处理与客户端的通信,负责接收客户请求,然后转给相关容器处理,最后返回响应结果) |
Container | 容器(处理用户serlvet请求,并返回对象给web用户模块) |
container容器结构:
非平行关系,是父子关系(通过一种分层的架构,使得Servlet容器具有很好的灵活性)(一个父可包括多个子,一个子只能有一个父)
容器 | 描述 |
---|---|
Engine | 表示整个Cataline的Servlet引擎(管理多个虚拟站点) |
Host | 代表一个虚拟主机/站点 |
Context | 代表一个Web应用程序 |
Wrapper | 代表一个Servlet |
Tomcat 采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件 按照一定的格式要求配置在这个顶层容器中。
这些可以在配置文件server.xml中进行配置
<Server>
</Server>
所有容器组件都实现了Container接口(一致性)
单容器对象指的是最底层的Wrapper
组合容器对象指的是上面的Context、Host或者Engine。
Container接口中有parent/child相关的方法,用于标识父子关系。
此外,Container接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期。
4.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的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求。
5.tomcat请求处理流程
Mapper组件实现了将用户请求的URL定位到一个Servlet的功能,使得tomcat实现每个请求都可以精准的找到对应的servlet。
Mapper组件工作原理:
Mapper组件里保存了Web应用的配置信息(即容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径)相当于一个多层次的Map
当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。
注意:一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet
具体步骤:
1、Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。
2、将连接交给线程池Executor处理,开始执行请求响应任务。
3、Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
4、Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
5、CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应对象Response传递到Engine容器中,调用 Pipeline。
6、Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的 Valve–StandardEngineValve,负责调用Host容器的Pipeline。
7、Host容器的管道开始处理,流程类似,最后执行 Context容器的Pipeline。
8、Context容器的管道开始处理,流程类似,最后执行 Wrapper容器的Pipeline。
9、Wrapper容器的管道开始处理,流程类似,最后执行 Wrapper容器对应的Servlet对象的处理方法。
在Tomcat中定义了Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链, Valve代表责任链上的每个处理器。
Pipeline 中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行),封装了具体的请求处理和输出响应的过程。
当然,我们也可以调用addValve()方法, 为Pipeline 添加其他的Valve, 后添加的Valve 位于基础的Valve之前,并按照添加顺序执行。
Pipiline通过获得首个Valve来启动整合链条的执行 。
6.tomcat中的JSP
JSP页面中编写 Java代码,添加第三方的标签库,以及使用EL表达式。但是无论经过何种形式的处理,最终输出到客户端的都是标准的HTML页面(包含js ,css…),并不包含任何的java相关的语法。 故jsp看做是一种运行在服务端的脚本。
- 服务器如何将 JSP页面转换为HTML页面?
Jasper模块是Tomcat的JSP核心引擎,JSP本质上是一个Servlet。
Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端 。
另外,在运行的时候,Jasper还会检测JSP文件是否修改,如果修改,则会重新编译JSP文件。
- JSP的编译方式
tomcat不会在启动web程序的时候自动编译jsp文件,而是在客户端第一次请求时,才编译需要访问的jsp文件(如果安装了Apache Ant,则可以使用ant进行预编译)
当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,并读取servlet注册信息。
然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化(Tomcat 创建servlet类实例的方法和原理)
Tomcat 在默认的web.xml 中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有的.jsp 或.jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。
- 编译过程
1、调用JspServlet的service方法,里面会获取jsp文件路径
如果界面没有传,会根据配置文件配置的servlet-mapping进行查找(上面xml里面有写)
判断当前请求是否是预编译请求,然后执行serviceJspFile()方法
2、 在serviceJspFile()方法中获取JspServletWrapper,然后调用JspServletWrapper的service方法
3、若是第一次调用,则需要编译,在JspServletWrapper的service方法中通过调用JspCompilationContext的compile()方法,再调用Compiler的compile()方法,生成Java文件和class文件。
4 、生成文件之后,JspServletWrapper的service方法加载编译并实例化之后的servlet,然后调用其service()方法,这个service方法就是编译生成的service方法。
编译结果:
如果在 tomcat/conf/web.xml 中配置了参数scratchdir , 则jsp编译后的结果,就会存储在该目录下。
如果没有配置该选项, 则会将编译后的结果,存储在Tomcat安装目录下的work/Catalina(Engine名称)/localhost(Host名称)/Context名称
如果使用的是 IDEA 开发工具集成Tomcat 访问web工程中的jsp , 编译后的结果,存放在 :C:\Users\用户名\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_Tomcat8_0\work\Catalina\localhost\jsp_demo\org\apache\jsp
- Jasper编译文件简介
分析:
- 编译后的类继承自 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 输出。