本文是 Spring源码分析:Spring源码分析二十一:Spring MVC③ DispatcherServlet的逻辑 的衍生文章。主要是因为本人菜鸡,在分析源码的过程中还有一些其他的内容不理解,故开设衍生篇来完善内容以学习。
Spring全集目录:Spring源码分析:全集整理
本系列目录如下:
衍生篇目录如下:
以下内容来源于百度百科
在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。Last-Modified格式类似这样:Last-Modified : Fri , 12 May 2006 18:53:33 GMT
客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过:If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
这种方案一般是用在 Controller 实现 org.springframework.web.servlet.mvc.Controller
接口的场景,如下:
@Component("/beanNameSay")
public class BeanNameSayController implements Controller, LastModified {
private long lastModified;
// 逻辑处理
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
return new ModelAndView("/hello.html");
}
@Override
public long getLastModified(HttpServletRequest request) {
// 如果 lastModified 刚初始化,则赋值为当前时间戳并返回。
if (lastModified == 0L){
lastModified = System.currentTimeMillis();
}
return lastModified;
}
}
在 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#getLastModified
和 ServletWebRequest#checkNotModified(long)
。我们一个一个来看
关于 HandlerAdapter#getLastModified
不同的 HandlerAdapter
有不同的实现方式,由于我们这里使用的是 BeanNameUrlHandlerMapping
处理器映射方式。所以这里匹配的HandlerAdapter 方法是 SimpleControllerHandlerAdapter#getLastModified
SimpleControllerHandlerAdapter#getLastModified
的具体实现如下。
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
// 如果handler 是 LastModified 的 实现类。则直接调用 handler 的 getLastModified 方法
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
// 否则返回-1
return -1L;
}
这里就很明确了。在这里会调用 BeanNameSayController#getLastModified
来获取最后修改时间。我们这里实现获取的是第一次请求时候保留的时间戳。
经过上面的分析,我们可以得知 ha.getLastModified(request, mappedHandler.getHandler());
调用返回的值是我们返回的第一次请求时候保留的时间戳。
我们来看看ServletWebRequest#checkNotModified(long)
怎么进行的校验
@Override
public boolean checkNotModified(long lastModifiedTimestamp) {
return checkNotModified(null, lastModifiedTimestamp);
}
@Override
public 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,并返回 notModified
if (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;
}
这里我们就可以知道是什么情况了
SimpleControllerHandlerAdapter#getLastModified
保存到当前请求的时间戳,并将该时间戳 通过 Last-Modified
响应头返回给浏览器。If-Modified-Since
请求头带上上次请求获取到的 Last-Modified
。在DispatcherServlet
的处理过程中会调用 HandlerAdapter#getLastModified
来获取第一步保存的时间戳 lastModified
,这个时间戳是上次调用时候的时间戳。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一起使用。对于我们通过 @RequestMapping("say")
注解方式来修饰的请求,是无法通过实现 org.springframework.web.servlet.mvc.LastModified
接口来实现该功能的。具体原因我们稍后再讲。
这里我们只能通过直接调用 WebRequest#checkNotModified(long)
的方式实现
@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";
}
}
我们来看一看为什么 直接实现 org.springframework.web.servlet.mvc.LastModified
不可以。还是之前 DispatcherServlet#doDispatch
的地方。
可以看到唯一不同的地方时 HandlerAdapter
不同,这里的HandlerAdapter
类型是 RequestMappingHandlerAdapter
类型, 而 RequestMappingHandlerAdapter#getLastModified
方法会调用 RequestMappingHandlerAdapter#getLastModifiedInternal
方法,如下。
@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
return -1;
}
可以看到直接返回的-1,也就是我们这种方式根本没办法修改 lastModified。
所以我们通过上面 hello 的写法,在调用的时候通过 WebRequest#checkNotModified(long)
方法直接进行判断。WebRequest#checkNotModified(long)
方法的逻辑这里不再赘述。
推荐阅读:
Last-Modify、ETag、Expires和Cache-Control
以上:内容部分参考
Last-Modify、ETag、Expires和Cache-Control
腾讯Http教程
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正