SpringMVC(17) - HTTP缓存支持

参考:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#mvc-caching

良好的HTTP缓存策略可以显着提高Web应用程序的性能和客户端的体验。 'Cache-Control'HTTP响应头主要负责这一点,以及条件性响应头,如'Last-Modified'和'ETag'。

'Cache-Control'HTTP响应头建议私有缓存(例如浏览器)和公共缓存(例如代理)如何缓存HTTP响应以供进一步重用。

ETag(entity tag:实体标签)是由HTTP/1.1兼容的Web服务器返回的HTTP响应头,用于确定给定URL的内容更改。它可以被认为是Last-Modified头的更复杂的后继者。当服务器返回带有ETag标头的表示时,在If-None-Match头中,客户端可以在后续GET中使用此标头。如果内容未更改,则服务器返回 304:Not Modified

1. Cache-Control HTTP头
Spring Web MVC支持许多用例和方法来为应用程序配置“Cache-Control”头。虽然RFC 7234第5.2.2节完整地描述了该头及其可能的指令(参考:https://tools.ietf.org/html/rfc7234#section-5.2.2),但有几种方法可以解决最常见的情况。

Spring Web MVC在其几个API中使用配置约定:setCachePeriod(int seconds):

  • -1值:不会生成“Cache-Control”响应头。
  • 0值:将使用“Cache-Control:no-store”指令阻止缓存。
  • n > 0值:将使用'Cache-Control: max-age=n'指令将给定的响应缓存n秒。

CacheControl构建器类简单地描述了可用的“Cache-Control”指令,并使构建自己的HTTP缓存策略变得更容易。构建完成后,可以在几个Spring Web MVC API中接受CacheControl实例作为参数。

// 缓存一小时 - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// 阻止缓存 - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// 在公共和私有缓存中缓存十天,公共缓存不应该转换响应
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();

2. 静态资源的HTTP缓存支持
应使用适当的“Cache-Control”和条件头提供静态资源,以获得最佳性能。 配置ResourceHttpRequestHandler以提供静态资源不仅可以通过读取文件的元数据本地写入“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>

3. 控制器中Cache-Control、ETag和Last-Modified响应头的支持
控制器可以支持'Cache-Control','ETag' 或 'If-Modified-Since'HTTP请求;如果要在响应上设置“Cache-Control”头,确实建议这样做。 这涉及计算给定请求的lastModified long 或 Etag值,将其与“If-Modified-Since”请求头值进行比较,并可能返回状态代码为304(未修改)的响应。

控制器可以使用HttpEntity类型与请求/响应进行交互。 返回ResponseEntity的控制器可以在响应中包含HTTP缓存信息,如下所示:

@GetMapping("/book/{id}")
public ResponseEntity<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 is also available
                .body(book);
}

执行此操作不仅会在响应中包含“ETag”和“Cache-Control”头,而且如果客户端发送的条件头与控制器设置的缓存信息匹配,它还会将响应转换为具有空响应体的 HTTP 304 Not Modified 响应。

@RequestMapping方法也可能希望支持相同的行为。 这可以通过以下方式实现:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. application-specific calculation

    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
    }

    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

这里有两个关键元素:调用request.checkNotModified(lastModified)并返回null。前者在返回true之前设置适当的响应状态和响应头。后者与前者相结合,导致Spring MVC不再对请求进行进一步处理。

请注意,有3种情况:

  • request.checkNotModified(lastModified):将lastModified与'If-Modified-Since'或'If-Unmodified-Since'请求头进行比较
  • request.checkNotModified(eTag):将eTag与'If-None-Match'请求头进行比较
  • request.checkNotModified(eTag, lastModified):同时执行这两项操作,这意味着两个条件都应该有效

当接收到条件 'GET'/'HEAD' 请求时,checkNotModified 将检查资源是否未被修改,如果是,则将导致HTTP 304 Not Modified响应。在条件 'POST'/'PUT'/'DELETE' 请求的情况下,checkNotModified将检查资源是否已被修改,如果已经修改,则将导致HTTP 409 Precondition Failed响应以防止并发修改。

4. 简单ETag支持
Servlet过滤器ShallowEtagHeaderFilter提供对ETag的支持。它是一个普通的Servlet过滤器,因此可以与任何Web框架结合使用。 ShallowEtagHeaderFilter过滤器创建所谓的简单ETag(与深ETag相反,稍后将详细介绍)。过滤器缓存呈现的JSP(或其他内容)的内容,生成MD5哈希,并将其作为ETag返回响应中的头。客户端下次发送对同一资源的请求时,会将该哈希值用作If-None-Match值。过滤器检测到此情况,再次呈现视图,并比较两个哈希值。如果它们相等,则返回304。

请注意,此策略可以节省网络带宽,但不能节省CPU,因为必须为每个请求计算完整响应。控制器级别的其他策略(如上所述)可以节省网络带宽并避免计算。

此过滤器具有writeWeakETag参数,该参数将过滤器配置为写入弱ETag,如下所示:W/"02a2d595e6ed9a0b24f027f2b63b134d6",如RFC 7232第2.3节中所定义(https://tools.ietf.org/html/rfc7232#section-2.3)。

在web.xml中配置ShallowEtagHeaderFilter:

<filter>
    <filter-name>etagFilter</filter-name>
    <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
    <!-- Optional parameter that configures the filter to write weak ETags
    <init-param>
        <param-name>writeWeakETag</param-name>
        <param-value>true</param-value>
    </init-param>
    -->
</filter>

<filter-mapping>
    <filter-name>etagFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

或者在Servlet 3.0+环境中,

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new ShallowEtagHeaderFilter() };
    }

}

有关更多详细信息,参考下文。

5. 基于代码的Servlet容器初始化
在Servlet 3.0+环境中,可以选择以编程方式配置Servlet容器作为替代方法,也可以与web.xml文件结合使用。 下面是注册DispatcherServlet的示例:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }

}

WebApplicationInitializer是Spring MVC提供的一个接口,可确保检测到自定义的实现并自动用于初始化任何Servlet 3容器。 名为AbstractDispatcherServletInitializer的WebApplicationInitializer的抽象基类实现通过简单地重写方法来指定servlet映射和DispatcherServlet配置的位置,从而更容易注册DispatcherServlet。

对于使用基于Java的Spring配置的应用程序,建议使用此方法:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

如果使用基于XML的Spring配置,则应直接从AbstractDispatcherServletInitializer扩展:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

AbstractDispatcherServletInitializer还提供了一种方便的方法来添加Filter实例并使它们自动映射到DispatcherServlet:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }

}

每个过滤器都根据其具体类型添加默认名称,并自动映射到DispatcherServlet。

AbstractDispatcherServletInitializer的isAsyncSupported受保护方法提供了一个单独的位置来启用DispatcherServlet上的异步支持以及映射到它的所有过滤器。 默认情况下,此标志设置为true。

最后,如果需要进一步自定义DispatcherServlet本身,则可以覆盖createDispatcherServlet方法。

猜你喜欢

转载自blog.csdn.net/mytt_10566/article/details/84205605