在Spring Boot中实现HTTP缓存的方法

缓存是HTTP协议的一个强大功能,但由于某些原因,它主要用于静态资源,如图像,CSS样式表或JavaScript文件,但是,HTTP缓存不仅限于这些,还可以将其用于动态计算的资源。

通过少量工作,您可以加快应用程序并改善整体用户体验。在本文中,您将学习 如何使用内置的HTTP响应缓存机制来实现缓存SpringBoot控制器的结果 。

1.如何以及何时使用HTTP响应缓存?

您可以在应用程序的多个层上进行缓存。数据库具有其缓存存储,Web客户端也在其需要重用的信息。HTTP协议负责网络通信。缓存机制允许我们通过减少客户端和服务器之间传输的数据量来优化网络流量。

何时优化: 当Web资源不经常更改或您确切知道何时更新时 ,就可以使用HTTP缓存进行优化。一旦确定了HTTP缓存的竞争者,就需要选择合适的方法来管理缓存的验证。 HTTP协议定义了几个请求和响应标头,您可以使用它们来控制客户端何时清除缓存 。

选择适当的HTTP标头取决于您要优化的特定情况。但是无论用例如何,我们可以根据缓存的验证发生在哪里进行缓存管理选项的划分。

2.客户端缓存验证

当您知道请求的资源在给定的时间内不会更改时,服务器可以将此类信息作为响应标头发送到客户端。基于该信息,客户端决定是否应该再次获取资源或重用先前下载的资源。

有两种可能的选项可以描述客户端何时应该再次获取资源并删除存储的缓存值。所以让我们看看他们是如何运行的。

HTTP缓存在固定的时间内有效:如果要 阻止客户端在指定时间内重新获取资源 ,则应该使用 Cache-Control 标头,可以在其中指定应该重新获取所获取数据的时间。

通过将标头的值设置为 max-age = <seconds>, 可以通知客户端多长时间不再需要再次获取资源。 缓存值的有效性与请求的时间有关。

为了设置在Spring的控制器中的HTTP标头,就要在RESTContoller用 ResponseEntity 包装类 。

@GetMapping(<font>"/{id}"</font><font>)
ResponseEntity<Product> getProduct(@PathVariable <b>long</b> id) {
  </font><font><i>// …</i></font><font>
  CacheControl cacheControl = CacheControl.maxAge(30, TimeUnit.MINUTES);
  <b>return</b> ResponseEntity.ok()
      .cacheControl(cacheControl)
      .body(product);
}
</font>

HTTP标头的值只是一个常规字符串,但是 Cache-Control Spring为我们提供了一个特殊的构建器类,它可以防止我们犯下像拼写错误这样的小错误。

HTTP缓存有效到固定日期:有时您知道资源何时会发生变化。对于公布的数据而言,这是常见的情况,如天气预报或昨天交易时段计算的股市指标。 资源的确切到期日期可以向客户端公开。 应该使用 Expires HTTP标头。应使用标准化数据格式 之一格式化日期值。

Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123

Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036

Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format

幸运的是,Java附带了第一个这些格式的预定义格式化程序。可以在下面找到将标题设置为当天结束的示例。

@GetMapping(<font>"/forecast"</font><font>)
ResponseEntity<Forecast> getTodaysForecast() {
  </font><font><i>// ...</i></font><font>
  ZonedDateTime expiresDate = ZonedDateTime.now().with(LocalTime.MAX);
  String expires = expiresDate.format(DateTimeFormatter.RFC_1123_DATE_TIME);
  <b>return</b> ResponseEntity.ok()
      .header(HttpHeaders.EXPIRES, expires)
      .body(weatherForecast);
}
</font>

请注意, HTTP日期格式需要有关时区的信息 。这就是上面的例子使用 ZonedDateTime的原因 。如果您尝试使用 LocalDateTime ,则最终会在运行时出现以下错误消息:

java.time.temporal.UnsupportedTemporalTypeException:不支持的字段:OffsetSeconds

如果响应中存在 Cache-Control 和 Expires 标头,则客户端仅使用 Cache-Control 。

3.服务器端缓存验证

在基于用户输入的动态生成的内容中,更常见的是服务器不知道何时将改变所请求的资源。在这种情况下,客户端可以使用先前获取的数据,但首先,它需要询问服务器该数据是否仍然有效。

自第一次握手以来资源是否被修改?如果跟踪Web资源的修改日期,则可以将此类日期作为响应的一部分公开给客户端。在下一个请求中,客户端将此日期发送回服务器,以便它可以验证自上一个请求以来资源是否已被修改。如果资源未更改,则服务器不必再次重新发送数据。相反,它使用304 HTTP代码响应,没有任何有效负载。

要公开资源的修改日期,您应该设置 Last-Modified 标头。Spring的ResponseEntity构建器有一个名为 lastModified()[url=https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.HeadersBuilder.html#lastModified-long-]的特殊方法[/url],它可以帮助您以正确的格式分配值。你会在一分钟内看到这一点。

但 在发送完整响应之前,应检查客户端是否 在请求中 包含 If-Modified-Since 标头 。客户端根据 Last-Modified 标头的值设置其值,该标头是与此特定资源的先前响应一起发送的。

如果 If-Modified-Since 标头的值与所请求资源的修改日期匹配,则可以节省一些带宽并使用空主体响应客户端。

Spring再次提供了一个辅助方法,简化了上述日期的比较。这个名为 checkNotModified()的 方法可以在 WebRequest 包装器类中找到,您可以将其作为输入添加到控制器的方法中。

让我们仔细看看完整的例子。

@GetMapping(<font>"/{id}"</font><font>)
ResponseEntity<Product> getProduct(@PathVariable <b>long</b> id, WebRequest request) {
  Product product = repository.find(id);
  <b>long</b> modificationDate = product.getModificationDate()
      .toInstant().toEpochMilli();

  <b>if</b> (request.checkNotMod<b>if</b>ied(mod<b>if</b>icationDate)) {
    <b>return</b> <b>null</b>;
  }

  <b>return</b> ResponseEntity.ok()
      .lastModified(modificationDate)
      .body(product);
}
</font>

首先,我们获取所请求的资源并访问其修改日期。我们将日期转换为自格林威治标准时间1970年1月1日以来的毫秒数,因为这是Spring框架期望的格式。

然后,我们将日期与 If-Modified-Since 标头的值进行比较,并在正匹配上返回一个空。否则,服务器发送具有 Last-Modified 标头的适当值的完整响应主体。

凭借所有这些知识,您几乎可以涵盖所有常见的缓存设置选项。但是有一个更重要的机制你应该知道的是......

使用ETag进行资源版本控制

到目前为止,我们定义了有效期的精确度,精确度为1秒。但是如果你需要 更好的精度而不仅仅是一秒 呢?这就是ETag的用武之地。

可以将ETag定义为唯一的字符串值,该值在该时间点明确地标识资源。 通常,服务器根据给定资源的属性计算ETag,或者,如果可用,则计算其最新修改日期。

客户端和服务器之间的通信流程与修改日期检查的情况几乎相同。只有标题的名称和值不同。

服务器在名为 ETag 的标题中设置ETag值。当客户端再次访问资源时,它应该在名为 If-None-Match 的头中发送其值。如果该值与资源的新计算的ETag匹配,则服务器可以使用空内容和HTTP代码304进行响应。

在Spring中,您可以实现ETag服务器流程,如下所示:

@GetMapping(<font>"/{id}"</font><font>)
ResponseEntity<Product> getProduct(@PathVariable <b>long</b> id, WebRequest request) {
  Product product = repository.find(id);
  String modificationDate = product.getModificationDate().toString();
  String eTag = DigestUtils.md5DigestAsHex(modificationDate.getBytes());

  <b>if</b> (request.checkNotMod<b>if</b>ied(eTag)) {
    <b>return</b> <b>null</b>;
  }

  <b>return</b> ResponseEntity.ok()
      .eTag(eTag)
      .body(product);
}
</font>

与前一个样本几乎相同,并且修改日期检查。我们只是使用不同的值进行比较(以及MD5算法来计算ETag)。 请注意, WebRequest 有一个重载的 checkNotModified() 方法来处理表示为字符串的ETag。

如果 Last-Modified 和 ETag 工作几乎相同,为什么我们需要两者吗?

Last-Modified vs ETag

正如我已经提到的, Last-Modified 标头 不太精确, 因为它具有一秒的精度。为了获得更高的精度,请选择 ETag 。

当您不跟踪 资源 的修改日期 时,您也被迫使用 ETag 。服务器可以根据资源的属性计算其值。将其视为对象的哈希码。

如果资源具有其修改日期并且您可以使用一秒精度,请使用 Last-Modified 标头。为什么?因为 ETag计算可能是一项昂贵的操作 。

顺便提一下,值得一提的是HTTP协议没有指定用于计算ETag的算法。选择算法时,您应该关注它的速度。

本文重点介绍缓存GET请求,但您应该知道服务器可以使用 ETag 来同步更新操作。

Spring ETag过滤器

因为ETag只是内容的字符串表示,所以服务器可以使用响应的字节表示来计算其值。意思是 你可以实际将ETag分配给任何响应。

Spring框架为您提供了ETag响应过滤器实现 ,它可以为您完成。您所要做的就是在应用程序中配置过滤器。

在Spring应用程序中添加HTTP过滤器的最简单方法是通过配置类中的 FilterRegistrationBean 。

@Bean
<b>public</b> FilterRegistrationBean filterRegistrationBean () {
  ShallowEtagHeaderFilter eTagFilter = <b>new</b> ShallowEtagHeaderFilter();
  FilterRegistrationBean registration = <b>new</b> FilterRegistrationBean();
  registration.setFilter(eTagFilter);
  registration.addUrlPatterns(<font>"/*"</font><font>);
  <b>return</b> registration;
}
</font>

在这种情况下,对 addUrlPatterns() 的调用是多余的,因为默认情况下所有路径都匹配。我把它放在这里证明你可以控制Spring应该添加ETag值的资源。

除了ETag生成之外,过滤器还会在可能的情况下响应HTTP 304和空体内容。

但要注意。

ETag计算可能很昂贵。 对于某些应用程序启用此过滤器实际上可能会导致弊大于利 。在使用之前考虑一下您的解决方案。

结论

现在您已了解如何使用HTTP缓存优化应用程序,哪种方法最适合您,因为应用程序有不同的需求。

您了解到客户端缓存验证是最有效的方法,因为不涉及数据传输。在适用时,您应该始终支持客户端缓存验证。

我们还讨论了服务器端验证并比较了 Last-Modified 和 ETag 标头。最后,您了解了如何在Spring应用程序中设置全局ETag过滤器。

总结

以上所述是小编给大家介绍的在Spring Boot中实现HTTP缓存的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • spring boot+spring cache实现两级缓存(redis+caffeine)

    spring boot中集成了spring cache,并有多种缓存方式的实现,如:Redis.Caffeine.JCache.EhCache等等.但如果只用一种缓存,要么会有较大的网络消耗(如Redis),要么就是内存占用太大(如Caffeine这种应用内存缓存).在很多场景下,可以结合起来实现一.二级缓存的方式,能够很大程度提高应用的处理效率. 内容说明: 缓存.两级缓存 spring cache:主要包含spring cache定义的接口方法说明和注解中的属性说明 spring boot

  • Spring Boot集成Redis实现缓存机制(从零开始学Spring Boot)

    本文章牵涉到的技术点比较多:spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对以上这些技术点有一定的了解或者也可以先看看这篇文章,针对文章中实际的技术点在进一步了解(注意,您需要自己下载Redis Server到您的本地,所以确保您本地的Redis可用,这里还使用了MySQL数据库,当然你也可以内存数据库进行测试).这篇文章会提供对应的Eclipse代码示例,具体大体的分如下几个步骤: (1)新建Java Maven Pro

  • 详解Spring Boot使用redis实现数据缓存

    基于spring Boot 1.5.2.RELEASE版本,一方面验证与Redis的集成方法,另外了解使用方法. 集成方法 1.配置依赖 修改pom.xml,增加如下内容. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2.配置R

  • 在Spring Boot中如何使用数据缓存

    在实际开发中,对于要反复读写的数据,最好的处理方式是将之在内存中缓存一份,频繁的数据库访问会造成程序效率低下,同时内存的读写速度本身就要强于硬盘.Spring在这一方面给我们提供了诸多的处理手段,而Spring Boot又将这些处理方式进一步简化,接下来我们就来看看如何在Spring Boot中解决数据缓存问题. 创建Project并添加数据库驱动 Spring Boot的创建方式还是和我们前文提到的创建方式一样,不同的是这里选择添加的依赖不同,这里我们添加Web.Cache和JPA依赖,如下图

  • Spring Boot 基于注解的 Redis 缓存使用详解

    看文本之前,请先确定你看过上一篇文章<Spring Boot Redis 集成配置>并保证 Redis 集成后正常可用,因为本文是基于上文继续增加的代码. 一.创建 Caching 配置类 RedisKeys.Java package com.shanhy.example.redis; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import org.springf

  • Spring Boot 中使用cache缓存的方法

    一.什么是缓存 Cache Cache 一词最早来自于CPU设计 当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理:没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存.正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在CPU缓存中,只有大约10%需要从内存读取.这大大节省了CPU直接读取内存的

  • SpringBoot项目中使用redis缓存的方法步骤

    本文介绍了SpringBoot项目中使用redis缓存的方法步骤,分享给大家,具体如下: Spring Data Redis为我们封装了Redis客户端的各种操作,简化使用. - 当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作 - 当Redis作为缓存使用时,我们可以将它作为Spring Cache的实现,直接通过注解使用 1.概述 在应用中有效的利用redis缓存可以很好的提升系统性能,特别是对于查询操作,可以有效的减少数据库压力. 具体的代码参照该

  • 实例详解Spring Boot实战之Redis缓存登录验证码

    本章简单介绍redis的配置及使用方法,本文示例代码在前面代码的基础上进行修改添加,实现了使用redis进行缓存验证码,以及校验验证码的过程. 1.添加依赖库(添加redis库,以及第三方的验证码库) <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency&

  • Spring Boot缓存实战 EhCache示例

    Spring boot默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager来实现缓存.但是要切换到其他缓存实现也很简单 pom文件 在pom中引入相应的jar包 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web<

  • Spring Boot Web 静态文件缓存处理的方法

    采用Spring Boot + Freemarker开发Web项目时,由于一些静态文件比较大,如果是在PC上访问影响不大,当在手机上访问时,特别是用流量访问时速度会慢很多,而且很耗流量. 通过对请求进行抓包,可以发现每次进入一个页面都需要加载静态文件,如果不差钱的公司可以将静态文件放在CDN上来加快访问速度,或者用Nginx来做静态文件的缓存. 今天给大家介绍一种其他的缓存优化方式,通过Spring的缓存机制来缓存静态文件,在Spring Boot中配置静态文件缓存只需要在配置文件中加入下面的配

随机推荐