Tomcat1 - Servlet容器,与 SpringMVC 的关系

lalala / 2023-09-05 / 原文

参考:

PoetryAndTheDistance  :一:Tomcat核心组件及应用架构详解

Hong EuiSung –@gowoonosori :  요청처리 내부구조

 

Http 请求响应实例

 

 

Servlet 接口

为了解耦 【http服务器代码】和【业务代码】,Java就定义了一个接口,各种业务类都必须实现这个接口,这个接口就叫 Servlet 接口,有时我们也把实现了 Servlet 接口的业务类叫作 Servlet。

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

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}
  • 其中最重要是的 service 方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest 和 ServletResponse。
    • ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息,因此本质上这两个类是对通信协议的封装。
    • 比如 HTTP 协议中的请求和响应就是对应了 HttpServletRequest 和 HttpServletResponse 这两个类。你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。还可以通过 HttpServletRequest 来创建和获取 Session。而 HttpServletResponse 是用来封装 HTTP 响应的。
  • 两个跟生命周期有关的方法 init 和 destroy
    • Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。我们可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源,
    • 比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建了自己的 Servlet WebApplicationContext
  • 还有 ServletConfig 这个类,ServletConfig 的作用就是封装 Servlet 的初始化参数。
    • 你可以在 web.xml 给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。

有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑。

(接口和抽象类都不能实例化,接口只能有方法的声明不能有实现,抽象类可以有方法的实现。接口用来抽象功能,抽象类用来抽象类型)

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

HttpServlet 实现的 Servlet 声明的业务方法接口 service 方法里会调用 doGet 和 doPost

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                .......省略
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

 

Servlet 容器

当客户请求某个资源时:

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

扩展机制

Servlet 规范提供了两种扩展机制:Filter 和 Listener。

Filter 

允许你对请求和响应做一些统一的定制化处理。过滤器的工作原理是这样的:Web 应用部署完成后,Servlet 容器需要实例化 Filter 并把 Filter 链接成一个 FilterChain。当请求进来时,获取第一个 Filter 并调用 doFilter 方法,doFilter 方法负责调用这个 FilterChain 中的下一个 Filter。

比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。还有 SpringSecurity 的 SessionRepositoryFilter 就 implements 了 Filter,用来校验请求头中的 session

Listener 

当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,Web 应用的启动和停止、用户请求到达等。 Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。

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

 

Servlet容器 与 SpringMVC 的关系

在 SpringMVC 中,有一个名为 DispatcherServlet 的 Servlet,它充当 FrontController,转发、匹配、将请求委托给其他控制器来处理请求。

执行service(),并在方法中执行 dispatch.doService() -> doDispatch(),doDispatch 负责 从 HandlerMapping 中获取handler

Root WebApplicationContext

  • 就是 org.springframework.context.ApplicationContext
  • 就是 Spring 的 IOC 容器,包括 Service、datasource、repositories 等 bean
  • 通过 Servlet 容器的 Listener 机制Spring 的 ContextLoaderListener 类实现 ServletContextListener,监听 Servlet 容器的启动事件,创建并初始化这个 spring 全局上下文。
  • 管理bean的生命周期并提供 IOC/DI
  • Root WebApplicationContext 不能访问 Servlet WebApplicationContext 中的 bean(Controller 里可以访问 Service 对象,Service 对象不可以访问 Controller 对象)

Servlet WebApplicationContext

  • 就是 org.springframework.web.context.WebApplicationContext
  • 与标准 javax.servlet.ServletContext 配合使用。ServletContext 实例可以做很多事情,例如通过调用 getResourceAsStream() 方法访问 WEB-INF 资源(xml 配置等)。
  • 继承自 RootWebApplicationContext 实现的 Context,主要包含 Controller、Intercepter、ViewResolver、HandlerMapping 等bean。
  • 之所以有这样的继承关系,是因为一个Servlet容器内部可能会出现多个Servlet,它们要共用 Root WebApplicationContext 中的服务和数据源。
  • 延迟加载,当第一个请求到来,Tomcat 发现 DispatcherServlet 还没被实例化,就会调用它的 init 方法,建立容器初始化相关 bean
  • 如果一个servlet 上下文的 bean注册了与Application Context和context相同的ID,则使用Servlet Context中声明的bean,查找 bean 时按照 Servlet Context -> Application Context的顺序查找。
public interface WebApplicationContext extends ApplicationContext {
    ServletContext getServletContext();
}

从服务器启动到请求的步骤

  1. Web 服务器初始化
  2. 监听到 Servlet 容器启动。加载 Root WebApplicationContext
  3. 网络服务器启动
  4. 客户端第一次向 Web 服务器发出请求
  5. Web Server 交付给 Servlet 容器
  6. 创建 Servlet线程
  7. 如果 DispatcherServlet 没有创建,DispatcherServlet init 初始化 Servlet WebApplicationContext
  8. 在生成的线程中调用 DisapatcherServlet 的 service() 方法。
  9. 通过 HandlerMapping 查找 Controller
  10. 通过 HandlerAdapter 传递给 Controller
  11. Controller->Service->Response

Filter 与 Intercepter

Filter 是 Servlet 规范的一部分,Servlet 容器 Tomcat 实现了它;Intercepter 是 Spring 实现的。它们的执行顺序是

  • Filter.doFilter();
  • HandlerInterceptor.preHandle();
  • Controller
  • HandlerInterceptor.postHandle();
  • HandlerInterceptor.afterCompletion();
  • Filter.doFilter()
  • Servlet 方法返回