play framework与play-redis集成后得到NPE

Got NPE after integrating play framework with play-redis

将play-redis(https://github.com/KarelCemus/play-redis)与play framework集成后,收到请求报错:

[20211204 23:20:48.350][HttpErrorHandler.scala:272:onServerError][E] Error while handling error
java.lang.NullPointerException: null
    at play.api.http.HttpErrorHandlerExceptions$.convertToPlayException(HttpErrorHandler.scala:377)
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:367)
    at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:264)
    at play.core.server.Server$$anonfun$handleErrors.applyOrElse(Server.scala:109)
    at play.core.server.Server$$anonfun$handleErrors.applyOrElse(Server.scala:105)
    at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:35)
    at play.core.server.Server$.getHandlerFor(Server.scala:129)
    at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:317)
    at play.core.server.AkkaHttpServer.$anonfun$createServerBinding(AkkaHttpServer.scala:224)
    at akka.stream.impl.fusing.MapAsync$$anon.onPush(Ops.scala:1297)
    at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:541)
    at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:495)
    at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:390)
    at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:625)
    at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:502)
    at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:600)
    at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:775)
    at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive.applyOrElse(ActorGraphInterpreter.scala:790)
    at akka.actor.Actor.aroundReceive(Actor.scala:537)
    at akka.actor.Actor.aroundReceive$(Actor.scala:535)
    at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:691)
    at akka.actor.ActorCell.receiveMessage(ActorCell.scala:579)
    at akka.actor.ActorCell.invoke(ActorCell.scala:547)
    at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
    at akka.dispatch.Mailbox.run(Mailbox.scala:231)
    at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

我确定原因一定是 play-redis 因为没有它应用程序运行顺利。特别是,我使用配置提供程序的自定义实现,因为需要通过调用名称服务的 rest API 来获取 ip 和端口。

@Singleton
class CustomRedisInstance @Inject() (
    config: Configuration,
    polarisExtensionService: PolarisExtensionService,
    @NamedCache("redisConnection") redisConnectionCache: AsyncCacheApi)(implicit
    asyncExecutionContext: AsyncExecutionContext)
    extends RedisStandalone
    with RedisDelegatingSettings {

  val pathPrefix = "play.cache.redis"

  def name = "play"

  private def defaultSettings =
    RedisSettings.load(
      // this should always be "play.cache.redis"
      // as it is the root of the configuration with all defaults
      config.underlying,
      "play.cache.redis")

  def settings: RedisSettings = {
    RedisSettings
      .withFallback(defaultSettings)
      .load(
        // this is the path to the actual configuration of the instance
        //
        // in case of named caches, this could be, e.g., "play.cache.redis.instances.my-cache"
        //
        // in that case, the name of the cache is "my-cache" and has to be considered in
        // the bindings in the CustomCacheModule (instead of "play", which is used now)
        config.underlying,
        "play.cache.redis")
  }


  def host: String = {
    val connectionInfoFuture = getConnectionInfoFromPolaris
    Try(Await.result(connectionInfoFuture, 10.seconds)) match {
      case Success(extractedVal) => extractedVal.host
      case Failure(_)            => config.get[String](s"$pathPrefix.host")
      case _                     => config.get[String](s"$pathPrefix.host")
    }
  }

  def port: Int = {
    val connectionInfoFuture = getConnectionInfoFromPolaris
    Try(Await.result(connectionInfoFuture, 10.seconds)) match {
      case Success(extractedVal) => extractedVal.port
      case Failure(_)            => config.get[Int](s"$pathPrefix.port")
      case _                     => config.get[Int](s"$pathPrefix.port")
    }
  }

  def database: Option[Int] = Some(config.get[Int](s"$pathPrefix.database"))

  def password: Option[String] = Some(config.get[String](s"$pathPrefix.password"))

}

但是play-redis本身没有错误日志。经过所有这些阅读手册和示例的艰苦工作,结果证明我应该转向 JedisLettuce?现在没希望了。

原因是我想使用RedisCaffeine会造成冲突,正如文档所说,需要将default-cache重命名为redis application.conf:

play.modules.enabled += play.api.cache.redis.RedisCacheModule
# provide additional configuration in the custom module
play.modules.enabled += services.CustomCacheModule

play.cache.redis {
   # do not bind default unqualified APIs
   bind-default: false

   # name of the instance in simple configuration,
   # i.e., not located under `instances` key
   # but directly under 'play.cache.redis'
   default-cache: "redis"

   source = custom
   host = 127.0.0.1
    # redis server: port
   port = 6380
    # redis server: database number (optional)
   database = 0
    # authentication password (optional)
   password = "#########"
   refresh-minute = 10
}

所以在CustomCacheModule中,NamedCacheImpl的输入参数需要从play改为redis

class CustomCacheModule extends AbstractModule {
  override def configure(): Unit = {
    // NamedCacheImpl's input used to be "play"
    bind(classOf[RedisInstance]).annotatedWith(new NamedCacheImpl("redis")).to(classOf[CustomRedisInstance])
    ()
  }
}