21.14.2 支持静态资源的HTTP缓存
静态资源需要对’Cache-Control’和一些可选报头进行配置,以发挥其最佳性能。配置一个ResourceHttpRequestHandler来处理静态资源,不仅可以在本地就通过读取某个文件的metadata来写入’Last-Modified’报头,而且可以在属性被配置的情况下写入’Cache-Control’报头。
你可以在一个ResourceHttpRequestHandler中设置cachePeriod属性,或者使用一个CacheControl的实例,它可以支持更多特定的指令:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public-resources/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
}
}
或者在XML中这样配置:
<mvc:resources mapping="/resources/**" location="/public-resources/">
<mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>
21.14.3 在Controller中支持Cache-Control,Etag和Last-Modified响应报头
Controller控制器可支持’Cache-Control’,’ETag’与/或’If-Modified-Since’的HTTP请求。在响应里设置’Cache-Control’报头是强烈推荐的。这涉及到为指定的请求计算lastModified参数的long型值,和/或一个Etag的值,然后与请求中的’If-Modified-Since’报头值进行对比,并有可能返回一个状态代码为304(未被修改)的响应。
参考”Using HttpEntity”这一章节,Controller控制器可以使用HttpEntity类型来与请求/响应进行交互。Controller返回的ResponseEntity对象可以被包含到响应中的HTTP缓存中,如下所示:
@RequestMapping("/book/{id}")
publicResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30,TimeUnit.DAYS))
.eTag(version) // 也可以设置lastModified
.body(book);
}
上面的做法中,不仅可在响应里包含’ETag’和’Cache-Control’报头,而且如果在客户端发送的有条件的报头里,匹配到了Controller中设置的缓存信息时,会把响应转换成HTTP 304 Not Modified的空body响应。
@RequestMapping的方法同样也支持类似操作,如下所示:
@RequestMapping
public StringmyHandleMethod(WebRequest webRequest, Model model) {
long lastModified = // 1. 特定应用计算出来的值
if(request.checkNotModified(lastModified)) {
// 2. 退出的捷径 –不需要做更多处理
return null;
}
// 3.或者需要对请求做更多处理,比如准备响应内容等。
model.addAttribute(...);
return "myViewName";
}
上面的例子有两个重点:调用request.checkNotModified(lastModified)和返回null。在这个方法返回true前,会先把响应设置为304状态。之后,基于此方法就会导致Spring MVC不对此请求做更多的处理。
这个方法里面有3个入参值请注意:
- request.checkNotModified(lastModified)会用lastModifed与请求中的’If-Modified-Since’报头值进行比较
- request.checkNotModified(eTag)会与请求中的’ETag’报头值进行比较
- request.checkNotModified(eTag,lastModified)以上两个行为都会做,意味着两个条件都得满足,否则会返回HTTP 304 Not Modified的响应。
21.14.4 Shallow Etag的支持
Etag是通过Servlet的过滤器ShallowEtagHeaderFilter来支持的。它是一个普通的Servlet过滤器,因此,他可以结合任何web框架来使用。ShallowEtagHeaderFilter过滤器正如其名,会创建一个shallow Etag(相对于deep ETag,有更深含义)。这个过滤器缓存渲染好的JSP的内容(或其他内容),并在其上生成一个MD5哈希值,并把它作为Etag报头,在响应中进行返回。当下次客户端请求同样的资源时,会使用这个哈希值作为If-None-Match的值。过滤器会检测到这个值,再次渲染此视图,并比较这两个哈希值,如果相同,会返回304。这个过滤器不会偷懒,还是会重新渲染视图,只是会省下一些带宽,渲染好的响应不会回传给客户端。
请注意,这种策略会节省网络带宽而不是CPU,每个请求的整个响应还是会被处理出来。在controller级别的其他策略(如上所述)就能既节省网络带宽,又避免了再次处理。可以在mvc-config-static-resources中配置。
在web.xml中配置ShallowEtagHeaderFilter:
<filter>
<filter-name>etagFilter</filter-name>
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>etagFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>
或者在Servlet3.0+环境下:
public classMyWebAppInitializer extendsAbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[]getServletFilters() {
return new Filter[] { newShallowEtagHeaderFilter() };
}
}
详见”21.15 基于代码的Servlet容器初始化”。