Scala Akka参与者抛出空指针错误,原因未知

Scala Akka参与者抛出空指针错误,原因未知,scala,null,akka,actor,Scala,Null,Akka,Actor,我有一个rest控制器,它调用一个服务,然后调用一个参与者从模拟数据库中获取一个查询。消息到达了参与者,但是应用程序在参与者能够响应之前崩溃,并且参与者出现了空指针异常。我使用akka http作为控制器和路由指令来编写响应。这些是我的依赖项: "com.typesafe.akka" %% "akka-http" % "10.1.8", "com.typesafe.akka" %% "akka-actor" % "2.5.22", "com.typesafe.akka" %% "akka-

我有一个rest控制器,它调用一个服务,然后调用一个参与者从模拟数据库中获取一个查询。消息到达了参与者,但是应用程序在参与者能够响应之前崩溃,并且参与者出现了空指针异常。我使用akka http作为控制器和路由指令来编写响应。这些是我的依赖项:

"com.typesafe.akka" %% "akka-http"   % "10.1.8",
"com.typesafe.akka" %% "akka-actor"  % "2.5.22",
"com.typesafe.akka" %% "akka-stream" % "2.5.22",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8"




class CacheActor extends Actor {

  val tweetRepositoryInMemory: TweetRepositoryInMemory = new TweetRepositoryInMemory()
  val log = Logging(context.system, this)

  var tweetMap: scala.collection.mutable.Map[String, List[String]] =
    scala.collection.mutable.Map[String, List[String]]()

  // consult the in-memory map, if the username is not found, call the repository, update  the map, and return the tweets
  def queryRepo(username: String): Future[Option[List[String]]] = {
    if (tweetMap isDefinedAt username) {

      return Future(tweetMap.get(username))
    } else {
      var listOfTweetTexts: List[String] = List[String]()

      val queryLimit = 10

      val resultTweets: Future[Seq[Tweet]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)

      onComplete(resultTweets) {
        case Success(tweets) =>
          for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

          tweetMap(username) = listOfTweetTexts

          return Future(Option(listOfTweetTexts))

        case Failure(t) =>
          log.error("An error has occurred: " + t.getMessage)
          return null
      }
    }
    return null
  }

  def receive = {

    case message: TweetQuery => // .take(message.limit)

      val queryResult: Future[Option[List[String]]] = queryRepo(message.userName)

      queryResult onComplete {
        case Success(result) => sender() ! result

        case Failure(t) => log.error("An error has occurred: " + t.getMessage)
      }
  }
}
“com.typesafe.akka”%%“akka http”%%“10.1.8”,
“com.typesafe.akka”%%“akka actor”%%“2.5.22”,
“com.typesafe.akka”%%“akka流”%%“2.5.22”,
“com.typesafe.akka”%%“akka http spray json”%%“10.1.8”
类CacheActor扩展了Actor{
val tweetRepositoryInMemory:tweetRepositoryInMemory=new tweetRepositoryInMemory()
val log=日志记录(context.system,this)
var tweetMap:scala.collection.mutable.Map[String,List[String]]=
scala.collection.mutable.Map[String,List[String]]()
//查阅内存映射,如果找不到用户名,请调用存储库,更新映射,然后返回推文
def queryRepo(用户名:字符串):未来[选项[列表[字符串]]={
if(tweetMap isDefinedAt用户名){
返回未来(tweetMap.get(用户名))
}否则{
var listOfTweetTexts:List[String]=List[String]()
val queryLimit=10
val resultTweets:Future[Seq[Tweet]]=tweetRepositoryInMemory.searchByUserName(用户名,queryLimit)
未完成(结果weets){
案例成功(推文)=>
为了(推特)
log.error(“发生错误:”+t.getMessage)
返回空
}
}
返回空
}
def接收={
案例消息:TweetQuery=>/.take(message.limit)
val queryResult:Future[Option[List[String]]]=queryRepo(message.userName)
查询结果未完成{
案例成功(结果)=>sender()!结果
案例失败(t)=>log.error(“发生错误:”+t.getMessage)
}
}
}

堆栈跟踪可能会有所帮助,但我怀疑您的
接收中的这一行会导致NPE:

queryResult onComplete {
如果
tweetMap
未在
username
中定义,则您的
queryRepo
函数将返回
null

更新

原因如下:

queryRepo
函数只在一种情况下返回一个
future[Seq[String]]

if (tweetMap isDefinedAt username) {
  return Future(tweetMap.get(username))
}
在else块中,创建一个
Future[Seq[String]]

val resultTweets: Future[Seq[String]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)
但是您永远不会返回它。相反您向Future传递一个回调,该回调在Future完成时异步执行,因此
onComplete
。(我注意到,您没有直接调用
Future
上的
onComplete
,而是调用一个函数
onComplete
,该函数将Future和部分函数作为参数,我假设该函数注册常规的
onComplete
回调。)

因此,if语句的else块的结果是
Unit
not
Future[Seq[String]]

val resultTweets: Future[Seq[String]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)
代码

 for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

 tweetMap(username) = listOfTweetTexts

 return Future(Option(listOfTweetTexts))
结束更新

如果更改以下行:

val resultTweets: Future[Seq[Tweet]] = tweetRepositoryInMemory.searchByUserName(username, queryLimit)

  onComplete(resultTweets) {
    case Success(tweets) =>
      for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }

      tweetMap(username) = listOfTweetTexts

      return Future(Option(listOfTweetTexts))

    case Failure(t) =>
      log.error("An error has occurred: " + t.getMessage)
      return null
  }
val resultTweets:Future[Seq[Tweet]]=tweetRepositoryInMemory.searchByUserName(用户名,queryLimit)
未完成(结果weets){
案例成功(推文)=>
为了(推特)
log.error(“发生错误:”+t.getMessage)
返回空
}
致:

tweetRepositoryInMemory.searchByUserName(用户名,queryLimit.map{tweets=>
//注意:这在“Future”中异步发生。最好不要关闭局部变量

val listOfTweetTexts=for(tweet感谢您的反馈Sacha,我当然可以。您的第一点是什么意思?我正在从receive中调用queryRepo方法。query repo返回一个未来,但receive最终返回该未来的结果。我有什么遗漏吗?另外,如果您能详细说明“还有许多其他观点"这将非常有用。@Boris我更新了我的答案,我希望现在答案更清楚。谢谢Sacha,这是非常有用的信息。我只是对你的笔记有一些疑问:“最好不要关闭局部变量”是什么意思?你的意思是最好不要在回调中变异局部变量?还有,yiel之间的区别是什么d、 处理期货时映射和onComplete(future)?1.关闭意味着从创建变量的上下文中捕获变量的lambda aka.closure。请阅读有关scala中lambda和closure的文档。2.更改局部变量(变量范围非常有限)在您的特定用例中不是很危险,因为您只是在异步的未来中对其进行变异,并将其作为未来的结果使用。但是,您必须意识到,未来在不同的线程中运行,并且来自两个不同线程的并发写操作是危险的,不是吗?
tweetRepositoryInMemory.searchByUserName(username, queryLimit).map { tweets =>
  // NOTE: This happens asynchronously in the `Future`. IT is better not to close over local variables  
  val listOfTweetTexts = for (tweet <- tweets) yield { tweet.text }
  // again, access to an actor member from within a `Future` is not wise or rather a bug in the making. 
  // But I will not refactor that much here. The way to do this would be to send a message to self and process the mutable member within `receive`
  tweetMap(username) = listOfTweetTexts
  Option(listOfTweetTexts)
}