web容器

Tomcat 或 Jetty 就是一个“HTTP 服务器 + Servlet 容器”,我们也叫它们 Web 容器。

Servlet 和 Servlet容器

Servlet,全称是 server applet,简单理解为运行在服务端的 Java 小程序。但是 Servlet 没有 main 方法,不能独立运行,因此必须把它部署到 Servlet 容器中,由容器来实例化并调用 Servlet。


Servlet 技术是 Web 开发的原点,几乎所有的 Java Web 框架(比如 Spring)都是基于 Servlet 的封装。

Spring 应用本身就是一个 Servlet,而 Tomcat 和 Jetty 这样的 Web 容器,负责加载和运行 Servlet。
image.png

web 容器的由来

早期的 Web 应用主要用于浏览新闻等静态页面。HTTP 服务器(比如 Apache、Nginx)向浏览器返回静态 HTML,浏览器负责解析 HTML,将结果呈现给用户。

后来希望通过一些交互操作,来获取动态结果。因此需要一些扩展机制能够让 HTTP 服务器调用服务端程序。Sun 公司推出了 Servlet 技术,也即是这里的扩展机制。

嵌入式 web 容器

微服务架构日渐流行,大而全的单体应用,拆分成一个个功能单一的微服务,服务的数量必然要增加。服务数量多,需要减少资源的消耗。因此要求 Web 容器本身应该消耗较少的内存和 CPU 资源。

为了降低资源消耗和部署成本:
应用来启动一个嵌入式的 Web 容器,而不是通过 Web 容器来部署和启动应用。

HTTP协议

HTTP 协议是浏览器与服务器之间的数据传送协议,不是数据传输协议。
HTTP协议主要有三个内容:
规定了客户端和服务端的请求/响应数据包的格式。
规定了客户端和服务端的交互动作。
规定了资源的操作API和资源的定位方式(客户端)

HTTP请求的过程

请求过程就是浏览器(客户端)和服务器根据 HTTP 协议进行交互,及内部处理请求的流程。
image.png

服务器的工作

备注:这里的服务器,是否改成应用服务器比较妥当。
主要工作是:接受连接;解析请求数据包、处理请求、生成响应数据包、发送响应。

请求数据包由三部分组成,请求行、请求报头、请求正文。Tomcat 将来自浏览器的HTTP 格式的请求数据解析成 Request 对象。
响应数据包由三部分组成,状态行、响应报头、报文主体。Tomcat 将 Response 对象转成 HTTP 格式的响应数据并发送给浏览器。

Request 对象封装了 HTTP 所有的请求信息。

处理请求:得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。Tomcat 把这个 Request 对象交给 Web 应用去处理,处理完后得到一个 Response 对象。

服务器如何处理请求?

处理请求的阶段,对于部分请求,服务器有时候需要调用服务端程序(自己写的 Java 业务类)来处理。
问题:对于不同的请求,一般需要用不同的 Java 类来处理。服务器怎么知道要调用哪个 Java 类的哪个方法呢
有两种思路:
image.png
思路1:
在 HTTP 服务器代码里写一大堆 if else 逻辑判断:
如果是 A 请求就调 X 类的 M1 方法;如果是 B 请求就调 Y 类的 M2 方法。
这样的话,在处理请求阶段,HTTP 服务器的代码跟业务类高度耦合:
不仅要管理业务类的生命周期,还要调用业务类的方法来处理具体的业务逻辑。
HTTP 服务器代码和业务逻辑高度耦合的结果是,如果要新加一个业务方法还要改 HTTP 服务器的代码。

思路2:
本身 HTTP 服务器是要处理连接、解析请求、处理请求、生成响应数据包、发送响应数据包。
处理请求阶段是根据请求的不同而具有不同的逻辑,而其他四个阶段是采用通用的、不变的逻辑。
因此可以考虑用单独的模块来处理请求,服务器只需要调用接口处理请求。
这个单独的模块不仅管理业务类的生命周期,并负责处理具体的请求。
这样就解决了HTTP 服务器的代码跟业务类高度耦合的问题。

Servlet、Servlet 容器的本质

Servlet 接口和 Servlet 容器的出现,使得 HTTP 服务器与业务类解耦。

Servlet

面向接口编程可以解决耦合问题。Servlet 本质上是一个接口,各种业务类都必须实现这个接口。
实现了 Servlet 接口的 具体 Servlet 业务类 也叫 Servlet。
但是这里还有一个问题,对于特定的请求,HTTP 服务器如何知道由哪个 Servlet 来处理呢?

Servlet 容器

Servlet 容器用来加载和管理业务类、调用具体的业务类(Servlet)的接口方法。
HTTP 服务器不直接跟业务类打交道,而是把请求交给 Servlet 容器处理。
Servlet 容器加载并实例化这个 Servlet,然后将请求转发到具体的 Servlet(其实就是调用这个 Servlet 的接口方法)
因此 Servlet 接口其实是 Servlet 容器跟具体业务类之间的接口。

Servlet 规范

Servlet 规范包含 Servlet 接口和 Servlet 容器这两部分规范。
Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器,同时它们也具有 HTTP 服务器的功能。

Servlet 接口(5个方法)

  1. public interface Servlet {
  2. void init(ServletConfig config) throws ServletException;
  3. ServletConfig getServletConfig();
  4. void service(ServletRequest req, ServletResponse resthrows ServletException, IOException;
  5. String getServletInfo();
  6. void destroy();
  7. }

1 service 方法。最重要的方法,具体业务类在这个方法里实现处理逻辑。
ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息。
通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。

2 init 和 destroy。生命周期有关的方法。
Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。
可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源
比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建了自己的 Spring 容器。

3 getServletConfig。封装 Servlet 的初始化参数。
可以在web.xml给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。

Servlet 类层次

有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑。
因此 Servlet 规范提供了 GenericServlet 抽象类,可以通过扩展它来实现 Servlet。
虽然 Servlet 规范并不在乎通信协议是什么,但是大多数的 Servlet 都是在 HTTP 环境中处理的。
因此 Servet 规范还提供了 HttpServlet 来继承 GenericServlet,并且加入了 HTTP 特性。
这样我们通过继承 HttpServlet 类来实现自己的 Servlet,只需要重写两个方法:doGet 和 doPost。

Servlet 容器如何管理 Servlet?

Servlet 容器管理 Servlet,主要有三步,定位、加载、调用。
image.png

Servlet 容器定位并加载 Servlet

以 Web 应用程序的方式来部署 Servlet 的。本质上讲,Web应用就是Servlet的集合。
Servlet 规范规定 Web 应用程序有一定的目录结构。在这个目录下分别放置了 Servlet 的类文件、Jar包、静态资源、配置文件。
Servlet 规范定义了 Servlet 来表示业务类。Servlet 容器通过读取配置文件,找到并加载 Servlet。

加载应用的结果( ServletContext )

前面提到,Servlet 规范定义了 Servlet 来表示业务类。
Servlet 规范不仅定义了 Servlet 来表示业务类,而且还定义了 ServletContext 接口来对应一个 Web 应用。
Web 应用部署好后,Servlet 容器在启动时会加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象,可看作全局对象。

Web应用包含若干业务类。因此一个 Web 应用可能有多个 Servlet,一个 ServletContext 持有多个 Servlet 实例。这意味着:
1 共享数据。这些 Servlet 实例可以通过全局的 ServletContext 来共享数据,比如 Web 应用的初始化参数、目录下的文件资源等。
2 请求转发。ServletContext 持有所有 Servlet 实例,因此可以通过它来实现 Servlet 请求的转发。

程序员关心的

如果我们要实现新的业务功能,只需要实现一个 Servlet 并注册到 Tomcat(Servlet 容器)中,剩下的事情就由 Tomcat 帮我们处理了。
具体的注册,可以是基于注解,或者是采用配置文件。

扩展机制:Filter 和 Listener

设计一个规范,还要充分考虑到可扩展性。Servlet 规范提供了两种扩展机制。

Filter 本质是一个接口。工作原理:Web 应用部署完成后,Servlet 容器需要实例化并调用 Filter。
从而可以对请求和响应做统一的定制化处理,比如
针对请求,可以根据请求的频率来限制访问,
针对响应,可以根据国家地区的不同来修改响应内容。

Listener 本质就是监听 Servlet 容器里的特定事件。
当事件发生时,Servlet 容器会负责调用监听器的方法。 //观察者模式?
事件:当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,如 Web 应用的启动和停止、用户请求到达等。 可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。

比如 Spring 就实现了自己的监听器,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启动时,创建并初始化全局的 Spring 容器。