SpringBoot Tomcat(8) 类加载器、生命周期、剩余过程与处理过程总结

1,046 阅读5分钟

有关SpringBoot启动Tomcat,添加过滤器及Servlet请点击此处相关部分。

有关SpringBoot开启Tomcat监听请点击此处相关部分。

类加载器

类加载器是用于加载Java类到Java虚拟机中的组件,它负责读取类的Class文件,并转换成java.lang.Class的一个实例。

类加载的方式分为隐式加载和显示加载两种。隐式加载指的是程序在使用new 等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中(见ClassLoader->loadClass())。显式加载指的是通过直接调用Class.forName()方法把所需的类加载到JVM中。

双亲委派机制指的是在加载类时先由父加载器加载,父加载器再由它的父加载器加载,层层向上,如果加载到就直接返回,加载不到再尝试由自己加载。

Tomcat的类加载架构图如下所示,Web环境的类加载器是WebAppClassLoader,它的加载顺序为先启动类、扩展类、应用程序类,再WebAPP类,最后Common类,因此它破坏了双亲委派机制。这么设计的主要原因,是为了解决同一个Web服务器里,1.各个Web项目各自使用的Java类库要互相隔离,2.各个项目可以提供共享的Java类库的问题。

生命周期与监听器

上篇说到,判断session是否过期在Context的backgroundProcess()方法执行,它是ContainerBase类下的一个方法,四大组件都是它的子类,ContainerBase类又是LifecycleMBeanBase的子类,该类与生命周期有关。

接口Lifecycle定义有哪些事件,枚举类LifecycleState列出生命状态对应的事件类型,四大组件初始化的时候,会调用LifecycleBase->addLifecycleListener()添加监听器,监听器是LifecycleListener接口的实现类,当生命周期变化时,会调用fireLifecycleEvent(),之后监听器处理对应的事件,可见监听器的设计模式是观察者模式。

LifecycleBase->fireLifecycleEvent():
    // this是当前的组件,type是事件类型,lifecycleListeners是监听器集合
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for (LifecycleListener listener : lifecycleListeners) {
        // 监听器处理事件,不同监听器处理不同的事件类型
        listener.lifecycleEvent(event);
    }

剩余过程

以返回JSON为例,当消息状态置为CLIENT_FLUSH时,客户端已经看到数据了,服务端还需完成回收的工作。

  • StandardWrapperValve
    • invoke()
      • filterChain.release() 释放过滤器链
  • CoyoteAdapter
    • service()
      • response.finishResponse() 1.如果bb和cb还有剩余的话,写入剩余部分到SocketWrapperBase的socketBufferHandler的writeBuffer中 2.将消息状态置为CLOSE,调用钩子函数,将剩余内容输出到客户端
      • request.recycle() 回收请求相关数据,这里的请求类是原始请求类的包装类,ByteBuffer、CharBuffer的回收只是下标和limit置为0,集合有关的置为空集合,对象有关的置为null
      • response.recycle() 回收响应相关数据,处理同上
  • Http11Processor
    • service()
      • inputBuffer.nextRequest() 回收请求相关数据,这里的请求类是与网络IO交互的最原始的请求类
      • outputBuffer.nextRequest() 回收响应相关数据,处理同上
      • while(...) 再一次尝试解析请求行,因为客户端没有发送数据了,所以跳出循环
  • AbstractProtocol->ConnectionHandler
    • process()
      • connections.remove(socket) connections去除本次会话相关内容,一次请求是一个socket

能够看出数组类型并没有将数据清空,而是将起始和结束下标置为0,频繁销毁的对象也仅是部分属性清空,然后入栈,这些对于Tomcat性能的提升都有不小的帮助。

Tomcat处理过程总结

以下内容摘自《Tomcat内核剖析》。

image.png

  1. 当Tomcat启动后, Connector组件的接收器(Acceptor)将会监听是否有客户端套接字连接并接收Socket。
  2. 一旦监听到客户端连接,则将连接交由线程池Executor处理,开始执行请求响应任务。
  3. HttpllProcessor组件负责从客户端连接中读取消息报文,然后开始解析HTTP的请求行、请求头部、请求体。将解析后的报文封装成Request对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
  4. Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配由哪个Host容器、哪个Context容器、哪个Wrapper容器处理请求,这个过程其实就是根据请求从Tomcat中找到对应的Servlet,然后将路由的结果封装到Request对象中,方便后面处理时通过Request对象选择容器。
  5. CoyoteAdaptor组件负责将Connector组件和Engine容器连接起来,把前面处理过程中生成的请求对象Request和响应对象Response传递到Engine容器,调用它的管道
  6. Engine容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些,处理逻辑。这里用xxxValve代表某阀门,我们可以根据自己的需要往这个管道中添加多个阀门,首先执行这个xxxValve,然后才执行基础阀门EngineValve,它会负责调用Host容器的管道。
  7. Host容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve,它继续往下调用Context容器的管道。
  8. Context容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve,它负责调用Wrapper容器的管道。
  9. Wrapper容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve,它会执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。