深入剖析Spring Web源码(四) - DispatcherServlet的实现之通用Servlet和HTTP Servlet

Spring是一个轻量级 J2EE框架。它可以运行在任何一个 Web容器上。事实上, Spring的入口点就是实现或者继承了 Web容器规范中的 Servlet, 监听器 (Listener)和过滤器 (Filter)。这一章我们将详细讨论 Spring Web MVC架构的具体实现。

DispatcherServlet的实现

DispatcherServlet是一个经过多个层次最终继承自 Servlet规范中的 HttpServlet, 进而实现 Servlet规范中定义的 Servlet接口。这些继承和实现组成了一个复杂的树形结构,在树形结构中的每个层次的类完成一个特定的初始化功能,服务功能或者清理资源的功能,每个层次的类之间分工合理,易于扩展。如图表 4 - 1,
这里写图片描述
Servlet是 Servlet规范中规定的一个服务器组件的接口,任何一个可以处理用户请求的服务器组件需要实现这个接口, Web容器就是根据 URL到 Servlet的映射派遣一个 HTTP请求到这个 Servlet组件的实现,进而对这个 HTTP请求进行处理,并且产生 HTTP响应。
通用 Servlet(GenericServlet)是 Servlet的一个抽象实现。这个实现是和协议无关的。它提供了 Servlet应该具有的基础功能。例如,保存 Servlet配置,为后来的操作提供初始化参数和信息等等。
HTTP Servlet(HttpServlet)是针对 HTTP协议对通用 Servlet的实现。它实现了 HTTP协议的一些基本操作。例如,根据 HTTP请求的方法分发不同类型的 HTTP请求到不同的方法进行处理。对于简单的 HTTP方法 (HEAD, OPTIONS, TRACE)提供了通用的实现,这些实现在子类中通常是不需要重写的。而对其他的业务服务类型的方法 (GET, POST, PUT, DELETE)提供了占位符方法。子类应该有选择的根据业务逻辑重写这些服务类型方法的实现。
HTTP Servlet Bean(HttpServletBean)是 Spring Web MVC的一个抽象实现。它提供了一个特殊的功能,可以将 Servlet配置的初始化参数作为 Bean的属性,自动赋值给 Servlet的属性。子类 Servlet的很多属性都是通过这个功能进行配置的。
Framework Servlet(FrameworkServlet)也是一个抽象的实现。在这个是西安层次上,它提供了加载一个对应的 Web应用程序环境的功能。这个 Web应用程序环境可以存在一个根环境,这个根环境可以是共享的也可以是这个 Servlet或者几个 Servlet专用的。它也提供了功能将 HTTP GET, POST, PUT, DELETE方法统一派遣到 Spring Web MVC的控制器方法进行派遣。在派遣前到处请求环境等信息到线程的局部存储。
派遣器 Servlet(DispatcherServlet)是这个继承链中最后一个类,它是 Spring Web MVC的核心实现类,它在框架 Servlet中加载的 Web应用程序环境中查找一切 Spring Web MVC所需要的并且注册的组件,如果一个需要的 Spring Web MVC组件没有注册,则通过缺省策略的配置创建并且初始化这些组件。在一个 HTTP请求被派遣的时候,它使用得到的 Spring Web MVC组件进行处理和响应。具体的处理和相应将在下面的章节具体分析。
资源 Servlet(ResourceServlet)是一个用来存取 Web应用中内部静态资源的一个 Servlet实现。
HTTP 请求处理器 Servlet(HttpRequestHandlerServlet)是用来派遣一个请求直接到 HttpRequestHandler的特殊的 Serlvet。可以用以基于 HTTP协议的远程调用的实现。
视图绘制 Servlet(ViewRenderServlet)是用来与 Portlet集成而设计的一个实现类。
从上图可以看出,前三个类被标识为灰色,因为他们都是 Servlet规范中规定接口或者实现,并不是 Spring的实现。而后面的所有类都是 Spring Web MVC的实现类。下面的第一小节中我们讲分析 Servlet规范中对 HTTP协议的实现。第二小节则深入剖析 Spring Web MVC控制器(派遣器 Servlet)的实现。

1. 通用Servlet和HTTP Servlet

HTTP( Hyper Text Transfer Protocol)是超文本传输协议的缩写,它用于传送 WWW方式的数据,关于 HTTP协议的详细内容请参考 RFC2616。 HTTP协议采用了请求 /响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、 URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于 MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容
HTTP协议支持各种类型的方法,其中包括, GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE。
(1)GET方法将请求参数放在请求的头中,请求服务器做某些服务操作并返回响应。
(2)POST方法从客户机向服务器传送数据,并可能要求服务器做出某些服务操作进行响应。
(3)PUT方法请求将一个资源放在服务器的某个路径下。
(4)DELETE方法请求将服务器某路径下的一个资源删除。
(5)HEAD要求服务器查找某对象的头信息,包括应该包含的请求体的长度,而不是对象本身。
(6)OPTION方法用来查询服务器的实现信息。
(7)TRACE多数情况下用在调试目操作上。
下图是 Servlet规范的接口和实现类的继承结构,每个类的方法包括实体方法,抽象方法或者占位符方法。抽象方法和占位符方法由子类实现。
这里写图片描述
从上图我们可以看出, Servlet接口定义了 3个重要的接口方法, init()方法是在 Sevlet初始化的时候调用的,提供给 Servlet组件进行初始化自己的机会。与此相对应, detroy()方法是在 Servlet析构时候调用的,提供给 Servlet组件进行释放使用过的资源的机会。而 sevice()方法是用来处理每一个 Web容器传递进来的请求与响应的。
通用 Servlet实现了 Servlet的接口方法 init(), 方法中保存了 Servlet容器传递过来的 ServletConfig对象。如下图程序片段所示,

public  void  init(ServletConfig config) throws  ServletException {  
    // 保存 Servlet 配置对象,对于处理一个 HTTP 请求的许多操作都需要的 Servlet 配置所包含的信息,例如 ,Servlet 名字, Servlet 配置的参数等等  
     this .config = config;  
    // 代理到另外一个无参数的 init 方法,这个方法是一个抽象方法,它是一个占位符,提供给子类重写并初始化的机会  
     this .init();  
}  

通用 Servlet的方法 service()是一个显示定义的抽象方法,要求实现类必须重写这个方法的实现。因为不同的 Servlet实现会依赖不同的协议,实现各不相同。
destroy()是一个方法占位符,子类可以有选择的实现进而进行资源的清理。
HTTP Servlet正如我们所愿,实现了通用 Servlet的 service()方法,根据 HTTP请求中所标识的方法,把 HTTP请求派遣到不同的处理方法中。如下图所示,
这里写图片描述
这些不同的方法有不同的实现,这些处理方法中的大部分是占位符,但是,它为 doOptions()和 doTrace()提供了具体实现,因为对于不同的 HTTP Servlet组件,这两个方法的行为基本是不变的。他们都是用于返回服务器信息和调试目的。如下图代码注释,

public  void  service(ServletRequest req, ServletResponse res)   throws ServletException, IOException  
{  
    HttpServletRequest  request;  
    HttpServletResponse response;  
    try {  
        // 既然是 HTTP 协议绑定的 Serlvet, 强制转换到 HTTP 的领域模型  
        request = (HttpServletRequest) req;  
        response = (HttpServletResponse) res;  
} catch (ClassCastException e) {  
       // 如果传入的 HTTP 请求和 HTTP 响应不是 HTTP 的领域模型,则抛出 Servlet 异常,这个异常会被 Servlet 容器所处理  
        throw new ServletException( "non-HTTP request or response" );  
    }  
    // 如果传入的请求和响应是预期的 HTTP 请求和 HTTP 响应,则调用 service() 方法。  
    service(request, response);  
}  
protected void service(HttpServletRequest req, HttpServletResponse resp)  throws ServletException, IOException  
{  
    // 从 HTTP 请求中取得这次请求所使用的 HTTT 方法  
    String method = req.getMethod();  
    if (method.equals(METHOD_GET)) {  
        // 如果这次请求使用 GET 方法  
        // 取得这个 Servlet 的最后修改的时间  
        long lastModified = getLastModified(req);  
        if (lastModified == -1) {  
            //-1 代表这个 Servlet 不支持最后修改操作,直接调用 doGet() 进行处理 HTTP GET 请求  
            doGet(req, resp);  
        } else {  
            // 如果这个 Servlet 支持最后修改操作,取得请求头中包含的请求的最后修改时间  
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);  
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {  
            // 如果请求头中包含的修改时间早于这个 Servlet 的最后修改时间,说明这个 Servlet 自从客户上一次 HTTP 请求已经被修改了 , 设置最新修改时间到响应头中  
              maybeSetLastModified(resp, lastModified);  
              // 调用 doGet 进行进行处理 HTTP GET 请求  
              doGet(req, resp);  
             } else {  
                // 如果请求头中包含修改时间晚于这个 Servlet 的最后修改时间,说明这个 Servlet 自从请求的最后修改时间后没有更改过,这种情况下,仅仅返回一个 HTTP 响应状态 SC_NOT_MODIFIED  
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);  
             }  
        }  
    } else if (method.equals(METHOD_HEAD)) {  
        // 如果这次请求使用 POST 方法  
        // 如果这个 Servlet 支持最后修改操作,则设置这个 Servlet 的最后修改时间到响应头中  
        long lastModified = getLastModified(req);  
        maybeSetLastModified(resp, lastModified);  
       // 和对 HTTP GET 方法处理不同的是,无论请求头中的修改时间是不是早于这个 Sevlet 的最后修改时间,都会发 HEAD 响应给客户,因为 HTTP HEAD 响应是用来查询 Servlet 头信息的操作  
        doHead(req, resp);  
    } else if (method.equals(METHOD_POST)) {  
        // 如果这次请求使用 POST 方法  
        doPost(req, resp);  
    } else if (method.equals(METHOD_PUT)) {  
        // 如果这次请求使用 PUT 方法  
        doPut(req, resp);    
    } else if (method.equals(METHOD_DELETE)) {  
        // 如果这次请求使用 DELETE 方法  
        doDelete(req, resp);  
    } else if (method.equals(METHOD_OPTIONS)) {  
        // 如果这次请求使用 OPTIONS 方法  
        doOptions(req,resp);  
    } else if (method.equals(METHOD_TRACE)) {  
        // 如果这次请求使用 TRACE 方法  
        doTrace(req,resp);  
    } else {  
       // 如果这次请求是其他未知方法,返回错误代码 SC_NOT_IMPLEMENTED 给 HTTP 响应,并且显示一个错误消息,说明这个操作是没有实现的  
        String errMsg = lStrings.getString( "http.method_not_implemented" );  
        Object[] errArgs = new Object[1];  
        errArgs[0] = method;  
        errMsg = MessageFormat.format(errMsg, errArgs);  
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);  
    }  
} 

从上面两个方法的实现中我们可以看到, HTTP Servlet根据不同的 HTTP方法进行了 HTTP请求的分发。这样,不同方法的请求会使用不同的处理方法进行处理。事实上 doGet() doPost(), doPut(), doDelete()都是占位符实现,子类应该有选择的重写这些方法来实现真正的服务逻辑。 Spring Web MVC就是通过重写这些方法,开始控制流的实现的。
下面是 doGet()方法的代码注释。

protected  void  doGet(HttpServletRequest req, HttpServletResponse resp)    throws ServletException, IOException  
{  
    // 取得请求头包含的 HTTP 协议版本  
    String protocol = req.getProtocol();  
    // 直接发送错误消息,可见,一个子类需要重写这些占位符方法 doGet(), doPost(), doPut(), doDelete() 中的一个或者多个  
    String msg = lStrings.getString( "http.method_get_not_supported" );  
    if (protocol.endsWith( "1.1" )) {  
        // 如果是 HTTP 1.1, 发送 SC_METHOD_NOT_ALLOWED  
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
    } else {  
        // 如果是 HTTP 的更早版本则发送 SC_BAD_REQUEST  
        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
    }  
}  

doHead()方法的实现通过对 HTTP响应类进行包装,实现了 NoBodyReponse类,这个类忽略了对 HTTP响应体的输出。重用了 doGet()方法的实现,并且保留了 HTTP头信息的输出。所以,如果一个子类 Servlet重写了 doGet()方法,这个方法 doHead()是不需要重写的。代码注释如下:

protected  void  doHead(HttpServletRequest req, HttpServletResponse resp)  throws ServletException, IOException  
{  
    // 构造一个特殊的响应类,这个类内部忽略了所有的响应体的输出  
    NoBodyResponse response = new NoBodyResponse(resp);  
    // 重用 doGet() 处理器罗杰  
    doGet(req, response);  
    // 设置响应体的字节大小,尽管响应体并没有输出,但是客户端可能关系这个信息  
    response.setContentLength();     
}  

doPost(), doPut(), doDelete()方法的实现和 doGet()方法的实现是类似的,他们都是一个占位符的实现,子类 Servlet需要有选择的进行重写进而实现真正需要的 HTTP服务。
然而, doOptions()和 doTrace()对任何 Servlet的实现,基本是不变的,他们是用来查询服务器信息和调试所用,他们的实现如下,

// 这个方法 doOptions() 设置支持的 HTTP 方法名称到相应头中  
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException  
{  
    // 取得当前这个 Servlet 以及父类 Servlet 声明的所有方法,这些方法不包括本类 HTTP Servlet 所声明的方法  
    Method[] methods = getAllDeclaredMethods( this.getClass());  
    // 初始化的时候,假设它不支持任何 HTTP 方法  
    boolean ALLOW_GET = false ;  
    boolean ALLOW_HEAD = false ;  
    boolean ALLOW_POST = false ;  
    boolean ALLOW_PUT = false ;  
    boolean ALLOW_DELETE = false ;  
    boolean ALLOW_TRACE = true ;  
    boolean ALLOW_OPTIONS = true ;  
    // 根据子类 Servlet 是否重写了 HTTP Servlet 的占位符方法,判断是否这个 Servlet 实现支持这种 HTTP 方法,例如,如果子类 Servlet 实现了 doGet(), 然后 HTTP GET 方法是支持的  
    for ( int i=0; i<methods.length; i++) {  
        // 遍历得到的所有生命的方法  
        Method m = methods[i];  
        // 如果名字是 doGet(), doPost(),  doPut() 或者 doDelete(), 它支持相应的方法  
        if (m.getName().equals( "doGet" )) {  
            ALLOW_GET = true ;  
            ALLOW_HEAD = true ;  
        }  
        if (m.getName().equals( "doPost" ))  
            ALLOW_POST = true ;  
        if (m.getName().equals( "doPut" ))  
            ALLOW_PUT = true ;  
        if (m.getName().equals( "doDelete" ))  
            ALLOW_DELETE = true ;  

    }  
    // 把支持的 HTTP 方法名称拼接成逗号分割的字符串,例如, “GET, POST”, “GET, POST, PUT, DELETE”  
    String allow = null ;  
    if (ALLOW_GET)  
        if (allow== null ) allow=METHOD_GET;  
    if (ALLOW_HEAD)  
        if (allow== null ) allow=METHOD_HEAD;  
        else allow += ", " + METHOD_HEAD;  
    if (ALLOW_POST)  
        if (allow== null ) allow=METHOD_POST;  
        else allow += ", " + METHOD_POST;  
    if (ALLOW_PUT)  
        if (allow== null ) allow=METHOD_PUT;  
        else allow += ", " + METHOD_PUT;  
    if (ALLOW_DELETE)  
        if (allow== null ) allow=METHOD_DELETE;  
        else allow += ", " + METHOD_DELETE;  
    if (ALLOW_TRACE)  
        if (allow== null ) allow=METHOD_TRACE;  
        else allow += ", " + METHOD_TRACE;  
    if (ALLOW_OPTIONS)  
        if (allow== null ) allow=METHOD_OPTIONS;  
         else allow += ", " + METHOD_OPTIONS;  
    // 把支持的方法拼接成的字符串设置到 HTTP 协议的相应头中,这个值的 key 是 "Allow"  
    resp.setHeader( "Allow" , allow);  
}  
// 这个方法返回一个字符串到 HTTP 响应体里面,这个字符串包含请求 URL, 版本信息以及请求的头信息,主要是用来调试  
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)  throws ServletException, IOException  
{    
    int responseLength;  
    // 连接 URI 字符串和协议版本信息字符串  
    String CRLF = "/r/n" ;  
    String responseString = "TRACE " + req.getRequestURI()+  " " + req.getProtocol();  
    Enumeration reqHeaderEnum = req.getHeaderNames();  
    // 遍历所有的请求头信息  
    while ( reqHeaderEnum.hasMoreElements() ) {  
        String headerName = (String)reqHeaderEnum.nextElement();  
        // 拼接所有的请求头到字符串中,并且使用:分割名值对,每对头信息之间使用回车换行进行分隔  
        responseString += CRLF + headerName + ": " +  
        req.getHeader(headerName);  
    }  
    // 附着回车换行符到字符串结尾  
    responseString += CRLF;  
    // 取得字符串字节长度信息  
    responseLength = responseString.length();  
    // 设置响应类型为 message/http  
    resp.setContentType( "message/http" );  
    // 设置响应体的长度  
    resp.setContentLength(responseLength);  
    // 输出字符串消息到响应中  
    ServletOutputStream out = resp.getOutputStream();  
    out.print(responseString);  
    // 关闭相应流,结束操作  
    out.close();  
    return ;  
}   

从上面的代码注释中,我们可以看到 Servlet规范中的 HTTP Servlet的实现只是一个占位符实现,并不包含完全的服务实现,一些服务的实现是由子类 Servlet完成的。 Spring Web MVC就是通过实现这些占位符方法来派遣 HTTP请求到 Spring Web MVC的控制器组件方法的。
下面一节我们将深入剖析 Spring Web MVC 的控制器是如何进行派遣和处理 HTTP 请求的

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值