前言
本篇文章 会针对tomcat的实现原理,以及servlet Tomcat容器与运行机制,利用servlet的规范实现的一套web服务器,而spring mvc 也是 spring实现了servlet 的web应用程序。 tomcat这么流行 的Servlet Web容器,具有大量的配置可以扩展 良好的运行效果,在开发中不应该只是会用,更重要的是对原理以及实现方式的理解,也许最后你也能写出很好的一个tomcat框架,也是为什么去研究他的原因。
Tomcat介绍
Tomcat
实现了
Java Servlet
,
JavaServer Page
,
Java
表达式语言和
Java
的
WebSocket
技术的一个开源实 现。说白了就是一个非常流行的Servlet Web
容器。
![](https://i-blog.csdnimg.cn/blog_migrate/86008bc9573683715d578061c4f8b061.png)
在9.0.40 根据 不同的安装包去下载不同的安装包。
![](https://i-blog.csdnimg.cn/blog_migrate/c9f62b0355c2254194a7059ddaf9726d.png)
下载过后 的关键目录
1.
lib
Tomcat
依赖的
jar
包
2.
logs
catalina-xxx.log
localhost-xxx.log
3.
webapps
web
应用部署目录,
eclipse
中的配置演示
![](https://i-blog.csdnimg.cn/blog_migrate/58a3714d507aae6ed7f95ef645511d8b.png)
在bin目录中 包括的 bat 文件启动的 对应的 win版本的启动以及 linux的启动方式。
我们要开发中间件 都参考这种形式去开发就可以了。
![](https://i-blog.csdnimg.cn/blog_migrate/e9c7d22fc816246faf294e79431538bf.png)
config目录 这里面 的web.xml 以及 catalina.policy 设置运行jvm的 权限 等参数 都放到 这里面。
![](https://i-blog.csdnimg.cn/blog_migrate/b63da0357dd15dd3616bf178e16e21da.png)
在 policy 中 包括了 shutdown 以及 juc日志等。
![](https://i-blog.csdnimg.cn/blog_migrate/ec3d69d8044c5e18de669adb5d6f024a.png)
配置文件
jconsol
调试参数,观察参数变化带来的影响
1. server.xml
调整连接池的大小
设置
I/O
模式
(BIO
与
NIO)
去掉
AJP
的
Connector
移除
access-log
日志
server 里面设置监听器等等。
以及 端口号 以及转发端口号
执行器 线程池,重新定义 数量等等。
对应的默认 host 主机名称等 都在server.xml中配置。
![](https://i-blog.csdnimg.cn/blog_migrate/b466f4b451c2c0e4bed31df685e15674.png)
server.xml 中可以 设置的。
阈值 需要处理的事情。
区分多个应用 还是有个 上下文 context 上下文 在 context里面 最终进行映射 ,这里可以使用这个 作为访问 到 其他目录下面的 路径。 默认 是 root 访问到 工作目录下 ,然后可以根据配置 可以 访问到其他目录下面 其实这里是可以配置到 其他目录下面的 访问的。 包括 f盘或者其他的。
2. web.xml
servlet
标签:
DefaultServlet
、
JspServlet
servlet-mapping
标签:
servlet
的访问路径
mime-mapping
标签,支持的内容类型:
json
、
xml
、
html
、
jpg
等
![](https://i-blog.csdnimg.cn/blog_migrate/284d4e9df4bd77cfd28b74016770f2c7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/264a2219d21e2b4eb75ebcecec5fd7c1.png)
对于jsp 的请求访问 处理 方式等。
![](https://i-blog.csdnimg.cn/blog_migrate/95f5e1cbfaef02a5667894a50e82a068.png)
在 web.xml中可以 设置的json,能够解读 对应的映射地方 在 tomcat中 自动给我们设置好的。
![](https://i-blog.csdnimg.cn/blog_migrate/e009a8c32882d4dd19f3f218d59da75e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2d9d01fcbc1ee8f13b50f5038ba3229d.png)
tomcat-users.xml 配置相关的权限等。配置方式 在 文件上 都有一个例子给我们使用。
![](https://i-blog.csdnimg.cn/blog_migrate/6d9ee73066c2242d0a24fb1a74bab6f0.png)
然后以及经常使用的地方也就是 webapps 启动 到 对应的 项目上
![](https://i-blog.csdnimg.cn/blog_migrate/b53189f0f4b66d9da51eafa4dc1f9a46.png)
在使用上可以配置 对应 的访问地址,这都是tomcat提供给我们进行项目地址
![](https://i-blog.csdnimg.cn/blog_migrate/fd8d698dfdce9cd152d4d296e43706fa.png)
jmx是jdk给我们提供的管理插件的功能,
![](https://i-blog.csdnimg.cn/blog_migrate/0e1a951adbddf72925336443c78b1c35.png)
部署方式
显式部署
1.
添加
context
元素方式
(server.xml)
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Context path="/Demo1" docBase="d:/Demo1" reloadable="true"></Context>
</Host>
2.
创建
xml
的方式
在
conf/Catalina/localhost
目录创建的
xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="d:/Demo1" reloadable="true"></Context>
能访问到其他目录下面。
隐式部署
将一个
war
文件或者整个应用程序复制到
Tomcat
的
webapps
Tomcat源码下载与构建
IDE
装载
Tomcat
项目源码
![](https://i-blog.csdnimg.cn/blog_migrate/5cdf4003485661df550e4e23be30eaec.png)
需要添加 环境变量这些。
Tomcat
体系结构
![](https://i-blog.csdnimg.cn/blog_migrate/db8062d582483ef2ded18a1829edcc7a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dc68a4620f102d90115fb6d3f7ed29c7.png)
Server
在
Tomcat
中,
Server
代表整个容器。
Service
服务是一个中间组件,它在服务器内部,将一个或多个连接器连接到一个引擎。
Engine
Engine
也就是以前版本中的
Container
。引擎表示特定服务的请求处理管道。 服务可能有多个连接器, 引擎接收并处理来自这些连接器的所有请求,将响应返回到适当的连接器以传输到客户端。 引擎接口可 以实现,以提供自定义引擎,尽管这是不常见的。引擎可以通过jvm
路由参数用于
Tomcat
服务器集群。
Engine
包含:
Host
、
Context
、
Wrapper
这几个容器,他们都是
Container
子类型。
Connerctor
Connector
负责把接收到的请求解析出来然后封装成
request
和
response
对象然后交给
Container
处理。 目前Connector
支持
http
和
ajp
协议。 连接器处理与客户端的通信。 Tomcat
有多个可用的连接器。 其中包括
HTTP
连接器、
AJP
连接器。当将 Tomcat作为独立服务器运行时使用
HTTP
协议的
HTTP
连接器;当将
Tomcat
连接到
Apache HTTPD
服务器等Web
服务器时使用的
AJP
协议的
AJP
连接器。 还可以创建自定义连接器。
Host
主机是网络名称的关联。 例如:
www.yourcompany.com
能访问到
Tomcat
服务器。引擎可能包含多个主机,主机元素还支持网络别名,如yourcompany.com
和
abc.yourcompany.com
。 用户很少创建自定义主机,因为标准主机实现提供了重要的附加功能。
Host
说白了就是我们所理解的虚拟主机。
Context
上下文表示
Web
应用程序。 主机可能包含多个上下文,每个上下文具有唯一的路径。 上下文接口可以 实现创建自定义上下文,但这种情况很少,因为标准上下文提供了重要的附加功能。
Context
就是我们所部属的具体
Web
应用的上下文,每个请求都在是相应的上下文里处理的。
Wrapper
Wrapper
是针对每个
Servlet
的
Container
,每个
Servlet
都有相应的
Wrapper
来管理。
Tomcat容器与运行机制
Tomcat
启动步骤
Tomcat
的启动方式有下面几种方式:
1.
通过命令行启动
2.
通过
Java
程序,作为一个内嵌服务启动
3.
作为
windows
服务自动启动
bootstrap 作为启动类,
赋值catlina.bat 调用格式, start参数, 其实 start.bat 调用时,都是调用的catalina.bat,这个可以在脚本中就可以看出来
![](https://i-blog.csdnimg.cn/blog_migrate/69def763cd02143eacbc1921c28f2d8b.png)
这里对于主调用的 启动类就是 bootstrap
在tomcat中 的启动类
![](https://i-blog.csdnimg.cn/blog_migrate/36f1e4bbec9b74b4cb74796826de9c4e.png)
对于参数来说, 传入的参数 第一个参数就是 start
![](https://i-blog.csdnimg.cn/blog_migrate/44ef4c32f4cf656a095abe5e1b5f2f4b.png)
启动 最终底层 都调用到了bootstarp
做了初始化的操作 init
![](https://i-blog.csdnimg.cn/blog_migrate/37b605b00fd8ddb851882e8a29b3fd86.png)
继续 加载 catalina
![](https://i-blog.csdnimg.cn/blog_migrate/bcc762c25dba636cbee25409e56a337e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7d90d74889fd264f6be23583f9b639a3.png)
紧接着 继续下去
![](https://i-blog.csdnimg.cn/blog_migrate/69ec2e41e10a0d5363abf3178e51208b.png)
根据不同的 命令 选择 不同的方式进行方式。
![](https://i-blog.csdnimg.cn/blog_migrate/e680f12616e136f315899cd2fbc6eb2a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/312a22a0ac2e2dbf8779024bdb129aa0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/cf82b51d52315d1d36eaa8888c15227c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3ffd36717ec8681c76d0ef8beafb378d.png)
然后继续下去 就是 初始化 命名 ,
![](https://i-blog.csdnimg.cn/blog_migrate/3dd51c1d6a8198ed46d907aed1b6c195.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b88a2bfca92d9d331191df3536d13184.png)
设置命名空间等等。 初始化的factroy
解析 对应的serverxml 并设置 home 的file
![](https://i-blog.csdnimg.cn/blog_migrate/4f4c11513bc8e4fcbccb21afeee70b42.png)
创建 摘要器等 初始化部分。
![](https://i-blog.csdnimg.cn/blog_migrate/0dbe82d58ff9f74c3cbb0c985036ce4d.png)
Bootstrap
Tomcat
启动类,
Tomcat
启动都是通过
org.apache.catalina.startup.Bootstrap
类的
main
方法来进行启 动的。它所做的工作包括:
commonLoader (common)-> System Loader sharedLoader (shared)-> commonLoader ->
System Loader catalinaLoader(server) -> commonLoader -> System Loader
默认,
commonLoader
用于
sharedLoader
和
catalinaLoader
org.apache.catalina.startup.Catalina setParentClassloader -> sharedLoader
Thread.contextClassloader -> catalinaLoader
- 3. Bootstrap的守护线程初始化方法执行完成
步骤
2
:处理命令行参数
Bootstrap
处理
start
、
stop
这样的启动命令参数,这里以
start
参数解释启动步骤。工作流程如下:
1. Catalina.setAwait(true)
2. Catalina.load()
catalina.home = D:/tomcat
catalina.base == catalina.home
setProperty
(
javax
.
naming
.
Context
.
INITIAL_CONTEXT_FACTORY
,
org
.
apache
.
naming
.
java
.
javaURLContextFactory
-
>
default
)
- 3. createStartDigester()
- 4. 加载server.xml并解析
自动的使用
digester
进行
server.xml
的解析
XML
对象映射工具,它将创建
server.xml
实际上此时容器的启动尚未开始。
将
System.out
、
System.err
绑定到
SystemLogHandler
类上
- 6. 调用所有的组件初始化方法,确保每个对象都注册到了JMX代理上
在这个过程中,连接器也初始化适配器。适配器是执行请求预处理的组件。典型的适配器是
HTTP1.1
(如果没有指定协议,则默认为,
org.apache.coyote.http11.Http11NioProtocol
)
1. 启动NamingContext绑定所有JNDI引用到NamingContext中
2. 启动<Server>标签下的service
3. 通过Service启动StandardHost
- 配置ErrorReportValve以对不同的HTTP错误代码执行正确的HTML输出
- 启动管道中的阀门Valve(至少是ErrorReportValve)
- 配置StandardHostValve
这个阀门将Webapp类加载器绑定到线程上下文
它还会找到请求的会话
并调用上下文管道
此组件部署所有webapps
,
(webapps & conf/Catalina/localhost/*.xml)
HostConfig将为你的上下文创建一个
Digester
摘要器,这个
Digester
将调研
ContextConfig.start()方法
ContextConfig.start()将会处理默认的
web.xml(conf/web.xml)
,然后再处理应用的
web.xml (WEB-INF/web.xml)
在容器(
StandardEngine
)的生存期内,有一个后台线程不断检查上下文是否已更改。如果
上下文发生变化(
war
包的时间戳,
context.xml
文件,
web.xml
文件
)
,然后发出重新加载
(
stop/remove/deploy/start
)
4. Tomcat 在HTTP端口上开始接受请求
5. Servlet类调用
Tomcat处理请求的过程
- 1. 用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。
- 2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
- 3. Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。
- 4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的 Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为“ ”的Context 去处理)。 path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的 Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。
- 5. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet() 或doPost().执行业务逻辑、数据存储等程序。
- 6. Context把执行完之后的HttpServletResponse对象返回给Host。
- 7. Host把HttpServletResponse对象返回给Engine。
- 8. Engine把HttpServletResponse对象返回Connector。
- 9. 1Connector把HttpServletResponse对象返回给客户Browser。
Tomcat中的组件
JVM
类加载器及双亲委派
JVM
类加载器结构
public class Main {
public static void main(String[] args) {
Main main = new Main();
System.out.println(main.getClass().getClassLoader());
System.out.println(main.getClass().getClassLoader().getParent());
System.out.println(main.getClass().getClassLoader().getParent().getParent());
}
}
双亲委派机制
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
双亲委派优势
Java
类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重
复加载,当父亲已经加载了该类时,子
ClassLoader
就没有必要再加载一次。
java
核心
api
中定义类型不会被随意替换,假设通过网络传递一个名为
java.lang.Integer
的类,通过
双亲委托模式传递到启动类加载器,而启动类加载器在核心
Java API
发现这个名字的类,发现该类
已被加载,并不会重新加载网络传递的过来的
java.lang.Integer
,而直接返回已加载过的
Integer.class
,这样便可以防止核心
API
库被随意篡改。
由于
jre\lib\ext
中存在
java.lang.String
类,当加载该类的时候,根据全限定名进行查找,找到后
由启动类加载器加载,发现
String
类中不包含
main()
方法,因此程序出错。
Tomcat
的类加载器结构
Catalina
类加载器和
Shared
类加载器,他们并不是父子关系,而是兄弟关系。
1.
保证应用程序各自类库的独立隔离
,一个
Web
容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
2.
共享相同版本类库
,部署在同一个
Web
容器中相同的类库相同的版本可以共享。否则,如果服务器有10
个应用程序,那么要有
10
份相同的类库加载进虚拟机,显然不合适。
3.
安全
-
隔离
Web
容器与应用程序类库
,
Web
容器也有自己依赖的类库,不能于应用程序的类库混
淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4.
支持
JSP
热修改
,
Web
容器要支持
jsp
的修改,
jsp
文件最终编译成
class
文件才能在虚拟机中运行,在程序运行后修改jsp
是常见的事,否则要你何用? 所以,
web
容器需要支持
jsp 修改后不用重启。
打破双亲委派机制
WebAppClassLoader
与
JasperLoader
查找类则不会先往上查找,而是直接在自己类加载器中进行查找,不遵循双亲委派机制。
为什么不遵循双亲委派机制
?
1.
如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管 你是什么版本的,只在乎你的全限定类名,并且只有一份。tomcat
为了实现隔离性,没有遵守双亲委派机制.
2.
每个
webappClassLoader
加载自己的目录下的
class
文件,不会传递给父类加载器。
3.
每个
jsp
文件对应一个唯一的类加载器,当一个
jsp
文件修改了,就直接卸载这个
jsp
类加载器。重新创建类加载器,重新加载jsp
文件。双亲委派机制无法做到。
所以双亲委派机制不能满足
Tomcat
的需求。
如果
Tomcat
的
CommonClassLoader
想加载
WebAppClassLoader
中的类,该怎么办?
可以使用线程上下文类加载器实现,使用线程上下文加载器,让父类加载器请求子类加载器去完成类加载的动作。
Catalina
Catalina
是
Tomcat
的核心组件,是
Servlet
容器,
Catalina
包含了所有的容器组件,其他模块均为
Catalina
提供支撑。通过
Coyote
模块提供连接通信,
Jasper
模块提供
JSP
引擎,
Naming
提供
JNDI
服务,Juli提供日志服务。结构如下:
![](https://i-blog.csdnimg.cn/blog_migrate/2025958f9b67fc1400766f0c2eaa0bcb.png)
主要的功能包括接收请求,处理请求,返回结果,这些具体的实现是在catalina里面的子容器里面。
Lifecycle
Tomcat
的卡特琳娜(
Catalina
)由许多组件构成。当
Catalina
启动的时候,这些组件也要跟着一起启动,并且当Catalina
关闭的时候,这些组件也要同时关闭,并且要进行必要的清理操作,释放资源。例如,当容器关闭的时候,需要调用已加载的servlet
对象的
destroy
方法,
session
对象也要持久化到secondary storage(二级存储,通常指的就是硬盘)。这就要求所有
Component
有一致的方法,可以统一处理。