§Play 缓存 API
缓存数据是现代应用程序中常见的优化方法,因此 Play 提供了一个全局缓存。
注意:关于缓存的一个重要点是,它的行为就像缓存应该的那样:您刚刚存储的数据可能会丢失。
对于存储在缓存中的任何数据,都需要制定一个再生策略,以防数据丢失。这种理念是 Play 背后的基本原则之一,它与 Java EE 不同,在 Java EE 中,会话预计在其整个生命周期内保留值。
Play 提供了一个基于 Caffeine 的 CacheApi 实现,以及一个基于 Ehcache 2.x 的传统实现。对于进程内缓存,Caffeine 通常是最佳选择。如果您需要分布式缓存,则有针对 memcached 和 redis 的第三方插件。
§导入缓存 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 由 AsyncCacheApi 和 SyncCacheApi 特性定义,具体取决于您想要异步还是同步实现,并且可以像任何其他依赖项一样注入到您的组件中。例如
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"
发现本文档中的错误? 此页面的源代码可以在 这里 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 请访问 我们的社区论坛 与社区进行交流。