zoukankan      html  css  js  c++  java
  • 图解 & 深入浅出 JavaWeb:Servlet 再说几句

    Writer      :BYSocket(泥沙砖瓦浆木匠)

    微         博:BYSocket

    豆         瓣:BYSocket

    FaceBook:BYSocket

    Twitter    :BYSocket

    上一篇的《 Servlet必会必知 》受到大家一致好评 — (感谢 读者 及 OSC 推荐 每日一’搏’)image

    后来觉得还有些东西没点到,这边补充补充。

    一、回到 HttpServlet 的 service方法

    Servlet 基础接口定义了用于客户端请求处理的service方法。 当请求到达Servlet容器,由Servlet容器路由到一个Servlet实例

    比如说 javax.servlet.http.HttpServlet 类 ,其中有一个 protected void service 方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";
     
    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
     
    private static final String LSTRING_FILE =
            "javax.servlet.http.LocalStrings";
    private static ResourceBundle lStrings =
            ResourceBundle.getBundle(LSTRING_FILE);
    /**
     * HTTP状态码304
     */
    public static final int SC_NOT_MODIFIED = 304;
     
    /**
     * 接收来自 public service方法的标准HTTP请求,
     * 并将它们分发给此类中定义的doXXX方法。
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 获取请求方法名
        String method = req.getMethod();
        // 如果是GET请求
        if (method.equals(METHOD_GET)) {
            // 上一次修改HttpServletRequest对象的时间
            long lastModified = getLastModified(req);
            // 没有改变
            if (lastModified == -1) {
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    // 获取请求头中服务器修改时间
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // 获取无效
                    ifModifiedSince = -1;
                }
                // 如果请求头服务器修改时间迟
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
                                  //maybeSetLastModified(resp, lastModified);
                    // 调用doGet方法
                                    doGet(req, resp);
                } else {
                    // 304 HTTP状态码
                    resp.setStatus(SC_NOT_MODIFIED);
                }
            }
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
          //maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            // 如果没有被请求到的话
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            // 501 HTTP状态码
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

    代码逻辑详解如下:

    1、HttpServlet的 protected void service方法 用于接受 public service接收的标准HTTP请求

    也就是说,HttpServlet 重写了父类 GenericServlet 的 service方法。如图显示的是该方法,将从容器获取的 ServletRequest 和 ServletResponse 对象强制转化成 用于HTTP处理的HttpServletRequest 和 HttpServletResponse 对象。然后将两个对象路由给了 HttpServlet的 protected void service方法(图中代码选中处)

    image

    2、然后根据请求的方法名,分发到此类定义的doXXX方法。如果没有被请求到的话,则返回501 HTTP 状态码。

    这样子仿佛明白了什么,也就是说,如果你在 HelloServlet重写doGet方法,这里分发到就是HttpServlet的子类HelloServlet的doGet方法。

    哦~ 还有,501 HTTP 状态码 — 未实现(Not implemented)表示服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。原来如此,所谓死记硬背这些HTTP 状态码有什么用?这样的记忆才是最有效的。

    休息休息,小广告插一下 :(维持生计,O(∩_∩)O~)

    涉及到的代码都会在开源项目 servlet-core-learning 。简介 — Servlet/JSP学习积累的例子,是Java EE初学者及Servlet/JSP核心技术巩固的最佳实践

    大致就是这两步骤。这就是service的工作流程

    1、接受 public service接收的标准HTTP请求。

    2、分发到定义的doXXX方法

    二、GET 请求的处理详解

    上面对于GET请求代码处理如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 如果是GET请求
    if (method.equals(METHOD_GET)) {
        // 上一次修改HttpServletRequest对象的时间
        long lastModified = getLastModified(req);
        // 没有改变
        if (lastModified == -1) {
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                // 获取请求头中服务器修改时间
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // 获取无效
                ifModifiedSince = -1;
            }
            // 如果请求头服务器修改时间迟
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
              //maybeSetLastModified(resp, lastModified);
                // 调用doGet方法
                doGet(req, resp);
            } else {
                // 304 HTTP状态码
                resp.setStatus(SC_NOT_MODIFIED);
            }
        }
    }

    这里,

    1、定义了 getLastModified(req) 方法。用于获取上一次修改HttpServletRequest对象的时间。如果lastModified为默认的 –1L,则总是刷新

    这个getLastModified,是HttpServlet定义了用于支持有条件GET操作。即当客户端通过GET请求获取资源时,当资源自第一次获取那个实际点发生更改后才再次发生数据,否则将使用客户端缓存的数据。

    在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。

    2、如果getLastModified方法的返回值是一个正数,那就要分以下两种情况考虑:

        (1)如果请求头没有包含If-Modified-Since头字段(应该是第一次访问资源时候) 或者 其getLastModified返回值比If-Modified-Since头字段指定时间,则调用doGet返回生成response 和 设置Last-Modified 消息头

        (2)如果其getLastModified返回值比If-Modified-Since头字段指定时间,则返回一个304状态给客户端,表示让客户端继续使用以前缓存的页面

    比如说 304 这个场景我在《 JavaEE 要懂的小事:一、图解Http协议 》文章中提到,第一次访问 百度 首页时,有些资源会成功获取 返回200再次F5,有些资源或直接调用客户端的缓存数据,则返回304

    image1_thumb

    三、Servlet线程问题

    Servlet容器可以并发路由多个请求到 Servlet 的 service方法。为了处理这些请求,Servlet必须在并发及线程安全问题做好处理。上一篇的《 Servlet必会必知 》提到定义全局变量会造成线程安全问题。在开发Servlet时,考虑线程安全问题提出了一下解决

    1、实现 SingleThreadModel 接口

        Servlet2.4 已经提出不提倡使用。实现此接口,Servlet容器为每个新的请求创建一个单独的Servlet实例。这会有严重性能问题

    2、同步锁

        使用synchronized关键字,虽然可以保证只有一个线程可以访问被保护区段,已达到保证线程安全。但是系统性能及并发量大大降低。不可取~

    3、避免使用实例变量,即Servlet中全局变量。使用局部变量 (推荐)

        方法中的局部变量分配在空间,每个线程有私有的栈空间。因此访问是线程安全的。

    我想到了以下一个问题:

    既然Sevlet的全局变量是线程不安全的,那SpringMVC Controller 也一样。那我们在Controller定义个 XXXService 变量会不会造成线程安全呢?
    答:因为这是Spring的一个Service Bean,是线程安全的,所以可以作为单例使用,不会造成线程安全。

    四、总结(别忘了点赞哦)

    补充文章内容要点:

    HttpServlet service 方法详解

    深入理解 代码 对HTTP状态码的运用

    Servlet的线程安全问题

    欢迎点击我的博客及GitHub — 博客提供RSS订阅哦

    ———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

  • 相关阅读:
    JavaScript定时器及相关面试题
    单线程JavaScript
    webpack基础入门
    SQL Server 服务器器信息备份(二)--权限备份
    SQL Server 服务器器信息备份(一)--login新建脚本备份
    Raid与DAN、SAN、NAS基础
    AlwaysOn可用性组功能测试(三)--其他测试
    AlwaysOn可用性组功能测试(二)--SQL Server群集故障转移对AlwaysOn可用性组的影响
    AlwaysOn可用性组功能测试(一)--AlwaysOn故障转移测试
    AlwaysOn可用性组测试环境安装与配置(一)--SQL群集环境搭建
  • 原文地址:https://www.cnblogs.com/Alandre/p/4779897.html
Copyright © 2011-2022 走看看