Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为什么不应该重写 service 方法?

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

作者头像
芋道源码
发布于 2019-09-25 02:04:08
发布于 2019-09-25 02:04:08
42000
代码可运行
举报
文章被收录于专栏:芋道源码1024芋道源码1024
运行总次数:0
代码可运行

来源:http://rrd.me/esQEx

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

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

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

先来看一个具体的例子:

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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多出现在对于静态资源的请求上面。

原来对于静态资源来说:

  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的配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中完成完全相同的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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有了新的理解,为什么我们不应该重写这个方法。

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 芋道源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
图解 & 深入浅出 JavaWeb:Servlet 再说几句
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
二哥聊运营工具
2021/12/17
2250
图解 & 深入浅出 JavaWeb:Servlet 再说几句
Servlet的生命周期
HttpServletservice()http methodservletdoXXX
高大北
2022/06/14
1750
HttpServlet源码分析
1.HttpServlet的用法 提供了创建Http Servlet的抽象类,通过实现此类定义自己的Servlet 2.HttpServlet是否是线程安全 先说结论:HttpServlet不是线程安全 tomcat在只在第一次有请求的时候加载Servlet,加载之后调用init方法进行初始化,容器中只会保存一个Servlet对象,当有Http请求的时候会调用service方法对请求进行处理。所以Servlet不是线程安全的,当我们在Servlet中定义类变量或者处理共享资源时,要注意线程安全问题。简单的理
代码改变世界-coding
2018/07/03
3730
Java Servlet详解(体系结构+注解配置+生命周期)
顾名思义:服务端的小程序 Servlet只是一个接口,定义了Java被浏览器访问到(Tomcat)的识别规则,我们需要定义一个类来实现Servlet接口
一只胡说八道的猴子
2020/09/27
5030
Java Servlet详解(体系结构+注解配置+生命周期)
JavaWeb
将CATALINA_HOME/conf/logging.properties文件中的内容修改如下:
星辰xc
2022/05/11
6.3K0
JavaWeb
Spring mvc HTTP协议之缓存机制
概述 Spring MVC 支持HTTP协议的 Last-Modified 缓存机制。 在客户端地一次输入URL时,服务器端会返回内容和状态码200, 表示请求成功,同时会添加一个“Last-Modified”属性,表示该请求资源的最后修改时间 客户端第二次请求此URL时,客户端会向服务器发送请求头 “IF-Modified-Since”,如果服务端内容没有变化,则自动返回HTTP304状态码(只返回相应头信息,不返回资源文件内容,这样就可以节省网络带宽,提供响应速度和用户体验) Spring MVC 中实
java404
2018/05/18
9820
Servlet API 源码剖析
从目录出发,整个源代码分为 servlet、http、descriptor、annotation 四个部分。
FoamValue
2020/08/31
6650
设计模式 | 行为型 | 模板方法模式
模板方法模式是一种行为设计模式,它在一个超类中定义一个算法骨架,并将某些步骤推迟到子类中实现。
被水淹没
2023/02/25
1970
设计模式 | 行为型 | 模板方法模式
Java Servlet完全教程
Servlet 是一些遵从Java Servlet API的Java类,这些Java类可以响应请求。尽管Servlet可以响应任意类型的请求,但是它们使用最广泛的是响应web方面的请求。Servlet必须部署在Java servlet容器才能使用。虽然很多开发者都使用Java Server Pages(JSP)和 Java Server Faces(JSF) 等Servlet框架,但是这些技术都要在幕后通过Servlet容器把页面编译为Java Servlet。也就是说,了解Java Servlet技术的基础知识对任何Java web开发者来说是很有用的。
用户7353950
2022/05/11
4690
Java Servlet完全教程
Servlet详解
Servlet是server+Applet的缩写,表示一个服务器应用。Servlet就是一套规范,按照这套规范写的代码就可以直接在Java服务器上面运行。
秋白
2019/02/21
6100
Servlet详解
Java-Servlet请求方式doXXX、service 具体分析
说起Servlet的接收处理请求的方式,想必各位都并不陌生,如doGet、doPost、service...
Arebirth
2019/09/24
5440
Java-Servlet请求方式doXXX、service 具体分析
Servlet入门笔记
J2EE(Java 2 Platform Enterprise Edition)是指“Java 2 企业版”
Breeze.
2022/07/12
4190
Servlet入门笔记
重温Java Web的技术细节
-web应用同样也需要一些初始化的参数,但 相对于每一个Servlet有一个ServletConfig来说,一个Web应用(确切的说是每个JVM)仅有一个ServletContext来存放这些参数,这个ServletContext对Web应用中的每个Servlet都可用。
智慧zhuhuix
2020/09/01
1K0
重温Java Web的技术细节
SpringMVC源码解析从service到doDispatch
请求在被Servlet处理之前会先被过滤器处理,之后调用Servlet的service方法来对相应的请求进行处理响应。所以我们这里分析的入口是Servlet的service方法。
JavaEdge
2021/02/22
2590
SpringMVC源码解析从service到doDispatch
SpringMVC源码解析之Last-Modified缓存机制
支持上次修改的HTTP请求,以方便内容缓存。 相同的合同作为Servlet API中的getLastModified方法。 通过委派到org.springframework.web.servlet.HandlerAdapter.getLastModified实施。 默认情况下,任何控制器或HttpRequestHandler Spring的默认框架内可以实现此接口,以实现最后修改时间检查。 注:另类处理的实现方法有不同的最后修改的处理方式。 例如,Spring 2.5的(使用注释的控制器的方法@RequestMapping )通过提供上次修改支持org.springframework.web.context.request.WebRequest.checkNotModified方法,允许主处理程序方法中最后一次修改检查。
JavaEdge
2021/02/22
5510
SpringMVC源码解析之Last-Modified缓存机制
重拾Java Web应用的基础体系结构
一、背景 Spring生态的强大与完善,使得大多数的Java程序员,在刚刚接触Java Web应用开发时,往往依赖于SSM、SpringBoot等各种高级框架。 Java Web的基础的体系结构是什么?到底是怎么运作的?这些高级的框架与基础的体系结构之间是什么关系? 只有真正理清了这些底层基础的结构,才能完全理解高级框架的设计原理,在使用框架开发项目时做到事半功倍。 本文旨在暂时抛开这些高级框架,重走Java Web底层之路。二、Web应用 Web应用的基础模型 用户通过Web浏览器向某个Web应用发出一个
智慧zhuhuix
2020/08/26
5550
重拾Java Web应用的基础体系结构
【Servlet】浅谈一下Servlet的继承关系。
javax.servlet.GenericServlet实现了接口Servlet:
.29.
2022/11/15
4430
【Servlet】浅谈一下Servlet的继承关系。
重拾Java Web应用的基础体系结构
一、背景 Spring生态的强大与完善,使得大多数的Java程序员,在刚刚接触Java Web应用开发时,往往依赖于SSM、SpringBoot等各种高级框架。 Java Web的基础的体系结构是什么?到底是怎么运作的?这些高级的框架与基础的体系结构之间是什么关系? 只有真正理清了这些底层基础的结构,才能完全理解高级框架的设计原理,在使用框架开发项目时做到事半功倍。 本文旨在暂时抛开这些高级框架,重走Java Web底层之路。 二、Web应用 Web应用的基础模型 用户通过Web浏览器向某个Web应用发出一
智慧zhuhuix
2020/08/28
4490
重拾Java Web应用的基础体系结构
Java匹马行天下之JavaWeb核心技术——Servlet
Servlet是在服务器上运行的小程序,也就是一个Java类,但比较特殊,不需要new,自动就可以运行。也有创建、垃圾回收和销毁过程。Servlet是JavaWeb的三大组件之一(Servlet、Filter、Listener),它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
泰斗贤若如
2019/06/18
7500
Servlet的源码分析
4 .使用 ctrl+o 即可查看该类的所有属性与方法 注 : 每种图标代表不同的方法 ,像是第一个红色方框加SF 代表 private 类型的最终静态常量 ,其他类型如图所示
时间静止不是简史
2020/07/24
1.1K0
Servlet的源码分析
相关推荐
图解 & 深入浅出 JavaWeb:Servlet 再说几句
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验