Servlet 规范和 Servlet 容器

如果大家觉得文章有错误内容,欢迎留言或者私信讨论~

前引

  通过之前的学习我们知道浏览器发给服务器的 HTTP 请求在服务器端需要调用服务端的程序来处理,也就是我们写的 Java 类,一般来说不同请求对应不同的 Java 类。

  那么问题来了,HTTP 请求怎么知道该调用那些类的哪些方法呢?最简单的方式就是通过 if-else 来做判断,A 请求就访问 AClass 的 A 方法, B 请求就访问 BClass 的 B 方法,但是这样业务代码和 HTTP 服务器的代码严重耦合,当我们需要更改或者新增业务方法的时候还需要去修改 HTTP 服务器的代码。

  为了解决这个问题,于是有一群人就定义了一个接口(面向接口编程是解决耦合问题的法宝),各种业务类都必须实现这个接口,这个接口就叫 Servlet 接口,有时我们也把实现了 Servlet 接口的业务类叫作 Servlet。

  但是对于特定的请求,HTTP 服务器该如何知道由哪个 servlet 来处理呢?servlet 又是由谁来进行实例化呢?显然 HTTP 服务器不适合做这个工作,不然又和业务代码耦合了。

  于是,还是那伙人发明了 servlet 容器,servlet 容器用来加载和管理业务类。HTTP 服务器不直接跟业务类打交道,而是把请求交给 Servlet 容器去处理,Servlet 容器会将请求转发到具体的 Servlet,如果这个 Servlet 还没创建,就加载并实例化这个 Servlet,然后调用这个 Servlet 的接口方法。因此 Servlet 接口其实是 Servlet 容器跟具体业务类之间的接口。下面我们通过一张图来加深理解。

在这里插入图片描述
  图的左边是 HTTP 服务器直接调用业务类,这样是紧耦合的。再看图的右边,HTTP 服务器不直接调用业务类,而是把请求交给 servlet 容器来处理,这样将原先 n 对 n 的关系变成了 1 对 1、1 对 n 的关系,以达成解耦的目的。

  而 Servlet 接口和 Servlet 容器这一整套规范叫作 Servlet 规范。Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器,同时它们也具有 HTTP 服务器的功能。

  接下来让我们看看 servlet 接口具体是怎么样的。

servlet 接口

  servl 接口定义了下面五个方法:


public interface Servlet {
    
    
    void init(ServletConfig config) throws ServletException;
    
    ServletConfig getServletConfig();
    
    void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
    
    String getServletInfo();
    
    void destroy();
}

  其中最重要是的 service 方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest 和 ServletResponse。ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息,因此本质上这两个类是对通信协议的封装。你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。

  其中还有两个跟生命周期相关的方法 init 和 destroy。,这是一个比较贴心的设计,Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。我们可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源,比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建了自己的 Spring 容器。

  你还会注意到 ServletConfig 这个类,ServletConfig 的作用就是封装 Servlet 的初始化参数。你可以在web.xml给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。

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

servlet 容器

  之前提到,为了解耦,HTTP 服务不直接调用业务方法,而是通过 servlet 容器,那么 servlet 容器又是如何工作的呢?Web 应用的目录格式是什么样的,以及我该怎样扩展和定制化 Servlet 容器的功能。

工作流程

  当客户请求某个资源的时候,HTTP 服务器会把请求信息封装到 ServletRequest 对象中,然后调用 servlet 容器的 service 方法,servlet 容器拿到请求后,根据请求的 url 和 servlet 的映射关系,找到对应的 servlet,如果 servlet 还没有被加载,就用反射机制创建这个 servlet,并调用 servlet 的 init 方法完成初始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器会把响应发送给客户端。

在这里插入图片描述

Web 容器

  servlet 容器会根据请求择出对应的 servlet 处理器,那么 servlet 是如何被加载到容器中的呢?一般来说,我们是以 Web 应用程序的方式来部署 Servlet 的,而根据 Servlet 规范,Web 应用程序有一定的目录结构,在这个目录下分别放置了 Servlet 的类文件、配置文件以及静态资源,Servlet 容器通过读取配置文件,就能找到并加载 Servlet。Web 应用的目录结构大概是下面这样的:


| -  MyWebApp
      | -  WEB-INF/web.xml        -- 配置文件,用来配置Servlet| -  WEB-INF/lib/           -- 存放Web应用所需各种JAR包
      | -  WEB-INF/classes/       -- 存放你的应用类,比如Servlet| -  META-INF/              -- 目录存放工程的一些信息

  Servlet 规范里定义了 ServletContext 这个接口来对应一个 Web 应用。Web 应用部署好后,Servlet 容器在启动时会加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象。你可以把 ServletContext 看成是一个全局对象,一个 Web 应用可能有多个 Servlet,这些 Servlet 可以通过全局的 ServletContext 来共享数据,这些数据包括 Web 应用的初始化参数、Web 应用目录下的文件资源等。由于 ServletContext 持有所有 Servlet 实例,你还可以通过它来实现 Servlet 请求的转发。

扩展机制

  你发现没有,当有了这些东西之后,程序员不再需要关心 Socket 网络通信、不需要关心 HTTP 协议,也不需要关心你的业务类是如何被实例化和调用的,因为这些都被 Servlet 规范标准化了,你只要关心怎么实现的你的业务逻辑。但统一的规范容易千篇一律,但是如果这个规范不能满足你的业务的个性化需求,就有问题了,因此设计一个规范或者一个中间件,要充分考虑到可扩展性。Servlet 规范提供了两种扩展机制:Filter 和 Listener。

  • Filter

  Filter 是过滤器,这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的工作原理是这样的:Web 应用部署完成后,Servlet 容器需要实例化 Filter 并把 Filter 链接成一个 FilterChain(责任链模式)。当请求进来时,获取第一个 Filter 并调用 doFilter 方法,doFilter 方法负责调用这个 FilterChain 中的下一个 Filter。

  • Listener

  Listener 是另外一种扩展机制。当 Web 应用在 servl 容器中运行的时候,servlet 容器内部就会不断发生各种事件,如 Web 应用的启动和停止、用户请求到达等。 Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。比如 Spring 就实现了自己的监听器,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启动时,创建并初始化全局的 Spring 容器。

总结

Filter 是干预过程的,它是过程的一部分,是基于过程行为的。Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致的

猜你喜欢

转载自blog.csdn.net/qq_43654226/article/details/127078467