文档

§Play 缓存 API

缓存数据是现代应用程序中常见的优化方法,因此 Play 提供了一个全局缓存。

注意:关于缓存的一个重要点是,它的行为就像缓存应该的那样:您刚刚存储的数据可能会丢失。

对于存储在缓存中的任何数据,都需要制定一个再生策略,以防数据丢失。这种理念是 Play 背后的基本原则之一,它与 Java EE 不同,在 Java EE 中,会话预计在其整个生命周期内保留值。

Play 提供了一个基于 Caffeine 的 CacheApi 实现,以及一个基于 Ehcache 2.x 的传统实现。对于进程内缓存,Caffeine 通常是最佳选择。如果您需要分布式缓存,则有针对 memcachedredis 的第三方插件。

§导入缓存 API

Play 为缓存 API 和 Caffeine 和 Ehcache 实现提供了单独的依赖项。

§Caffeine

要获取 Caffeine 实现,请将 caffeine 添加到您的依赖项列表中

libraryDependencies ++= Seq(
  caffeine
)

这也会自动设置运行时 DI 的绑定,以便组件可注入。

§EhCache

要获取 EhCache 实现,请将 ehcache 添加到您的依赖项列表中

libraryDependencies ++= Seq(
  ehcache
)

这也会自动设置运行时 DI 的绑定,以便组件可注入。

§自定义缓存实现

要仅添加 API,请将 cacheApi 添加到您的依赖项列表中。

libraryDependencies ++= Seq(
  cacheApi
)

如果您想为 Cached 助手和 AsyncCacheApi 等定义自己的绑定,而不必依赖任何特定的缓存实现,API 依赖项很有用。如果您正在编写自定义缓存模块,则应使用此方法。

§访问缓存 API

缓存 API 由 AsyncCacheApiSyncCacheApi 特性定义,具体取决于您想要异步还是同步实现,并且可以像任何其他依赖项一样注入到您的组件中。例如

import javax.inject.Inject

import play.api.cache._
import play.api.mvc._

class Application @Inject() (cache: AsyncCacheApi, cc: ControllerComponents) extends AbstractController(cc) {}

注意: API 故意保持最少,以允许插入多个实现。如果您需要更具体的 API,请使用缓存插件提供的 API。

使用此简单 API,您可以在缓存中存储数据

val result: Future[Done] = cache.set("item.key", connectedUser)

然后稍后检索它

val futureMaybeUser: Future[Option[User]] = cache.get[User]("item.key")

还有一个方便的助手,用于从缓存中检索或在缓存中设置值(如果它丢失了)

val futureUser: Future[User] = cache.getOrElseUpdate[User]("item.key") {
  User.findById(connectedUser)
}

注意getOrElseUpdate 在 EhCache 中不是原子操作,而是实现为 get,然后计算值,然后是 set。这意味着如果多个线程同时调用 getOrElse,则可能多次计算该值。

您可以通过传递持续时间来指定过期持续时间,默认情况下持续时间是无限的

import scala.concurrent.duration._

val result: Future[Done] = cache.set("item.key", connectedUser, 5.minutes)

要从缓存中删除项目,请使用 remove 方法

val removeResult: Future[Done] = cache.remove("item.key")

要从缓存中删除所有项目,请使用 removeAll 方法

val removeAllResult: Future[Done] = cache.removeAll()

removeAll() 仅在 AsyncCacheApi 上可用,因为删除缓存中的所有元素很少是您想同步执行的操作。预期是仅在特殊情况下(而不是应用程序的正常操作的一部分)作为管理员操作才需要删除缓存中的所有项目。

请注意,SyncCacheApi 具有相同的 API,只是它直接返回值,而不是使用期货。

§访问不同的缓存

可以通过名称定义和使用具有不同配置的不同缓存。要访问不同的缓存,在注入它们时,请在依赖项上使用 NamedCache 限定符,例如

import javax.inject.Inject

import play.api.cache._
import play.api.mvc._

class Application @Inject() (
    @NamedCache("session-cache") sessionCache: AsyncCacheApi,
    cc: ControllerComponents
) extends AbstractController(cc) {}

如果要访问多个不同的缓存,则需要告诉 Play 在 application.conf 中绑定它们,如下所示

play.cache.bindCaches = ["db-cache", "user-cache", "session-cache"]

定义和配置命名缓存取决于您使用的缓存实现,下面给出了使用 Caffeine 配置命名缓存的示例。

§使用 Caffeine 配置命名缓存

如果要传递一个默认的自定义配置,该配置将用作所有缓存的回退,您可以通过指定以下内容来实现

    play.cache.caffeine.defaults = {
        initial-capacity = 200
        ...
    }

您还可以通过以下方式为特定缓存传递自定义配置数据

    play.cache.caffeine.user-cache = {
        initial-capacity = 200
        ...
    }

§使用 EhCache 配置命名缓存

使用 EhCache 实现时,默认缓存称为 play,可以通过创建名为 ehcache.xml 的文件来配置。可以使用不同的配置甚至实现来配置其他缓存。

默认情况下,Play 会尝试为您创建名称来自 play.cache.bindCaches 的缓存。如果您想在 ehcache.xml 中自己定义它们,您可以设置

play.cache.createBoundCaches = false

§设置执行上下文

默认情况下,Caffeine 和 EhCache 将元素存储在内存中。因此,对缓存的读写应该非常快,因为几乎没有阻塞 I/O。
但是,根据缓存的配置方式(例如,使用 EhCache 的 DiskStore),可能会出现阻塞 I/O,这可能会变得过于昂贵,因为即使异步实现也会在默认执行上下文中阻塞线程。

对于这种情况,您可以配置一个不同的 Pekko 调度器 并通过 play.cache.dispatcher 设置它,以便缓存插件使用它

play.cache.dispatcher = "contexts.blockingCacheDispatcher"

contexts {
  blockingCacheDispatcher {
    fork-join-executor {
      parallelism-factor = 3.0
    }
  }
}

§Caffeine

使用 Caffeine 时,这将设置 Caffeine 的 内部执行器。实际上,设置 play.cache.dispatcher 会设置 play.cache.caffeine.defaults.executor。就像 上面描述的那样,因此您可以为不同的缓存设置不同的执行器

    play.cache.caffeine.user-cache = {
        executor = "contexts.anotherBlockingCacheDispatcher"
        ...
    }

§EhCache

对于 EhCache,Play 会在给定调度程序的线程上以 Future 的形式运行任何 EhCache 操作。

§缓存 HTTP 响应

您可以使用标准的 Action 组合轻松创建智能缓存操作。

注意:Play HTTP Result 实例可以安全地缓存并在以后重用。

Cached 类可以帮助您构建缓存操作。

import javax.inject.Inject

import play.api.cache.Cached

class Application @Inject() (cached: Cached, cc: ControllerComponents) extends AbstractController(cc) {}

您可以使用固定键(如 "homePage")缓存操作的结果。

def index = cached("homePage") {
  Action {
    Ok("Hello world")
  }
}

如果结果不同,您可以使用不同的键缓存每个结果。在此示例中,每个用户都有不同的缓存结果。

def userProfile = WithAuthentication(_.session.get("username")) { userId =>
  cached(req => "profile." + userId) {
    Action.async {
      User.find(userId).map { user => Ok(views.html.profile(user)) }
    }
  }
}

§控制缓存

您可以轻松控制要缓存的内容或要从缓存中排除的内容。

您可能只想缓存 200 Ok 结果。

def get(index: Int) = cached.status(_ => "/resource/" + index, 200) {
  Action {
    if (index > 0) {
      Ok(Json.obj("id" -> index))
    } else {
      NotFound
    }
  }
}

或仅将 404 Not Found 缓存几分钟

def get(index: Int) = {
  val caching = cached
    .status(_ => "/resource/" + index, 200)
    .includeStatus(404, 600)

  caching {
    Action {
      if (index % 2 == 1) {
        Ok(Json.obj("id" -> index))
      } else {
        NotFound
      }
    }
  }
}

§自定义实现

可以提供缓存 API 的自定义实现。确保您具有 cacheApi 依赖项。

然后,您可以实现 AsyncCacheApi 并将其绑定到 DI 容器中。您还可以将 SyncCacheApi 绑定到 DefaultSyncCacheApi,它只是包装异步实现。

请注意,removeAll 方法可能不受您的缓存实现支持,因为该方法要么不可行,要么效率低下。如果是这种情况,您可以在 removeAll 方法中抛出 UnsupportedOperationException

要除了默认实现之外还提供缓存 API 的实现,您可以创建自定义限定符,或重用 NamedCache 限定符来绑定实现。

§将 Caffeine 与您的自定义实现一起使用

要使用 Caffeine 的默认实现,您需要 caffeine 依赖项,并且必须在 application.conf 中禁用 Caffeine 模块自动绑定它。

play.modules.disabled += "play.api.cache.caffeine.CaffeineCacheModule"

§将 EhCache 与您的自定义实现一起使用

要使用 EhCache 的默认实现,您需要 ehcache 依赖项,并且必须在 application.conf 中禁用 EhCache 模块自动绑定它。

play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"

下一步:使用 Play WS 调用 REST API


发现本文档中的错误? 此页面的源代码可以在 这里 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 请访问 我们的社区论坛 与社区进行交流。