前言
本文分析常用的@Controller中方法中实现Last-Modified的功能
一、引入
RequestMappingHandlerAdapter.java
/**
* This implementation always returns -1. An {@code @RequestMapping} method can
* calculate the lastModified value, call {@link WebRequest#checkNotModified(long)},
* and return {@code null} if the result of that call is {@code true}.
*/
@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
return -1;
}
从注释可以看出,RequestMappingHandlerAdapter的获取最后修改时间方法返回值是-1,即默认没有实现lastModified的功能;要想实现lastModified 缓存功能 ,@RequestMapping 方法可以通过WebRequest#checkNotModified(long)方法计算最后修改时间,如果请求的资源没有修改,则目标方法返回null值即可。
二、使用
定义一个@GetMapping方法,并调用WebRequest#checkNotModified(long)方法,即可实现Last-Modified的功能。
private long lastModified = System.currentTimeMillis();
@GetMapping(value = "/checkNotModified", produces = {
MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity checkNotModified(WebRequest request) {
if (request.checkNotModified(lastModified)) {
System.out.println("lastModified : " + lastModified);
return null;
}
return new ResponseEntity();
}
三、原理分析
从WebRequest#checkNotModified(long)方法开始
1、checkNotModified( )
ServletWebRequest.java
public boolean checkNotModified(long lastModifiedTimestamp) {
return checkNotModified(null, lastModifiedTimestamp);
}
public boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {
HttpServletResponse response = getResponse();
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 (validateIfUnmodifiedSince(lastModifiedTimestamp)) {
if (this.notModified && response != null) {
//设置412状态码
response.setStatus(HttpStatus.PRECONDITION_FAILED.value());
}
return this.notModified;
}
//检测If-None-Match
boolean validated = validateIfNoneMatch(etag);
if (!validated) {
//检测If-Modified-Since,是否修改过
validateIfModifiedSince(lastModifiedTimestamp);
}
// Update response
if (response != null) {
boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());
if (this.notModified) {
//没有修改过,并且是get或head请求,设置状态码304
response.setStatus(isHttpGetOrHead ?
HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());
}
if (isHttpGetOrHead) {
if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(HttpHeaders.LAST_MODIFIED)) == -1) {
//设置Last-Modified为最后修改时间lastModifiedTimestamp
response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp);
}
if (StringUtils.hasLength(etag) && response.getHeader(HttpHeaders.ETAG) == null) {
//设置Etag
response.setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag));
}
}
}
return this.notModified;
}
2、validateIfUnmodifiedSince( )
private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {
if (lastModifiedTimestamp < 0) {
return false;
}
//解析出If-Unmodified-Since
long ifUnmodifiedSince = parseDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
if (ifUnmodifiedSince == -1) {
return false;
}
// We will perform this validation...
//notModified 为true时,表明被修改过,响应中设置412状态码
//notModified 为false时,表明没有被修改过
this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));
return true;
}
3、validateIfModifiedSince( )
private boolean validateIfModifiedSince(long lastModifiedTimestamp) {
if (lastModifiedTimestamp < 0) {
return false;
}
//解析出If-Modified-Since
long ifModifiedSince = parseDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
if (ifModifiedSince == -1) {
return false;
}
// We will perform this validation...
//notModified 为true时,表明没有被修改过,响应中设置403状态码
//notModified 为false时,表明被修改过
this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);
return true;
}
总结:
Last-Modified存在的问题:
1、 lastModifiedTimestamp/1000*1000,可以看出资源在1秒之内发生变化时,系统可能察觉不出来;
2、 文件周期生成,内容没有发生变化时,请求时系统会重新获取资源;