根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象。将 Tomcat Request 对象转成标准的 ServletRequest。调用 Servlet 容器,得到 ServletResponse。将 ServletResponse 转成 Tomcat Response 对象。将 Tomcat Response 转成网络字节流。将响应字节流写回给浏览器。

优秀的模块化设计应该考虑高内聚、低耦合。通过分析连接器的详细功能列表,我们发现连接器需要完成 3 个高内聚的功能:

网络通信。应用层协议解析。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 对象给容器。其中 Endpoint 和 Processor 放在一起抽象成了 ProtocolHandler 组件

io 和线程模型

同样一个颜色的是内部类的关系

Http11NioProtocol start 时会分别启动 poller 和 acceptor 线程acceptor 持有 ServerSocket/ServerSocketChannel, 负责监听新的连接,并将得到的 Socket 注册到 Poller 上Poller 持有 Selector, 负责selector.select() 监听读写事件,将新的 socket 注册到 selector 上,以及其它通过 addEvent 加入到 Poller 中的 eventHttp11NioProcessor 封装了 http 1.1 的协议处理部分,比如 parseRequestLine,连接出问题时 response 设置状态码为 503 或 400 等。以读事件为例, 最终会将 数据读取到 Request 对象的 inputBuffer 中

线程数量:

public class NioEndpoint extends AbstractEndpoint { private Executor executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);

private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors()); // new Thread().start() 的方式 protected int acceptorThreadCount = 0; // new Thread().start() 的方式

// poller 内部除了 selector.select() 逻辑外,一般通过executor 异步执行 // acceptor 就是简单的 accept 一个socket 并将其 加入到poller 的event 队列中( 以将socket 注册到selector)所以没有用到executor }

业务处理

container 架构

Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器是父子关系,形成一个树形结构。Tomcat 是用组合模式来管理这些容器的,具体实现方法是,所有容器组件都实现了 Container 接口。

public interface Container extends Lifecycle { public void setName(String name); public Container getParent(); public void setParent(Container container); public void addChild(Container child); public void removeChild(Container child); public Container findChild(String name); }

假如有用户访问一个 URL:http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?Tomcat 是用 Mapper 组件。

根据协议和端口号选定 Service 和 Engine。根据域名选定 Host。根据 URL 路径找到 Context 组件。根据 URL 路径找到 Wrapper(Servlet)。

为了更清晰一点,上图只画出了 Host 类族,Engine、Context、Wrapter 与 Host 类似。黄色部分组成了一个 pipeline,可以看到 Engine、Context、Wrapter 和 Host 作为容器,并不亲自“干活”,而是交给对应的 pipeline。

public class CoyoteAdapter implements Adapter { // 有读事件时会触发该操作 public boolean event(org.apache.coyote.Request req, org.apache.coyote.Response res, SocketStatus status) { … // 将读取的数据写入到 request inputbuffer request.read(); … // 触发filter、servlet的执行 connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent()); … } }

pipeline 逐步传递请求直到 Servlet

Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理。Valve 表示一个处理点,比如权限认证和记录日志。

每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里 Pipeline 中的 Valve 就都会被调用到。不同容器的 Pipeline 是怎么链式触发的呢?Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter 方法,最终会调到 Servlet 的 service 方法。

那 Valve 和 Filter 有什么区别吗?Valve 是 Tomcat 的私有机制,与 Tomcat 的基础架构 /API 是紧耦合的。Servlet API 是公有的标准,所有的 Web 容器包括 Jetty 都支持 Filter 机制。

Tomcat 的类加载

Tomcat 热部署与热加载[1] 值得细读

Tomcat 并没有完全遵循类加载的双亲委派机制,考虑几个问题:

如果在一个 Tomcat 内部署多个应用,多个应用内使用了某个类似的几个不同版本,如何互不影响?org.apache.catalina.loader.WebappClassLoader如果多个应用都用到了某类似的相同版本,是否可以统一提供,不在各个应用内分别提供,占用内存呢?common ClassLoader 其实质是一个指定了 classpath(classpath 由 catalina.properties 中的 common.loader 指定common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar")的 URLClassLoader

public final class Bootstrap { ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; public void init() throws Exception { initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); … } private void initClassLoaders() { … commonLoader = createClassLoader(“common”, null); … catalinaLoader = createClassLoader(“server”, commonLoader); } }

热部署和热加载是类似的,都是在不重启 Tomcat 的情况下,使得应用的最新代码生效。热部署表示重新部署应用,它的执行主体是 Host,表示主机。热加载表示重新加载 class,它的执行主体是 Context,表示应用。

Sprint Boot 如何利用 Tomcat 加载 Servlet?

在内嵌式的模式下,Bootstrap 和 Catalina 的工作就由 Spring Boot 来做了,Spring Boot 调用了 Tomcat 的 API 来启动这些组件。

Tomcat 源码中直接提供 Tomcat 类,其 java doc 中有如下表述:Tomcat supports multiple styles of configuration and startup - the most common and stable is server.xml-based,implemented in org.apache.catalina.startup.Bootstrap. Tomcat is for use in apps that embed Tomcat. 从 Tomcat 类的属性可以看到,该有的属性都有了,内部也符合 Server ==> Service ==> connector + Engine ==> Host ==> Context ==> Wrapper 的管理关系,下图绿色部分是通用的。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

Java基础部分

算法与编程

数据库部分

流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算**

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: