学习tomcat(๑˃̵ᴗ˂̵)و ヨシ!


大部分的中间件或者框架都有一些共性的部分,例如多线程、反射和类加载等。所以深入研究一两个中间件的话,面对其他的中间件,那么就会很容易理解它里面所用的技术以及原理。

tomcat目录结构:

img

bin:startup和shutdown脚本,sh—>linux,bat—>windows

lib:依赖jar包

logs:日志

temp:临时文件

webapps:web应用目录

work:tomcat的运行工作目录(通过webapps中的项目生成的。当客户端用户访问一个jsp文件时,tomcat会通过jsp生成java文件,然后再编译java文件生成class文件,生成的java和class文件都会存放到这个目录下。

conf:存放一些配置文件 像service.xml、web.xml

看一下service.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Service name="Catalina">
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
-->
<Engine name="Catalina" defaultHost="localhost">

<Realm className="org.apache.catalina.realm.LockOutRealm">

<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>

<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">

</Host>
</Engine>
</Service>
</Server>

可以从配置文件中看到一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connector(就是因为一个服务可以有多个连接,如同时提供Http和Https,也可以提供向相同协议不同端口的连接)。

tomcat连接器:

连接器的功能(可以从配置文件中里看到:

  • 建立Socket连接,读取请求网络中的字节流。
  • 根据相应的应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象并传给容器。
  • 将容器返回的Tomcat Response对象转换为字节流并返回给客户端。

tomcat容器组件:

tomcat的容器就是来装Servlet的,容器的所有组件都实现了Container接口。

从配置文件中我们可以看到容器中的组件以及其关系:

Engine、Host、Context和Wrapper(配置文件中没。

从上至下依次是:

Engine—>Host—>Context—>Wrapper—>Servlet

如图:

img

  1. Engine,它对应的是顶层管理组件,一个Service最多只能有一个Engine,实现类为 org.apache.catalina.core.StandardEngine
  2. Host,它对应的是虚拟主机,一个 Host 代表一个虚拟主机,通过配置Host就可以添加一个主机,实现类为 org.apache.catalina.core.StandardHost
  3. Context,它对应的是webapp里面的每个应用文件夹,一个 Web 应用对应一个Context,实现类为 org.apache.catalina.core.StandardContext。
  4. Wrapper,它对应的是Context中的所有servlet,一个 Wrapper 代表一个 Servlet,管理访问关系与具体的Servlet的对应。实现类为org.apache.catalina.core.StandardWrapper。

※Container处理请求是使用Pipeline-Valve管道来处理的(类似数据结构中树形结构?),所以无论添加多少个Host或者多少个Context都可以找到对应的Servlet。

tomcat处理请求流程:

假设webapp下部署一个应用didistudy。

访问http://localhost:8080/didistudy/tomcat.xx是如何找到对应Servlet进行处理的流程如下:

  • 发送请求到8080端口,连接器Connector获得请求

  • Connector把字节流转换为ServletRequest对象,然后交给Service下的容器组件Engine进行处理

  • Engine获得地址后然后匹配他下面名为localhost的Host主机

  • Host匹配到路径为/didistudy的Context(即在webapp下面找到相应的文件夹

  • Context匹配到URL规则为*.xx的servlet(即Wrapper对应为某个Servlet类

  • 调用其doGet/doPost方法

  • Servlet执行完后将对象依次返回,Context—>Host—>Engine—>连接器Connector—>将对象解析为字节流发送给客户端

类加载器:

java类加载器

基本概念:

java源文件(.java):里面就是我们写的代码,但是不能被java虚拟机识别。

java类文件(.class):由java文件通过 javac这个命令(jdk本身提供的工具)编译生成,本质是一种二进制字节码文件,可以由java虚拟机加载(类加载)然后转换成 java.lang.Class 类的一个实例。

java虚拟机(JVM):识别.class文件,可以把.class文件加载到内存中,生成对应的java对象。还有内存管理、程序优化、锁管理等功能。所有的java程序最终都运行在jvm之上。

img

java运行过程:

java源文件(.java)—>通过编译器编译—>java类文件(.class)—>通过类加载器加载—>JVM class对象

类加载的触发:

JVM运行过程中,首先会加载初始类Main.class,然后再从初始类链接触发它相关的类的加载(如果Main.class中引用了A和C,就会触发A和C的加载,而没有引用B,B就不会被加载)。

通过打断点的方式看一下类加载流程

img

类加载器的介绍:

分为启动类加载器(BootStrapClassloader)和用户自定义类加载器。

**启动类加载器(BootstrapClassLoader)**:

使用C/C++代码写的,封装到JVM内核中,是虚拟机的一部分,用来加载JVM提供的基础运行类,即位于%JAVA_HOME%jre/lib 这个目录下面的核心类库,如包名为java、javax、sun等开头的类。

用户自定义类加载器:

由Java语言实现,独立于虚拟机外部并且全都继承抽象类java.lang.ClassLoader。

  1. java库中的平台类加载器PlatformClassLoader和应用程序类加载器AppClassLoader等。
  2. 自己写的类加载器,比如通过网络加载类等机制,一般来说,用户自定义类加载器以ClassLoader为基类,重写其中的findClass,使findClass可以从用户指定的位置读取字节码.class文件。

※上面提到的ClassLoader类是所有类加载器的基类,完成类的加载工作是通过调用 defineClass 来实现的;而启动类的加载过程是通过调用 loadClass 来实现的。

方法 说明
getParent() 返回该类加载器的父类加载器(下文介绍的双亲委派模型会用到)。
findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例()。
loadClass(String name) 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。和findClass的不同之处在于:loadClass添加了双亲委派和判断
findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的
resolveClass(Class<?> c) 链接指定的 Java 类。

※java中的类加载默认是采用双亲委派模型,即加载一个类时,首先判断自身define加载器有没有加载过此类,如果加载了直接获取class对象,如果没有查到,则交给加载器的父类加载器去重复上面过程。

由下到上的关系;

用户类加载器—>AppClassLoader—>PlatformClassLoader—>BootstrapClassLoader

很多具体的可以看看https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html

tomcat的类加载器:

来看一下Tomcat9版本的官方文档给出的Tomcat的类加载器图:

https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html

imgimg

  • Bootstrap : 加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)。
  • System: 加载tomcat启动的类,比如bootstrap.jar,tomcat-guli.jar。通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
  • Common:加载tomcat自身使用以及部署在其上面的应用程序通用的一些类,加载路径可以在catalina.properties中common.loader属性配置。
  • Server:负责加载Tomcat专用的类。
  • Shared:负责加载Tomcat下所有的Web应用程序的类。
  • Webapp1、Webapp2……: 会加载webapp路径下项目中的所有的类。一个项目对应一个WebappClassLoader,这样就实现了应用之间类的隔离了。

※上面有提到WebAppClassLoaser,它是各个Webapp私有的类加载器,该加载器加载的类库不仅包括每个web应用下/WEB-INF/classes目录下的所有.class类和资源文件,还包括web应用下/WEB-INF/lib目录下所有的jar类库。加载路径中的class只对当前Webapp可见。

tomcat的类加载流程:

写个简单的servlet,然后对类加载器信息进行在浏览器页面输出即可。

img

img

可以看出这些类加载器的关系。

不过tomcat程序的加载类的过程与JVM加载类的双亲委托机制是有些差异的。

在web应用加载某个类的时候,WebappX类加载器首先尝试加载该类,其余的类加载器都会按照JVM中的双亲委托模型来加载该类,如果在配置文件中配置了,那么就是遵循双亲委派规则,最后WebappX类加载器加载。

那tomcat的类加载器加载类的过程总结就如下:

Bootstrap(包含JVM的bootstrapClassLoader,extClassLoader加载的所有基础的,依赖的类)

->/WEB-INF/Classes/*.class(WebappClassLoader )

->/WEB-INF/lib/*.jar(WebappClassLoader)

-> System tomcat(与JVM中的System类加载器appClassLoader不同)加载的相关类集合 (%CATALINA_HOME%\bin\catalina.bat)

-> Common加载的相关类集合(%CATALINA_HOME%\conf\catalina.properties)

tomcat的war包部署:

.war里面包括写的代码编译成的class文件等等。

在Tomcat的代码中有一个判断,只有当程序为.war结尾时才会去走以下的逻辑。用.war包结尾的就被Tomcat视为一个项目。(注意:但是将其打包为.jar包放在Tomcat文件夹下是无法生效的,因为在Java中一般.jar包是一个依赖包而且源码中也有文件类型判断。)

image-20220509214812170

这里有两种模式

war模式:将WEB工程以war包的形式上传到Tomcat服务器 ,存放于服务器的webapps目录下。
war exploded模式:将WEB工程以当前文件夹的位置关系上传到服务器,包括文件夹、jsp页面 、classes等等移到Tomcat 部署文件夹里面,进行加载部署。(因此这种方式支持热部署)。

tomcat部署war包getshell,不过有时候war包还不一定有效,如果没开热部署,就不会自动解压了。

这里提到两个概念:

热部署(由host负责):Tomcat在启动的时候会将其目录下webapp中war包解压后然后封装为一个Context供外部访问。那么热部署就是在程序运行时,如果我们修改了War包中的东西。那么Tomcat就会删除之前的War包解压的文件夹,重新解压新的War包。

热加载(由Context负责):Tomcat默认情况下是不开启热加载的。服务器会监听 class 文件改变,包括web-inf/class,wen-inf/lib,web-inf/web.xml等文件,若发生更改,则局部进行加载,不清空session ,不释放内存。

tomcat在host中做WAR解压和部署相关的工作。

HostConfig实现了LifecycleListener接口,它是一个监听器,作用是部署应用。

看一下它其中deployApps的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void deployApps() {

File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);

}

再进一步看一下deployWARs的代码

image-20220509222523416

tomca创建servlet类实例:

当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,并读取servlet注册信息。然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化。

参考文章:

https://www.jianshu.com/p/efd19125255c

https://juejin.cn/post/6844903881067986957

https://cloud.tencent.com/developer/article/1913028?from=article.detail.1890176

https://blog.csdn.net/m0_45406092/article/details/111413229


文章作者: didi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 didi !
  目录