Servlet容器与Servlet规范

当浏览器发送一个 HTTP 请求过来时,HTTP 服务器怎么知道该调用哪个 Java 类进行处理呢?最蠢的方法就是写一堆 if else 判断,如果是 A 请求就调用 A 方法,如果是 B 请求就调用 B 方法。但是这样做明细有问题嘛,因为你 HTTP 服务器的代码和业务代码耦合在一起啦,新加一个业务代码还得改 HTTP 服务器的代码。
那咋办呢,解耦呗,怎么解耦呢,接口呗。因为面向接口编程是解决耦合问题的法宝。因此有些人就定义了一个接口,各种业务类都得实现这个接口,这个接口就是 Servlet 接口,有时把实现了 Servlet 接口的业务类叫 Servlet(最典型最常见的 Servlet 就是 Spring 里的DispatcherServlet,通过 DispatcherServlet 我们关注与 RequestMapping 的请求 url 路径就可以了)。
但是这里还是有个问题,对于特定请求,HTTP 服务器如何知道由哪个 Servlet 处理呢?Servlet 又是谁来进行实例化呢?HTTP 服务器显然不适合做这个工作,否则又和业务耦合了。
因此又发明了 Servlet 容器,Servlet 容器用来加载和管理业务类。HTTP 服务器不直接和业务打交道,而是把请求交给 Servlet 容器处理。Servlet 容器会将请求转发到具体的 Servlet,如果这个 Servlet 还没创建,就加载并实例化这个 Servlet,然后调用这个 Servlet 的接口方法。因此 Servlet 接口其实是 Servlet 容器跟具体业务类之间的接口。

null

而 Servlet 接口和 Servlet 容器这一套规范就叫做 Servlet 规范。Tomcat 按照 Servlet 规范的要求实现了 Servlet 容器,同时他们也具有 HTTP 服务器的功能。作为 Java 程序员,如果我们要实现新的业务功能,只需要实现一个 Servlet,并把它注册到 Tomcat(Servlet 容器)中,剩下的事情就交给 Tomcat 处理就好了。

Servlet 接口

在 Java Servelt API 中已经提供了两个抽象类方便开发者实现 Servlet 类,分别是 GenericServlet 和 HttpServlet,GenericServlet 定义了一个通用的、与协议无关的 Servlet,而 HttpServlet 则定义了 HTTP 的 Servlet。

Servlet 的生命周期主要包括加载实例化,初始化,处理客户端请求,销毁。加载实例化主要由 Web 容器完成,而其他的三个阶段则对应 Servlet 的 init,service,destory 方法。Servlet 对象被创建后需要对其进行初始化操作,初始化工作放在了以 ServletConfig 类型为参数的 init 方法中,ServletConfig 为 web.xml 配置文件中配置的对应初始化参数,由 Web 容器完成 Web.xml 配置读取并封装成 ServletConfig 对象。

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


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 方法,具体业务类在这个方法里实现处理逻辑。Spring 的就是由 Servlet.service->GenericServlet->FrameworkServlet->DispatcherServlet.doService 来处理逻辑。

Servlet 容器工作流程
null

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

Web 应用
一般来说,我们是以 Web 应用程序的方式来部署 Servlet 的,而根据 Servlet 规范,Web 应用程序有一定的目录结构,在这个目录下分别放置了 Servlet 的类文件、配置文件以及静态资源,Servlet 容器通过读取配置文件,就能找到并加载 Servlet。


| -  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 请求的转发。

扩展机制

  • Servlet 规范提供了两种扩展机制:Filter 和 Listener。
    Filter 是过滤器,这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的工作原理是这样的:Web 应用部署完成后,Servlet 容器需要实例化 Filter 并把 Filter 链接成一个 FilterChain。当请求进来时,获取第一个 Filter 并调用 doFilter 方法,doFilter 方法负责调用这个 FilterChain 中的下一个 Filter。
  • Listener 是监听器,这是另一种扩展机制。当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,如 Web 应用的启动和停止、用户请求到达等。 Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在 web.xml 中。比如 Spring 就实现了自己的监听器,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启动时,创建并初始化全局的 Spring 容器。

总结如下
Servlet 本质上是一个接口,实现了 Servlet 接口的业务类也叫 Servlet。Servlet 接口其实是 Servlet 容器跟具体 Servlet 业务类之间的接口。Servlet 接口跟 Servlet 容器这一整套规范叫作 Servlet 规范,而 Servlet 规范使得程序员可以专注业务逻辑的开发,同时 Servlet 规范也给开发者提供了扩展的机制 Filter 和 Listener。

  • Filter 是干预过程的,它是过程的一部分,是基于过程行为的。
  • Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致的。
  1. Servlet 容器与 Spring 容器有什么关系?

Tomcat 在启动时给每个 Web 应用创建一个全局的上下文环境,这个上下文就是 ServletContext,其为后面的 Spring 容器提供宿主环境。

Tomcat 在启动过程中触发容器初始化事件,Spring 的org.springframework.web.context.ContextLoaderListener会监听到这个事件,它的 contextInitialized 方法会被调用,在这个方法中,Spring 会初始化全局的 Spring 根容器,这个就是 Spring 的 IoC 容器,IoC 容器初始化完毕后,Spring 将其存储到 ServletContext 中,便于以后来获取。

Tomcat 在启动过程中还会扫描 Servlet,一个 Web 应用中的 Servlet 可以有多个,以 SpringMVC 中的 DispatcherServlet 为例,这个 Servlet 实际上是一个标准的前端控制器,用以转发、匹配、处理每个 Servlet 请求。

Servlet 一般会延迟加载,当第一个请求达到时,Tomcat 发现 DispatcherServlet 还没有被实例化,就调用 DispatcherServlet 的 init 方法,DispatcherServlet 在初始化的时候会建立自己的容器,叫做 SpringMVC 容器,用来持有 Spring MVC 相关的 Bean。同时,Spring MVC 还会通过 ServletContext 拿到 Spring 根容器,并将 Spring 根容器设为 SpringMVC 容器的父容器,请注意,Spring MVC 容器可以访问父容器中的 Bean,但是父容器不能访问子容器的 Bean, 也就是说 Spring 根容器不能访问 SpringMVC 容器里的 Bean。说的通俗点就是,在 Controller 里可以访问 Service 对象,但是在 Service 里不可以访问 Controller 对象。

  1. Servlet 容器的 Filter 跟 Spring 的 intercepter 有什么区别?
    Filter 是 Servlet 规范的一部分,是 Servlet 容器 Tomcat 实现的。Intercepter 是 Spring 发明的。它们的执行顺序是:
    Filter.doFilter();
    HandlerInterceptor.preHandle();
    Controller
    HandlerInterceptor.postHandle();
    DispatcherServlet 渲染视图
    HandlerInterceptor.afterCompletion();
    Filter.doFilter();
    Servlet 方法返回

道路且长,行则将至。