• 为什么不应该重写service方法?


    我编写了一个index.html,如下:

    01 <html
    02 <body
    03 <h3>I'm a test page . </h3
    04 <h3>I'm a test page . </h3
    05 <h3>I'm a test page . </h3
    06 <h3>I'm a test page . </h3
    07 <h3>I'm a test page . </h3
    08 <h3>I'm a test page . </h3
    09 <h3>I'm a test page . </h3
    10 </body
    11 </html>

    我们来访问这个页面看看。

    Image(2)

    这是我第一次访问这个页面(表示本地并没有对这个文件的缓存):

    我们来看看http请求和响应的消息头:

    《图:一》

    为了作为对比,我们再F5刷新一次:

    《图:二》

    这次请求的头信息中多了一条If-Modified-Since,而且返回的响应中,状态变为了304,这是怎么回事?还记得红薯那篇文章页中的304么,你会发现,304多出现在对于静态资源的请求上面。

    原来对于静态资源来说:

    1. 当浏览器第一次发起请求时(请求头中没有If-Modified-Since),server会在响应中告诉浏览器这个资源最后修改的时间(响应头中的Last-Modified)。(见图一)

    2. 浏览器也很聪明,当你再次(点击链接,或者F5,或者回车,但是不能是ctrl+F5)请求这个资源时,浏览器会询问server这个资源自上次告诉我的最后修改时间以来有没有被修改(请求头中If-Modified-Since)。(见图二)

    3. 如果资源没有被修改,server返回304状态码,并不会再次将资源发送给浏览器,浏览器则很知趣的使用本地的缓存文件。(见图二)

    所以所有的静态资源如果没有发生变化,通常是不会传递多次的,不管什么浏览器或者server都应该遵守这种询问的约定。看起来很爽啊,很智能是不是?这种约定的机制就是 http缓存协商——这是约定优于配置的又一体现。

    有了缓存协商的知识,理解为什么我们不应该重写service就很容易了。还是从代码出发,这次我们看一个复杂一点的例子:

    在这个例子中,我们请求一个控制器(MeServlet),然后转向一个视图(index.html),为了简单起见,web.xml中将只有这个servlet的配置:

    01 <web-app
    02     <servlet
    03         <servlet-name>me</servlet-name
    04         <servlet-class>com.me.web.MeServlet</servlet-class
    05     </servlet
    06     <servlet-mapping
    07         <servlet-name>me</servlet-name
    08         <url-pattern>/test</url-pattern
    09     </servlet-mapping
    10 </web-app>

    然后是MeServlet:

    01 public class MeServlet extends HttpServlet { 
    02     @Override 
    03     protected void service(HttpServletRequest req, HttpServletResponse res) 
    04             throws ServletException, IOException { 
    05         /** 
    06          * 1. 处理具体的业务: 
    07          * -- 处理请求参数 
    08          * -- 检查缓存 
    09          * -- 处理具体数据 
    10          * -- 更新缓存 
    11          */ 
    12         doBizLogic(req, res); 
    13         /** 
    14          * 2. 根据处理的结果转向具体的视图: 
    15          * -- 这里假设就是 index.html 
    16          */ 
    17         getServletContext() 
    18                 .getRequestDispatcher("/index.html").include(req, res); 
    19     
    20     public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
    21         System.out.println("do biz."); 
    22     
    23 }

    可以看到,每次F5刷新返回的状态码都是200,让我们看看具体的请求和响应头:

    我们发现无论我们如何刷新页面,每次响应状态都是200,index.html的内容每次都被完整的发送给浏览器,这看起来很笨,为什么不像静态资源一样进行缓存协商呢?原因是缓存协商是基于http请求和响应头中的Modified信息的,如果没有这个信息,是无法进行缓存协商的。而对于动态内容而言,server无法帮我们决定内容是不是有改变,也无法替我们决定动态内容的最后修改时间。

    所以它不会帮我们在响应中加上Last-Modified,我们必须自己来做这件事,我们小小地修改一下MeServlet:

    01 public class MeServlet extends HttpServlet { 
    02     @Override 
    03     protected long getLastModified(HttpServletRequest req) { 
    04         /** 
    05          * 这里你要自己决定动态内容的最后修改时间,例如你可以返回 
    06          * -- 数据缓存最后更新的时间 
    07          * -- 简单起见,我们假设最后的修改时间是 1000 
    08          */ 
    09         return 1000
    10     
    11     @Override 
    12     protected void service(HttpServletRequest req, HttpServletResponse res) 
    13             throws ServletException, IOException { 
    14         /** 
    15          * 1. 处理具体的业务: 
    16          * -- 处理请求参数 
    17          * -- 检查缓存 
    18          * -- 处理具体数据 
    19          * -- 更新缓存 
    20          */ 
    21         doBizLogic(req, res); 
    22         /** 
    23          * 2. 根据处理的结果转向具体的视图: 
    24          * -- 这里假设就是 index.html 
    25          */ 
    26         getServletContext() 
    27                 .getRequestDispatcher("/index.html").include(req, res); 
    28     
    29     public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
    30         System.out.println("do biz."); 
    31     
    32 }

    你会看到getLastModified这个方法是重写的,说明HttpServlet中已经有了这个方法,我们使用这个方法来告诉server在这个动态资源中,最后内容变化的时间是多少。最理想的情况是server会自己回调这个方法,那就太省心啦。

    我们先访问的看看:发现依然每次都是200,server没有告诉浏览器最后的修改时间,缓存协商机制无法工作。

    先别沮丧,忘了我们要解释什么问题吗——为什么不要重写service方法。也许你已经猜到了,如果你看看service方法的实现,现在你已经明白了,service方法自己实现了缓存协商的机制,如果我们重写它,反而将这中良好的机制给去掉了。

    我们再修改一下,这次我们重写doGet,在doGet中完成完全相同的逻辑:

    01 public class MeServlet extends HttpServlet { 
    02     @Override 
    03     protected long getLastModified(HttpServletRequest req) { 
    04         /** 
    05          * 这里你要自己决定动态内容的最后修改时间,例如你可以返回 
    06          * -- 数据缓存最后更新的时间 
    07          * -- 简单起见,我们假设最后的修改时间是 1000 
    08          */ 
    09         return 1000
    10     
    11     @Override 
    12     protected void doGet(HttpServletRequest req, HttpServletResponse res) 
    13             throws ServletException, IOException { 
    14         /** 
    15          * 1. 处理具体的业务: 
    16          * -- 处理请求参数 
    17          * -- 检查缓存 
    18          * -- 处理具体数据 
    19          * -- 更新缓存 
    20          */ 
    21         doBizLogic(req, res); 
    22         /** 
    23          * 2. 根据处理的结果转向具体的视图: 
    24          * -- 这里假设就是 index.html 
    25          */ 
    26         getServletContext() 
    27                 .getRequestDispatcher("/index.html").include(req, res); 
    28     
    29     public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
    30         System.out.println("do biz."); 
    31     
    32 }

    这次在访问,

    终于出现了久违的Last-Modified,再次回车请求页面,哈哈变成304了。

    现在你也许已经清楚了,为什么不应该重写service方法,似乎是为了保留HttpServlet默认实现的缓存协商的机制;其实还有另外一个原因:就是禁用你没有在servlet中重写的方法,例如post、head等,这样就从一定程度上提高了安全性。

    理论到此为止,现在让我们来看看缓存协商机制有什么实际的好处:

    还是红薯的那边文章,我们现在全加载(ctrl+F5)一次看看,

    我们看到总共发起了45个请求,请求的数据量为198.93KB,然后F5刷新一次:

    这次只有36个请求,数据量只有23.62KB

    我们看到这篇文章被9960个id访问, 而每一个id实际上可能访问这个页面多次(像我这样,实际的数据可能得问问红薯),然后我们看到很多304静态资源都是整站通用的:

    如果你是osc的常客,并且不经常更换浏览器,不经常清理缓存,甚至其他人的头像都可以是通用的,为了简单起见,我们这里考虑每个id都只访问这个页面一次,并且假设所有的资源都已经缓存在用户本地,得出:

    (198.93-23.62)×9960 = 1746086.6KB = 1705.1637M = 1.665G。

    很惊人吧,这只是一个页面,别忘了,我们还假设所有的用户都只访问一次,你想想osc上面有多少篇博文,加起来……

    流量是什么,是银子啊。

    幸运的是,这些省银子的事情浏览器和server都已经帮我们做好了,那我们就不需要关心这个了吗??我们看到12_77118这个请求所占用的资源也不少,如果文章再长点,再长点的话……还会更大。

    如果红薯愿意,也可以让这个请求实现缓存协商,可以进一步减少流量。

    当然这里的计算并不是完全的精确,实际的情况复杂很多,但是这个计算的量级应该是对的,是值得参考的。

    流量涉及的另一个问题就是带宽,以更小的贷款提供更高的并发是每个站长应该追求的。不过考虑到osc以新闻为主,一次性消费,所以……不过那时题外话了。

    好了,如果你有耐心看到这里,我想你也许会对service有了新的理解,为什么我们不应该重写这个方法。

    万事有例外,如果你需要实现一个前端控制器的话,就是另外一回事了,这留给大家自己思考。

    第一次发文,大家开拍吧,:)~~

    原文摘于开源中国http://my.oschina.net/dtkking/blog/89443?from=20121118  ,觉得写得很好, 感谢作者!!

  • 相关阅读:
    LeetCode-1-two-sum
    LeetCode-14-longest-common-prefix
    动态规划--矩阵链乘法
    动态规划--装配线调度算法
    LeetCode-9-palindrome-number
    LeetCode-8-string-to-integer-atoi
    LeetCode-7-Reverse Integer
    SElinux用户管理操作
    Minix3信号处理分析
    面对困难
  • 原文地址:https://www.cnblogs.com/xqzblog/p/dtubest.html
Copyright © 2020-2023  润新知