故事通常是这样开始的:

从前,有一个程序猿,他语重心长地对孙子说:“孩子,要是你以后写servlet,最好不要重写service方法啊”

孙子大为不解,程序猿又说:“听爷爷的,准没错,爷爷的爷爷就是这么说的……”

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

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

先来看一个具体的例子:

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

 

 

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

一切从代码里面来,我们先抛开理论,看一个具体的code,我编写了一个index.html,如下:

 

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

 

这是我第一次访问这个页面(表示本地并没有对这个文件的缓存),我们来看看http请求和响应的消息头:

 

图一

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

 

图二

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

原来对于静态资源来说:

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

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

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

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

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

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

 

然后是MeServlet:

 

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

 

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

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

 

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

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

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

 

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

 

这次再访问:

 

终于出现了久违的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有了新的理解,为什么我们不应该重写这个方法。

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

扩展阅读

JavaScript数组去重(12种方法,史上最全)

七点建议助您写出优雅的Java代码

MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

Spring Boot教你一种方法生成全局唯一ID

排查Java线上服务故障的方法和实例分析

MySQL大数据量分页查询方法及其优化

来源:https://my.oschina.net/dtkking/blog/89443

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

  1. myeclipse实现Servlet实例(2) 继承GenericServet类实现,需要重写service方法

    1.在myeclipse新建web project,配置Tomcat(在myeclipse的Window--preferences) 2.然后在src新建servlet文件( 此处放在com.tsin ...

  2. (转) 为什么不应该重写service方法?

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

  3. idea中实现类快速重写service方法 快捷键

    1.在实现类中 CTRL+O 快捷键,会弹出所有方法 2.选择service中的方法,会自动重写

  4. Servlet中service()方法

    在学习Servlet的过程中,我们大多时候编码都是直接继承HttpServlet这个类,并且重写doGet ,doPost,但是查看Api时我们会发现Servlet接口 ,GenericSevlet抽 ...

  5. HttpServlet Service方法

    service() 方法是执行实际任务的主要方法.Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端. 每次服务器接 ...

  6. servlet学习记录:Servlet中的service()方法

    Servlet的生存时间是由init,service,destory方法构成,这里分析一下service这个方法 Servlet接口中定义了一个service()方法,而我们一般是使用HttpServ ...

  7. httpservlet类中两个service方法

    在浏览器访问html页面时,当数据提交给servlet时发生了什么,这是我们需要了解的. 1.我们需要了解一下servlet的继承体系. servlet接口 ------->GenericSer ...

  8. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  9. EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态

    本文目录 查看实体当前.原始和数据库值:DbEntityEntry 查看实体的某个属性值:GetValue<TValue>方法 拷贝DbPropertyValues到实体:ToObject ...

随机推荐

  1. MFC入门经典

    今天向同学请教了下MFC的入门问题,当真有种"听君一席话,胜读十年书"的感觉.我个人以为每个学习C++控制台类型编程的新手都希望能够把小黑窗变为交互简单的窗口程序,这就促使我们学习 ...

  2. jQueryUI Sortable 应用Demo

    最近工作用需要设计一个自由布局的页面设计.我选了jQuery UI 的 sortable ,可以拖拽,自由排序 使用很方便,写一个demo,做个记录. 第一.单项目自由排序 下图效果 代码段: < ...

  3. pymysql增删改查

    #!/usr/bin/env python # encoding: utf-8  # Date: 2018/6/24 # 1.增删改import pymysql conn = pymysql.conn ...

  4. mysql索引原理与慢查询优化1

    一 介绍 为何要有索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句 ...

  5. 前端自动化之ts编译

    前端自动化之ts编译 gulp引用包:gulp-tsc gulpfiles.js代码: var typescript = require('gulp-tsc'); gulp.task('compile ...

  6. SpringMVC Controller 介绍(详细深刻)

    一.简介 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Mo ...

  7. 10-编译PHP并与nginx整合

    nginx的URL重写.nginx+PHP的配置也是不可不学的部分.PHP自己手动编译,mysql就自己yum了. yum install 安装mysql 同时进行php的编译,手动编译php 这么多 ...

  8. shell 别名alias

    在这说下 shell   命令 alias 别名   看个人爱好 设置. 直接执行命令  显示当前所有别名 alias  别名='新的别名'  该命令在当窗口关闭以后 会失效  想要永久生效  需要在 ...

  9. opennebula模板对照比较

    良好模板 有问题模板

  10. 截取utf8中文字符串

    英文直接截取即可. 中文应字节长度会乱码,应先转unicode截取. 如下: #-*- coding:utf8 -*- s = u'截取中文' s.decode('utf8')[0:3].encode ...