Tomcat源码学习

目录

    • 环境搭建
      • 源码下载
      • 环境配置
        • 增加Maven的配置
        • 增加启动类
    • 源码分析
      • 整体架构
        • 整体架构图示
        • Server
        • service
        • Connector
        • Engine
        • Host
        • Context
        • Wrapper
      • Tomcat的生命周期模板
        • Lifecycle
        • LifecycleBase
        • Container
      • Tomcat的启动流程
        • 启动流程图
        • BootStrap的初始化
        • 三个类加载器
        • 解析server.xml文件
        • 服务器初始化
        • 连接器初始化
      • Tomcat的请求处理

环境搭建

我们研究的版本是tomcat9(之前spring版本的研究也对应的tomcat9)

源码下载

tomcat9下载传送门

环境配置

增加Maven的配置

在解压后的源码中放入一个内容如下的pom文件,其中的版本号跟随你自己下载的tomcat版本号自行调整


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>org.apache.tomcatgroupId>
    <artifactId>tomcat9artifactId>
    <name>tomcat-9.0.41name>
    <version>9.0.41version>
    <build>
        <finalName>tomcat-9.0.41finalName>
        <sourceDirectory>javasourceDirectory>
        
        <resources>
            <resource>
                <directory>javadirectory>
            resource>
        resources>
        <testResources>
            <testResource>
                <directory>testdirectory>
            testResource>
        testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.6.0version>
                <configuration>
                    <encoding>UTF-8encoding>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-jar-pluginartifactId>
                <version>3.0.2version>
            plugin>
        plugins>
    build>
     <dependencies>
        <dependency>
            <groupId>org.apache.antgroupId>
            <artifactId>antartifactId>
            <version>1.9.5version>
        dependency>
         
         
         <dependency>
             <groupId>biz.aQute.bndgroupId>
             <artifactId>biz.aQute.bndlibartifactId>
             <version>5.2.0version>
             <scope>providedscope>
         dependency>
 
         
         
         <dependency>
             <groupId>org.apache.tomcatgroupId>
             <artifactId>tomcat-jasperartifactId>
             <version>9.0.41version>
         dependency>
         <dependency>
            <groupId>org.apache.antgroupId>
            <artifactId>ant-apache-log4jartifactId>
            <version>1.9.5version>
        dependency>
        <dependency>
            <groupId>org.apache.antgroupId>
            <artifactId>ant-commons-loggingartifactId>
            <version>1.9.5version>
        dependency>
        <dependency>
            <groupId>javax.xml.rpcgroupId>
            <artifactId>javax.xml.rpc-apiartifactId>
            <version>1.1version>
        dependency>
        <dependency>
            <groupId>wsdl4jgroupId>
            <artifactId>wsdl4jartifactId>
            <version>1.6.2version>
        dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compilergroupId>
            <artifactId>ecjartifactId>
            <version>4.6.1version>
        dependency>
    dependencies>
project>

增加启动类

创建启动类,mainClass指定为tomcat的Bootstrap,增加以下的vm options解决乱码的问题

-Duser.language=en
-Duser.region=US
-Dfile.encoding=UTF-8
-Dsun.jnu.encoding=UTF-8

源码分析

整体架构

从conf下的server.xml开始分析(结合源码),逐步得到整体的包含关系,每一个标签都有对应的类(一般是接口,tomcat对其做了抽象封装)



<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  
  <GlobalNamingResources>
    
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  GlobalNamingResources>
  
  
  <Service name="Catalina">
    
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    
    <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">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />

      Host>
    Engine>
  Service>
Server>

整体架构图示

Tomcat源码学习_第1张图片
Tomcat源码学习_第2张图片

Server

server即是tomcat,其内包含一些监听器,可以指定一些资源的配置信息,最重要的是他包含了service进来

service

service服务包含了Connector连接器和Engine引擎两个重要组件

Connector

连接器能够进行端口的监听,获取到请求

Engine

引擎能够进行请求的处理,其内包含了Host

Host

虚拟主机,可以映射域名,它内部包含了Context

Context

他就是一个个的web应用(咱们部署的包),它内部包含了Wrapper

Wrapper

每一个Wrapper都封装了一个Servlet的配置详情

Tomcat的生命周期模板

Lifecycle

他是tomcat的生命周期接口,基本上上述组件都实现了他,类图如下:
Tomcat源码学习_第3张图片

LifecycleBase

Lifecycle的具体实现类,利用模板模式,定义好整体的算法步骤,核心的实现留给组件自己去实现

Container

容器接口,上述的Host,Engine,Context,Wrapper等都是他的实现,容器可以继续添加子组件,并且容器中有一个特性是拥有一个管道Pipeline(请求必须要经历每一层容器的管道,里面的所有的阀门就可以来进行干预)

比如Host中的AccessLogValve日志阀门,可以用来记录请求的日志,阀门机制是Tomcat中,控制数据流向处理的核心,利用阀门来做类似Filter的预处理,阀门属于一个责任链的模式
容器的特点:

  • addChildren(Container child):有子容器
  • Container getParent():有父容器
  • Pipeline getPipeline():有管道
  • Pipeline里面有Valve阀门,阀门形成链式调用可以对请求进行预处理

Tomcat的启动流程

  1. BootStrap.init()方法:准备三个类加载器,拿到Catalina(反射创建实例)类
  2. 解析需要执行的命令(没有的话默认就是start):daemon.load(args)加载信息,通过反射执行catalina.load方法
  • 解析服务器的server.xml文件
  • 初始化输出流和错误流
  • getServer().init(),执行服务器的初始化方法
  1. daemon.start();执行服务器的启动方法,通过反射调用catalina.start方法
  • 拿到服务器,然后进入到服务器的启动流程getServer().start()
  • 跟服务器初始化流程基本一模一样,也是一层层的把上一步创建的组件调用其生命周期的启动方法
  • Engine的启动会找到所有的虚拟主机Host,把Host封装为StartChild(Callable类),交给线程池去执行,并发的启动
  1. 服务启动完毕后调用getServer().await(),一直while循环接收数据,监听8005端口,接收命令

启动流程图

Tomcat源码学习_第4张图片

BootStrap的初始化

首先调用BootStrap的init方法进行初始化(加了锁的),并且用volatile修饰了BootStrap的实例变量daemon

三个类加载器

准备common,server和shared三个类加载器(我实际断点调试时发现全部返回的是父类URLClassLoader类型)

commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);

解析server.xml文件

配置文件的每一个标签就对应这一个类,创建对象封装好标签配置的属性内容,Host里面并不会在此时扫描加载Context应用组件(只是单纯的解析xml中的架构,按照层次封装到Catalina对象中)

服务器初始化

getServer().init()执行服务器的初始化,他最终调用到了子类StandardServerinitInternal()初始化方法,具体的步骤如下

  • globalNamingResources.init();JNDI功能初始化
  • 循环遍历service,调用service的初始化方法service.init()
  • service的初始化方法中,去调用Engine引擎的初始化方法engine.init()
  • Engine初始化之后,再让Connector连接器初始化connector.init()
  • 连接器的初始化重点是协议处理器的初始化protocolHandler.init():他最重要的是endpoint.init()端口的初始化,将ServerSocket绑定好
  • 至此层层返回,初始化流程整个完成

整个初始化的逻辑,是一层一层嵌套的,他的顺序就是上面讲过的包含关系,一层层传递下去

连接器初始化

connector在初始化时准备好了ProtocolHandler协议处理器和CoyoteAdapter适配器两个组件(新版本的tomcat默认采用的都是http/1.1 的NIO协议),之后他开始调用ProtocolHandler.start()方法,协议处理器又调用了endpoint.start()断点启动方法:

  • 创建启动worker线程池(默认是10个,这里能联系我之前那篇tomcat调优看,这里会去拿最小处理线程数和最大处理线程数的参数)
  • 进行最大连接数maxConnections的限制,最大为8*1024=8192条连接(但是不建议参数设置过大,根据具体机器配置调整)
  • 创建启动一个Poller拉取者单线程,然后在创建启动一个Acceptor接受者单线程

Acceptor一直循环接收端口来的数据(serverSocket.accept,端口开启一个线程在后台一直接收数据),监听8080端口

Tomcat的请求处理

  1. Acceptor一直接收8080的请求,会尝试endpoint.setSocketOptions(socket)
  2. socket封装为socketWrapper,此时poller会先来注册事件 socketWrapper
  3. 创建一个PollerEvent,添加事件队列到SynchronizedQueue
  4. Poller一直判断是否有事件.有的话就触发events.poll()
  5. 读取socket的内容并处理processSocket(socketWrapper),poller会拿到worker线程池,把socketWrapper封装到SocketProcessorBase里面,把这个SocketProcessorBase直接扔给线程池
  6. SocketProcessorBase会被线程池的一个线程处理,最终会被ConnectorHandler.process进行处理,交给Http11Processor.process进行处理,最终Http11Processor的service方法最终处理这个request(最终会层层调用到servlet的service方法)

你可能感兴趣的:(tomcat,Java源码解读系列,tomcat,学习,java)