文章目录

  • 一、前言
  • 二、Last-Modify
  • 三、实现方案
    • 1. 实现 org.springframework.web.servlet.mvc.LastModified接口
      • 1.1. 简单演示
      • 1.2. 原理分析
        • 1.2.1 HandlerAdapter#getLastModified
        • 1.2.2 ServletWebRequest#checkNotModified(long)
    • 2. 使用WebRequest#checkNotModified(long)
      • 2.1. 简单演示
      • 2.2. 原理分析
  • 四、Http其他缓存方法

一、前言

本文是 Spring源码分析:Spring源码分析二十一:Spring MVC③ DispatcherServlet的逻辑 的衍生文章。主要是因为本人菜鸡,在分析源码的过程中还有一些其他的内容不理解,故开设衍生篇来完善内容以学习。


Spring全集目录:Spring源码分析:全集整理


本系列目录如下:

  1. Spring源码分析十九:Spring MVC① 搭建
  2. Spring源码分析二十:Spring MVC② DispatcherServlet的初始化
  3. Spring源码分析二十一:Spring MVC③ DispatcherServlet的逻辑

衍生篇目录如下:

  1. Spring 源码分析衍生篇十 :Last-Modified 缓存机制
  2. Spring 源码分析衍生篇十一 :HandlerMapping

二、Last-Modify

以下内容来源于百度百科

  1. 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。Last-Modified格式类似这样:Last-Modified : Fri , 12 May 2006 18:53:33 GMT

  2. 客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过:If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
    如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

三、实现方案

1. 实现 org.springframework.web.servlet.mvc.LastModified接口

1.1. 简单演示

这种方案一般是用在 Controller 实现 org.springframework.web.servlet.mvc.Controller 接口的场景,如下:

@Component("/beanNameSay")
public class BeanNameSayController implements Controller, LastModified {private long lastModified;// 逻辑处理@Overridepublic ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {return new ModelAndView("/hello.html");}@Overridepublic long getLastModified(HttpServletRequest request) {// 如果 lastModified  刚初始化,则赋值为当前时间戳并返回。if (lastModified == 0L){lastModified = System.currentTimeMillis();}return lastModified;}
}

在一次请求成功后,第二次请求返回的状态码 304。

1.2. 原理分析

在 Spring源码分析二十一:Spring MVC③ DispatcherServlet的逻辑 一文中,我们其中提到了Last-Modified 缓存机制。本文我们集中关注这一点

 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {...// 获取请求方式String method = request.getMethod();boolean isGet = "GET".equals(method);// 如果是 get请求或者 head 请求则进入该分支if (isGet || "HEAD".equals(method)) {// 调用 HandlerAdapter#getLastModified 方法 来获取最后修改时间long lastModified = ha.getLastModified(request, mappedHandler.getHandler());// 判断到目前为止是否有过修改,没有则直接return。实现缓存的功能if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}...}

我们这里需要关注的方法无非就是两个 HandlerAdapter#getLastModifiedServletWebRequest#checkNotModified(long) 。我们一个一个来看

1.2.1 HandlerAdapter#getLastModified

关于 HandlerAdapter#getLastModified 不同的 HandlerAdapter 有不同的实现方式,由于我们这里使用的是 BeanNameUrlHandlerMapping 处理器映射方式。所以这里匹配的HandlerAdapter 方法是 SimpleControllerHandlerAdapter#getLastModified

SimpleControllerHandlerAdapter#getLastModified 的具体实现如下。

 @Overridepublic long getLastModified(HttpServletRequest request, Object handler) {// 如果handler 是 LastModified 的 实现类。则直接调用 handler 的 getLastModified 方法if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);}// 否则返回-1return -1L;}

这里就很明确了。在这里会调用 BeanNameSayController#getLastModified 来获取最后修改时间。我们这里实现获取的是第一次请求时候保留的时间戳。

1.2.2 ServletWebRequest#checkNotModified(long)

经过上面的分析,我们可以得知 ha.getLastModified(request, mappedHandler.getHandler()); 调用返回的值是我们返回的第一次请求时候保留的时间戳。

我们来看看ServletWebRequest#checkNotModified(long) 怎么进行的校验

 @Overridepublic boolean checkNotModified(long lastModifiedTimestamp) {return checkNotModified(null, lastModifiedTimestamp);}@Overridepublic boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {HttpServletResponse response = getResponse();// notModified  为true 标志没有被修改,默认false// 如果 notModified  已经true || 返回状态码已经不是200直接返回 notModified if (this.notModified || (response != null && HttpStatus.OK.value() != response.getStatus())) {return this.notModified;}// Evaluate conditions in order of precedence.// See https://tools.ietf.org/html/rfc7232#section-6// 解析校验 If-Unmodified-Since 请求头。这个请求头和  If-Modified-Since 请求头相反if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {// 设置状态码 304,并返回 notModifiedif (this.notModified && response != null) {response.setStatus(HttpStatus.PRECONDITION_FAILED.value());}return this.notModified;}// 校验 If-None-Match 请求头。这是针对 Etag 缓存。boolean validated = validateIfNoneMatch(etag);if (!validated) {// 校验 If-Modified-Since 请求头validateIfModifiedSince(lastModifiedTimestamp);}// Update response// 更新 Response。包括状态码等信息if (response != null) {boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());if (this.notModified) {response.setStatus(isHttpGetOrHead ?HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());}if (isHttpGetOrHead) {if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(HttpHeaders.LAST_MODIFIED)) == -1) {response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp);}if (StringUtils.hasLength(etag) && response.getHeader(HttpHeaders.ETAG) == null) {response.setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag));}}}return this.notModified;}// 解析校验 If-Modified-Since 请求头private boolean validateIfModifiedSince(long lastModifiedTimestamp) {if (lastModifiedTimestamp < 0) {return false;}long ifModifiedSince = parseDateHeader(HttpHeaders.IF_MODIFIED_SINCE);if (ifModifiedSince == -1) {return false;}// We will perform this validation...this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);return true;}

这里我们就可以知道是什么情况了

  1. 浏览器第一次请求,一切正常。SimpleControllerHandlerAdapter#getLastModified 保存到当前请求的时间戳,并将该时间戳 通过 Last-Modified 响应头返回给浏览器。
  2. 浏览器第二次请求,会使用 If-Modified-Since 请求头带上上次请求获取到的 Last-Modified。在DispatcherServlet 的处理过程中会调用 HandlerAdapter#getLastModified 来获取第一步保存的时间戳 lastModified,这个时间戳是上次调用时候的时间戳。
  3. WebRequest#checkNotModified(long) 方法校验了下面三个请求头来确定请求是否被修改:
    - If-Unmodified-Since :与 If-Modified-Since 相反,只要它没有被最后给定的日期之后修改。如果请求在给定日期之后被修改,则该响应将是412(先决条件失败)错误。
    - If-None-Match : 针对 ETag 缓存。有服务器没有ETag与给定资源匹配的情况下,服务器才会返回具有状态的请求资源。对于其他方法,仅当最终现有资源ETag不符合任何列出的值时才会处理该请求。
    - If-Modified-Since :只有当它已经给定的日期之后被最后修改。如果请求没有被修改,那么响应将是304没有任何主体的;Last-Modified头将包含最后一次修改的日期。不同于If-Unmodified-Since,If-Modified-Since只能与GET或HEAD一起使用。

2. 使用WebRequest#checkNotModified(long)

对于我们通过 @RequestMapping("say") 注解方式来修饰的请求,是无法通过实现 org.springframework.web.servlet.mvc.LastModified 接口来实现该功能的。具体原因我们稍后再讲。

这里我们只能通过直接调用 WebRequest#checkNotModified(long) 的方式实现

2.1. 简单演示

@RestController
@RequestMapping("say")
public class SayController{private long lastModified;@RequestMapping("hello")public String hello(WebRequest webRequest) {if(webRequest.checkNotModified(lastModified)){return null;}lastModified = System.currentTimeMillis();return "hello";}
}

2.2. 原理分析

我们来看一看为什么 直接实现 org.springframework.web.servlet.mvc.LastModified 不可以。还是之前 DispatcherServlet#doDispatch 的地方。


可以看到唯一不同的地方时 HandlerAdapter 不同,这里的HandlerAdapter 类型是 RequestMappingHandlerAdapter 类型, 而 RequestMappingHandlerAdapter#getLastModified 方法会调用 RequestMappingHandlerAdapter#getLastModifiedInternal 方法,如下。

 @Overrideprotected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {return -1;}

可以看到直接返回的-1,也就是我们这种方式根本没办法修改 lastModified。
所以我们通过上面 hello 的写法,在调用的时候通过 WebRequest#checkNotModified(long) 方法直接进行判断。WebRequest#checkNotModified(long) 方法的逻辑这里不再赘述。

四、Http其他缓存方法

推荐阅读:
Last-Modify、ETag、Expires和Cache-Control


以上:内容部分参考
Last-Modify、ETag、Expires和Cache-Control
腾讯Http教程
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

Spring 源码分析衍生篇十 :Last-Modified 缓存机制相关推荐

  1. Spring 源码分析衍生篇十三 :事务扩展机制 TransactionSynchronization

    文章目录 一.前言 二.TransactionSynchronization 1. TransactionSynchronization 1.1 TransactionSynchronization ...

  2. Spring 源码分析衍生篇三 : lookup-method 和 replaced-method

    文章目录 一.前言 二.基本使用 1. 作用 三.原理实现 1. 预处理 1.1 AbstractBeanDefinition#prepareMethodOverrides 1.2 Autowired ...

  3. Spring源码分析前篇

    穷举法:把生活所见所闻全部归纳到我们所学的知识体系中来,加以思考总结变成自己的东西.(举例子) 类比法:用自己熟悉的方法(利用自己已有的知识体系),去对比学习新的知识. 学习最好的方法:就是重复 Sp ...

  4. Spring源码分析系列-循环依赖和三级缓存

    目录 循环依赖 多级缓存 一级缓存 二级缓存 当循环依赖遇上AOP 三级缓存 Spring三级缓存源码实现 总结 循环依赖   BeanFactory作为bean工厂管理我们的单例bean,那么肯定需 ...

  5. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  6. spring源码分析之BeanDefinition相关

    目录 前言: BeanDefinition的家族系列 1.BeanDefintion的UML类图 2.BeanDefintion家族类详解 2.1.通用接口 2.2.BeanDefintion接口 2 ...

  7. Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean

    前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...

  8. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  9. Spring 源码分析 (一)——迈向 Spring 之路

    一切都是从 Bean 开始的 在 1996 年,Java 还只是一个新兴的.初出茅庐的编程语言.人们之所以关注她仅仅是因为,可以使用 Java 的 Applet 来开发 Web 应用.但这些开发者很快 ...

最新文章

  1. python系统问题
  2. 产品经理如何做好数据埋点
  3. python不想学了-嫌Python太慢但又不想学C/C++?来了解下JIT技术
  4. Kafka创建查看topic,生产消费指定topic消息
  5. oracle recover redo,oracle redo log日志(当前或非当前日志)损坏之后的db恢复
  6. 个人技术博客--团队Git规范(参考西瓜学长)
  7. 用 VR 检查代码,码农们的必备神器!
  8. Java语言的基础知识6
  9. 《Android Property
  10. 现代信号处理 张贤达_清华信号处理著名学者张贤达去世,享年74岁
  11. Excel中,条件格式的跟多应用-「数字条」「图标集」
  12. 【Python】闭包Closure
  13. 怎么在Guitar Pro乐谱中加入哇音
  14. AIDA64 Business Edition 4.00.2700绿色单文件破解版下载
  15. 程序员做饭指南,GitHub教程来了
  16. 飞思卡尔单片机学习记录(一)
  17. 改造家里的开关成为智能开关,保留原有开关控制,零火版,基础入门(一)
  18. linux脚本 输出双引号,Linux Shell中三种引号的用法及区别
  19. 通过对TCPWindowSize的调整对网络流量的性能优化
  20. Hive——多行转一行及一行转多行

热门文章

  1. esaypoi导出excel后office打开报错
  2. 程序员表白代码大全,快来向你的ta表白吧___
  3. iOS编程基础-Swift(三)-变量与简单类型
  4. 2010年11月13日
  5. 深入Android 【二】 —— 架构和学习
  6. 使用Calendar加一天,减一天
  7. java项目集合,你想找的java项目都在这了
  8. 【C语言】循环语句for
  9. 电脑技巧:Win10粘贴文件到C盘提示没有权限的解决方法
  10. 微软发布 Entity Framework EF Core 8 或 EF8