我有一个rest控制器,它调用服务,后者又调用actor从模拟数据库中获取查询。该消息将其发送给actor,但是应用程序在actor可以响应之前崩溃了,并且actor出现了空指针异常。我正在使用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)
}
}
}
答案 0 :(得分:0)
堆栈跟踪会有所帮助,但是我怀疑您receive
中的这一行会导致NPE:
queryResult onComplete {
如果未在queryRepo
上定义null
,则您的tweetMap
函数将返回username
。
更新
这就是原因:
queryRepo
函数仅在一种情况下返回Furture[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的回调,该回调在期货完成时异步执行,因此为onComplete
。 (我注意到您不是直接在onComplete
上调用Future
,而是将未来和部分函数作为参数的函数onComplete
,我假设该函数注册了常规onComplete
回调。)
因此if语句的else块的结果为Unit
,而不是 Future[Seq[String]]
代码
for (tweet <- tweets) { listOfTweetTexts ::= tweet.text; }
tweetMap(username) = listOfTweetTexts
return Future(Option(listOfTweetTexts))
最有可能在queryRepo
返回null
之后执行。
def queryRepo(username: String): Future[Option[List[String]]] = {
if (tweetMap isDefinedAt username) {
...
} else {
...
}
return null // <--- here
}
结束更新
如果您更改以下几行:
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
}
收件人:
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)
}
NullPointerException
应该不再出现。
但是,我的印象是,您通常可以在scala中对未来,演员和功能编程进行更多的培训。
例如
receive
内部而不是在异步Future
或Thread
中访问时才起作用null
。