为什么不应该重写 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>
我们来访问这个页面看看。
Image(2)
这是我第一次访问这个页面(表示本地并没有对这个文件的缓存):
我们来看看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的配置:
<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 {@Overrideprotected 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 {@Overrideprotected long getLastModified(HttpServletRequest req) {/*** 这里你要自己决定动态内容的最后修改时间,例如你可以返回* -- 数据缓存最后更新的时间* -- 简单起见,我们假设最后的修改时间是 1000*/return 1000;}@Overrideprotected 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 {@Overrideprotected long getLastModified(HttpServletRequest req) {/*** 这里你要自己决定动态内容的最后修改时间,例如你可以返回* -- 数据缓存最后更新的时间* -- 简单起见,我们假设最后的修改时间是 1000*/return 1000;}@Overrideprotected 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有了新的理解,为什么我们不应该重写这个方法。
万事有例外,如果你需要实现一个前端控制器的话,就是另外一回事了,这留给大家自己思考。
为什么不应该重写 service 方法?相关推荐
- 为什么不应该重写service方法?
2019独角兽企业重金招聘Python工程师标准>>> 故事通常是这样开始的: 从前,有一个程序猿,他语重心长地对孙子说:"孩子,要是你以后写servlet,最好不要重写s ...
- 为什么不应该重写service方法
转载自 为什么不应该重写service方法 故事通常是这样开始的: 从前,有一个程序猿,他语重心长地对孙子说:"孩子,要是你以后写servlet,最好不要重写service方法啊&quo ...
- 这是小小本周的第六篇,本篇小小将会介绍一个很古老很古老很古老的为什么系列之不能重写service方法。...
这是小小本周的第六篇,本篇小小将会介绍一个很古老很古老很古老的为什么系列之不能重写service方法. 小小最近看到一年一度的考研,心生感慨,不过,作为一个专科,没法参与今年的考研,但是没关系,小小明 ...
- [javaweb] servlet介绍与servlet的继承关系 和 service 方法 (一)
Servlet servlet是Server Applet的简称,servlet是运行在服务器上的一个小程序,用来处理服务器请求的.一般的网页程序,是通过浏览器访问来实现的,在这个过程中,浏览器发送访 ...
- 不推荐重写service
不推荐重写HttpServlet类的service, 原因是HttpServlet的service方法里面有一串神奇的代码:与浏览器有一个约定! 而这个约定的作用 就是可以让网页刷新时不必重新请求一 ...
- JAVA中重写equals()方法的同时要重写hashcode()方法
object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true:注意:当此方法 ...
- java如何重写_java中如何重写一个方法
方法的重写: 1.在子类中可以根据需要对从基类中继承来的方法进行重写. 2.重写的方法和被重写的方法必须具有相同方法名称.参数列表和返回类型. 3.重写方法不能使用比被重写的方法更严格的访问权限. 在 ...
- 重写 equals 方法就一定要重写 hashCode 方法?其实有个前提
作者 l 会点代码的大叔(CodeDaShu) 如果问到 == 和 equals 的区别,相信很多程序员同学都能脱口而出:一个是判断地址,一个是判断内容. 但是如果继续追问:"你重写过 eq ...
- 重写equals方法的hashcode_Java equals 和 hashCode 的这几个问题可以说明白吗?
前言 上一篇文章 如何妙用Spring 数据绑定机制,灵魂追问 环节留下了一个有关 equals 和 hashcode 问题 .基础面试经常会碰到与之相关的问题,这不是一个复杂的问题,但很多朋友都苦于 ...
最新文章
- ★移动机器人 激光 构建地图 定位
- linux基础命令练习1
- 一个字符串中到底能有多少个字符? 我竟然算错了!
- OData model cache logic in gateway system
- matlab中数组创建方法
- c语言求平衡因子,平衡二叉树(AVL树)的基本操作
- Python编程基础21:GUI编程
- Codeforces Round #716 (Div. 2) C. Product 1 Modulo N
- 在MAC环境下之以太坊(ethereum)开发环境安装
- 打造线上的大数据风控,我们发现了这三个坑
- outlook导入服务器邮件,OUTLOOK怎么导入邮件?
- php checkbox默认选择问题,PHP_php select,radio和checkbox默认选择的实现方法,这是扩展yibing的select默认选择 - phpStudy...
- 掌握聚合管道操作,熟悉Map-Reduce操作
- android为什么总是闪退怎么办,手机老是闪退怎么办【解决方法】
- Technical Artist 的不归路 —— 场景构图中的物件
- 宏观经济学gdp计算方法_曼昆宏观经济学读书笔记(一):GDP、通胀率、失业率...
- 工厂控制灯光系统小结(观小蜜蜂老师教学视频有感)
- Pycharm 5.0 and Pycharm 2016 的破解
- js制作 电子杂志(附源代码)
- CSS3 Generator在线工具
热门文章
- oracle 修改表
- 为啥不装杀毒软件?全家桶太多...
- 2017python学习的第五天:模块
- (基础篇)数组的详解与使用
- 生成数据库测试数据的方法
- 【VMCloud云平台】SCDPM(六)额外篇-DPM备份到Azure上
- 字符串(strcmp)
- 一个关于用户体验的思考
- openCV4.2.0 error: (-5:Bad argument) CAP_IMAGES: can’t find starting number (in the name of file)
- 详解/etc/profile、/etc/bash.bahsrc、~/.profile、~/.bashrc的用途