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