这是小小本周的第六篇,本篇小小将会介绍一个很古老很古老很古老的为什么系列之不能重写service方法。
小小最近看到一年一度的考研,心生感慨,不过,作为一个专科,没法参与今年的考研,但是没关系,小小明年都能参加​考研了,争取早日考研,早日上岸。​

故事的开头

从前有做山,山上有做庙,庙里有个老和尚,老和尚说,在很久很久以前是这样的。
孩子啊,以后你要写servlet,绝对绝对绝对不能重写service方法,孩子对孩子的孩子说道,绝对不能重写service方法,孙子睁大眼睛问道,巴巴为什么要这样?
“听爷爷的绝对没错”

今日话题: 为什么不能重写Service方法

一个具体的例子

小小正在看osc,看见一篇大作,只见小小左手一个动作,右手一个动作,熟练的打开chrome开发者工具,猛然间看到了如下的结果。
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图
这个时候,聪明的你,一定发现了,除了第一个名为12-77118的请求返回状态为200,其他都为304,那么200和304有什么区别?
一切都从代码中来。
编写一个index.html如下

<html> 
<body> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
<h3>I'm a test page . </h3> 
</body> 
</html>

访问这个页面
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(1)
第一次访问
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(2)
第二次访问
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(3)
这次请求的头信息中,多了一条if-modified-since,而且返回的响应中,状态变成了304,这是什么原因?
对于静态页面来说。
1. 当浏览器第一次发起请求,server会在响应中告诉浏览器这个资源最后修改的时间。
2. 浏览器也很聪明,当你再次请求这个资源时候,浏览器会询问server这个资源自从上次告诉我的最后修改时间以来有没有被修改过。
3. 如果资源没有被修改,server返回304状态码,并不会再次把资源发送给浏览器,浏览器会使用本地的缓存文件。所有的静态资源如果没有发生变化,通常不会传递多次,不管什么浏览器都是这样的,这叫http缓存协商。这是约定优于配置的体现。

一个更加复杂的例子

在这个例子中,我们请求一个控制器,然后转向一个视图。
web.xml中的servlet配置如下

<web-app> 
    <servlet> 
        <servlet-name>me</servlet-name> 
        <servlet-class>com.me.web.MeServlet</servlet-class> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>me</servlet-name> 
        <url-pattern>/test</url-pattern> 
    </servlet-mapping> 
</web-app>

MeServlet

public class MeServlet extends HttpServlet { 
    @Override 
    protected void service(HttpServletRequest req, HttpServletResponse res) 
            throws ServletException, IOException { 
        /** 
         * 1. 处理具体的业务: 
         * -- 处理请求参数 
         * -- 检查缓存 
         * -- 处理具体数据 
         * -- 更新缓存 
         */ 
        doBizLogic(req, res); 
        /** 
         * 2. 根据处理的结果转向具体的视图: 
         * -- 这里假设就是 index.html 
         */ 
        getServletContext() 
                .getRequestDispatcher("/index.html").include(req, res); 
    } 
    public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
        System.out.println("do biz."); 
    } 
}

再次不断地刷新
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(4)
可以发现,无论什么时候,每次相应的状态都是200,index.html的内容都能被完整的记录下来发送给浏览器,这为什么不能进行缓存协商呢,原因是因为缓存协商基于http请求和响应头中的Modified信息的,如果没有这个信息,是无法进行缓存协商的,对于动态内容来说,server无法帮我们决定内容是不是有改变的,也无法决定修改时间。
继续修改

public class MeServlet extends HttpServlet { 
    @Override 
    protected long getLastModified(HttpServletRequest req) { 
        /** 
         * 这里你要自己决定动态内容的最后修改时间,例如你可以返回 
         * -- 数据缓存最后更新的时间 
         * -- 简单起见,我们假设最后的修改时间是 1000 
         */ 
        return 1000; 
    } 
    @Override 
    protected void service(HttpServletRequest req, HttpServletResponse res) 
            throws ServletException, IOException { 
        /** 
         * 1. 处理具体的业务: 
         * -- 处理请求参数 
         * -- 检查缓存 
         * -- 处理具体数据 
         * -- 更新缓存 
         */ 
        doBizLogic(req, res); 
        /** 
         * 2. 根据处理的结果转向具体的视图: 
         * -- 这里假设就是 index.html 
         */ 
        getServletContext() 
                .getRequestDispatcher("/index.html").include(req, res); 
    } 
    public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
        System.out.println("do biz."); 
    } 
}

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

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

先别沮丧,忘了我们要解释什么问题吗——为什么不要重写service方法。也许你已经猜到了,如果你看看service方法的实现,现在你已经明白了,service方法自己实现了缓存协商的机制,如果我们重写它,反而将这中良好的机制给去掉了。
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(5)
我们再修改一下,这次我们重写doGet,在doGet中完成完全相同的逻辑:

public class MeServlet extends HttpServlet { 
    @Override 
    protected long getLastModified(HttpServletRequest req) { 
        /** 
         * 这里你要自己决定动态内容的最后修改时间,例如你可以返回 
         * -- 数据缓存最后更新的时间 
         * -- 简单起见,我们假设最后的修改时间是 1000 
         */ 
        return 1000; 
    } 
    @Override 
    protected void doGet(HttpServletRequest req, HttpServletResponse res) 
            throws ServletException, IOException { 
        /** 
         * 1. 处理具体的业务: 
         * -- 处理请求参数 
         * -- 检查缓存 
         * -- 处理具体数据 
         * -- 更新缓存 
         */ 
        doBizLogic(req, res); 
        /** 
         * 2. 根据处理的结果转向具体的视图: 
         * -- 这里假设就是 index.html 
         */ 
        getServletContext() 
                .getRequestDispatcher("/index.html").include(req, res); 
    } 
    public void doBizLogic(HttpServletRequest request, HttpServletResponse response) { 
        System.out.println("do biz."); 
    } 
}

再次访问
语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(6)

语重心长 | 项目开发中,绝对绝对绝对不能重写Service方法插图(7)

此时已经可以看到缓存已经出来啦。

作者

小小,一个双鱼座的文艺作者,来自于二线城市,生活在一线城市,在大城市的孤独与漂泊只有我自己一个人知道。
我是小小,就是文中的老和尚,我们下期再见。