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

故事通常是这样开始的: 
从前,有一个程序猿,他语重心长地对孙子说:“孩子,要是你以后写servlet,最好不要重写service方法啊” 
孙子大为不解,程序猿又说:“听爷爷的,准没错,爷爷的爷爷就是这么说的……”

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

如果你也曾思考过这个问题,但暂时无解,这篇文章或许可以给你一点启发。

先来看一个具体的例子:

当时我正在osc看红薯的一篇大作,只见我右手F12熟练的打开了chrome的开发者工具,左手迅猛的按了几下F5,然后看到了这个结果。

聪明的你一定已经发现,除了第一个名为12_77118的请求返回状态为200,其他的都为304,那么200和304有什么区别呢?这个稍后解释。

一切从代码里面来,我们先抛开理论,看一个具体的code:

我编写了一个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>

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

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

我们来看看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的配置:

<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.");
    }
}

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

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

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

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方法自己实现了缓存协商的机制,如果我们重写它,反而将这中良好的机制给去掉了。

我们再修改一下,这次我们重写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.");
    }
}

这次在访问,

终于出现了久违的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#viewSource

原文地址:https://www.cnblogs.com/shiysin/p/10276226.html

时间: 2024-10-10 00:14:05

为什么不应该重写service方法?的相关文章

Servlet中service方法

在学习Servlet的过程中,我们大多时候编码都是直接继承HttpServlet这个类,并且重写doGet ,doPost,但是查看Api时我们会发现Servlet接口 ,GenericSevlet抽象类 以及HttpServlet类中都有service方法,那么为什么我们继承HttpSevlet类时不要重写service 而要重写doGet doPost呢?service的作用是什么捏?? 正如上文中所说的,Servlet中,service方法是一直存在的,因为最高层的接口Servlet(像H

servlet的service方法吞异常问题

今天发现一个问题,编写代码向Servlet发送请求,接收响应报文,发出去之后"报服务器返回500"异常,然而从后台日志中没有发现异常记录.然后用浏览器发请求,发现日志中出现了异常记录. 思来想去,不知道怎么回事,之前也遇见过几个此种问题,但是都绕过去了,回头看代码,发现是IDE的问题. 之前的Servlet中的service方法在声明的时候都不会声明抛出异常,然而,如果用eclipse 的快捷键选择重写service方法的话,默认的会声明service方法抛出ServletExcept

HttpServlet Service方法

service() 方法是执行实际任务的主要方法.Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端. 每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务.service() 方法检查 HTTP 请求类型(GET.POST.PUT.DELETE 等),并在适当的时候调用 doGet.doPost.doPut,doDelete 等方法. 下面是该方法的特征: public void s

service方法、doGet方法、doPost方法的区别和一些常见错误 Servlet的生命周期

1.Service方法和doGet方法和doPost方法的区别 * Service方法:  可以处理get/post方式的请求,如果servlet中有Service方法,会优先调用service方法对请求进行处理. * doGet方法:  处理get方式的请求 如果没有重写此方法,会调用父类,父类会返回405错误 * doPost方法:  处理post方式的请求 如果没有重写此方法,会调用父类,父类会返回405错误 * 注意: * 如果在覆写的service方法中调用了父类的service方法(

关于继承时子类重写父类方法和覆盖父类变量的若干问题

假设,子类重载父类的方法,并将子类的成员覆盖. 创建子类对象实例,将其上转型成父类. 例子1 public class Parent { public void init() { System.out.println("1 init parent"); this.demo(); } public void demo() { System.out.println("2 demo parent"); } } public class Son extends Parent

方法重写和方法重载

重写和重载虽然都有一个共同点是发生在方法之间,但是两者之间没有任何的关系! 方法重载:是指一个类中有多个方法,这些方法的名字一样,但是形参不一样 方法重写:发生在子类和父类之间,当子类继承了父类之后,子类就拥有了父类中定义的方法,当然除了构造器没有继承,子类可以去调用父类的方法,也可以重写,在子类中重写父类的方法,保证方法名一样,形参也要一样吧,这样子类再访问这个方法时就会默认使用这个在子类中被重写的方法,而不是父类中那个被覆盖的方法. 当然,我们如果想在子类中使用父类中被覆盖的方法,我们可以使

java重写equals方法(重点讲解)

为什么equals()方法要重写? 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象.这样我们往往需要重写equals()方法. 我们向一个没有重复对象的集合中添加元素时,集合中存放的往往是对象,我们需要先判断集合中是否存在已知对象,这样就必须重写equals方法. 怎样重写equals()方法? 重写equals方法的要求: 1.自反性:对于任何非空引用x,x.equals(x)应该返回tru

javabean对象要实现的接口们和要重写的方法们

在使用list集合的时候,什么也不用. 原因:list允许存储重复的元素. 在使用set集合的时候,要重写,equals()方法 和 hashCode() 方法. 愿意:set集合 不允许存放相同的元素,而默认比较的是两个 对象地址的值,这样,两个对象恒不等.所以 我们需要重写equals()方法,当再次存入的时候用以判定两个对象到底是不是相等.如果两个对象相等了,那么两个 对象必然应该用有相同的hash值,所以在重写equals() 方法的同时,也要重写hashCode()方法. 如果要使用C

【原创】关于java对象需要重写equals方法,hashcode方法,toString方法 ,compareto()方法的说明

在项目开发中,我们都有这样的经历,就是在新增表时,会相应的增加java类,在java类中都存在常见的几个方法,包括:equals(),hashcode(),toString() ,compareto()这四个方法,对于刚刚接触java的初学者来说,对于这块可以没有更深入的了解,只是单纯意义上的复制粘贴,并没有很好的去了解.现在借用这个时间给大家说说这几个方法作用. equals: 比较两个对象相等时使用(需要配合Hashcode一起使用,在后边再进行详细解释)   hashcode: 重写has