coding……
但行好事 莫问前程

Tomcat源码解读『基础类介绍』

通过之前两篇关于Tomcat的介绍,我们已经清楚知道Tomcat的作用及基本的工作原理。本篇文章开始,我们来从解读Tomcat源码。本篇先来介绍一下Tomcat的基础类,以便我们后续介绍Tomcat启动流程,工作流程。

我们知道,Tomcat的能作为web容器正常工作,依赖于Server.xml配置文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>

上篇文章我们也讲过,上述xml配置节点,基本都是Tomcat的基础组件。比如Server、Service、Connector、Container、Engine、Host、Context等。也在上篇文章大致介绍了上述组件的基本功能。我们今天要介绍的Tomcat基础类,就是Tomcat源码中对上述组件的抽象和实现。

1. Server

Server 组件的具体实现类是 StandardServer。它的子组件是Service,因此它还需要管理Service的生命周期,也就是说在启动时调用Service组件的启动方法,在停止时调用Service组件的停止方法。Server在内部维护了若干 Service 组件,它是以数组来保存的。

StandardServer内部提供了一个addService方法,用于将Service子组件添加到数组services[]中,如下:

public void addService(Service service) {

    service.setServer(this);

    synchronized (servicesLock) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;

        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("service", null, service);
    }

}

它并没有一开始就分配一个很长的数组,而是在添加的过程中动态地扩展数组长度,当添加一个新的 Service 实例时,会创建一个新数组并把原来数组内容复制到新数组。

除了Service子组件的维护,Server另一个重要的功能就是来实现Tomcat的关闭,这也是为什么你可以通过shutdown命令来关闭Tomcat的原因。具体实现在StandardServer的await方法中,在await方法中会启动一个Socket来监听8005(停止端口),并在一个死循环里接收Socket上的连接请求,如果有新的连接到来就建立连接,然后从Socket中读取数据,如果读到的数据是停止命令“SHUTDOWN”,就退出循环,进入stop流程。

public void await() {
    // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
    if (getPortWithOffset() == -2) {
        // undocumented yet - for embedding apps that are around, alive.
        return;
    }
    if (getPortWithOffset() == -1) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }

    // Set up a server socket to wait on
    try {
        // 创建socket,默认端口8005
        awaitSocket = new ServerSocket(getPortWithOffset(), 1,
                InetAddress.getByName(address));
    } catch (IOException e) {
        // log ……
        return;
    }

    try {
        awaitThread = Thread.currentThread();

        // 死循环,等待停止命令
        while (!stopAwait) {
            ServerSocket serverSocket = awaitSocket;
            if (serverSocket == null) {
                break;
            }

            // 等待连接,从连接中获取请求命令
            Socket socket = null;
            StringBuilder command = new StringBuilder();
            try {
                InputStream stream;
                long acceptStartTime = System.currentTimeMillis();
                try {
                    socket = serverSocket.accept();
                    socket.setSoTimeout(10 * 1000);  // Ten seconds
                    stream = socket.getInputStream();
                } catch (SocketTimeoutException ste) {
                    // log ……
                    continue;
                } catch (AccessControlException ace) {
                    // log ……
                    continue;
                } catch (IOException e) {
                    if (stopAwait) {
                        // Wait was aborted with socket.close()
                        break;
                    }
                    // log ……
                    break;
                }

                // Read a set of characters from the socket
                int expected = 1024; // Cut off to avoid DoS attack
                while (expected < shutdown.length()) {
                    if (random == null)
                        random = new Random();
                    expected += (random.nextInt() % 1024);
                }
                while (expected > 0) {
                    int ch = -1;
                    try {
                        ch = stream.read();
                    } catch (IOException e) {
                        log.warn(sm.getString("standardServer.accept.readError"), e);
                        ch = -1;
                    }
                    // Control character or EOF (-1) terminates loop
                    if (ch < 32 || ch == 127) {
                        break;
                    }
                    command.append((char) ch);
                    expected--;
                }
            } finally {
                // Close the socket now that we are done with it
                try {
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    // Ignore
                }
            }

            // 如果连接是SHUTDOWN命令,结束循环
            boolean match = command.toString().equals(shutdown);
            if (match) {
                log.info(sm.getString("standardServer.shutdownViaPort"));
                break;
            } else
                log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
        }
    } finally {
        ServerSocket serverSocket = awaitSocket;
        awaitThread = null;
        awaitSocket = null;

        // Close the server socket and return
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    }
}

2. Service

Service组件的具体实现类是StandardService。通过之前的文章我们知道,Service组件中有一个或多个Connector和一个Container。所以Service需要负责管理Connector和Container的生命周期。

除此之外,Service内部还有一个比较重要的成员就是映射器Mapper及其监听器,映射器及其监听器用于将用户请求的 URL 定位到一个 Servlet。它的工作原理是: Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及 Wrapper容器里Servlet映射的路径。映射器Mapper的监听器用于支持热部署,当Web应用的部署发生变化时,Mapper中的映射信息也要跟着变化,MapperListener就是一个监听器,它监听容器的变化,并把信息更新到Mapper 中,这是典型的观察者模式。

Service组件最重要的功能对下层容器的管理,比如启动时,要负责启动Connector和Container组件。Service的启动方法在startIntel中,如下:

protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    // 1. 触发启动监听器
    setState(LifecycleState.STARTING);

    // 2. 先启动Engine,Engine会启动它子容器
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    // 3. 再启动Mapper监听器
    mapperListener.start();

    // 4. 最后启动连接器,连接器会启动它子组件,比如Endpoint
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

从启动方法可以看到,Service先启动了Engine组件,再启动Mapper监听器,最后才是启动连接器。这很好理解,因为内层组件启动好了才能对外提供服务,才能启动外层的连接器组件。而 Mapper 也依赖容器组件,容器组件启动好了才能监听它们的变化,因此Mapper和MapperListener在容器组件之后启动。组件停止的顺序跟启动顺序正好相反的,也是基于它们的依赖关系。

3. Connector

通过之前的文章我们知道,连接器的作用是处理 Socket 连接,将网络字节流转化为Request和Response对象。也就是说连接器对Servlet容器屏蔽了协议及I/O模型等的区别,无论是HTTP还是AJP,对容器而言,获取到的都是一个标准的ServletRequest对象。连接器的工作流程可以细分为如下几个部分:

  • 监听网络端口
  • 接受网络连接请求
  • 读取请求网络字节流
  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象
  • 将 Tomcat Request 对象转成标准的 ServletRequest
  • 调用 Servlet 容器,得到 ServletResponse
  • 将 ServletResponse 转成 Tomcat Response 对象
  • 将 Tomcat Response 转成网络字节流
  • 将响应字节流写回给浏览器

Tomcat设计者将上述功能进一步细分为以下三部分:

  • 网络通信
  • 应用层协议解析
  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化

Tomcat 的设计者设计了3个组件来实现这3个功能,分别是EndPoint、Processor和Adapter

网络通信的I/O模型是变化的,可能是非阻塞 I/O、异步 I/O 或者 APR。应用层协议也是变化的,可能是 HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。但是整体的处理逻辑是不变的,EndPoint负责提供字节流给 Processor,Processor负责提供Tomcat Request对象给Adapter,Adapter 负责提供 ServletRequest对象给容器。

如果要支持新的I/O方案、新的应用层协议,只需要实现相关的具体子类。上层通用的处理逻辑是不变的。

由于I/O模型和应用层协议可以自由组合,比如NIO + HTTP或者NIO2 + AJP。Tomcat的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler的接口来封装这两点变化点。各种协议和通信模型的组合有相应的具体实现类,比如: Http11NioProtocol 和 AjpNioProtocol。

除了这些变化点,系统也存在一些相对稳定的部分,因此 Tomcat 设计了一系列抽象基类来封装这些稳定的部分,抽象基类AbstractProtocol实现了 ProtocolHandler接口。每一种应用层协议有自己的抽象基类,比如 AbstractAjpProtocol和AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。

这样设计的目的是尽量将稳定的部分放到抽象基类,同时每一种 I/O 模型和协议的组合都有相应的具体实现类,我们在使用时可以自由选择。所以Tomcat连接器可以细分为如下几个部分:

3.1 ProtocolHandler

ProtocolHandler成员变量初始化是在Connector构造函数中完成的,如果调用Connector无参构造函数,ProtocolHandler默认为