Akka与Scala开发报错:无法找到ActorSystem隐式值
这个问题的核心是Akka的Http()客户端API需要隐式的ActorSystem(以及ActorMaterializer,取决于你使用的Akka版本)才能初始化WebSocket流,而你的SocketActor类里没有把这些隐式值放到正确的作用域中。
我给你两种常见的解决方案,其中第一种更符合Akka的设计实践:
方案1:从Actor上下文获取隐式的ActorSystem和Materializer
每个Actor都隶属于一个ActorSystem,你可以直接通过context.system获取当前Actor所在的系统,然后把它声明为隐式值,同时创建对应的ActorMaterializer。这样不需要从外部传递任何参数,代码更简洁也更符合Akka的设计理念。
修改后的SocketActor代码如下:
package com.lightbend.akka.sample import akka.actor.{Actor, ActorRef, ActorSystem, ActorMaterializer, Props} import akka.Done import akka.http.scaladsl.Http import akka.stream.scaladsl._ import akka.http.scaladsl.model.ws._ import scala.concurrent.Future object SocketActor { def props(coinApiIdentifier: String): Props = Props(new SocketActor(coinApiIdentifier)) case object Start case object Stop } class SocketActor(val ticker: String) extends Actor { import SocketActor._ // 从Actor上下文获取隐式的ActorSystem,并创建对应的Materializer implicit val system: ActorSystem = context.system implicit val materializer: ActorMaterializer = ActorMaterializer()(system) private val incoming: Sink[Message, Future[Done]] = Sink.foreach[Message] { case message: TextMessage.Strict => println(message.text) } private val outgoing = Source.single(TextMessage("hello world!")) // 现在Http()能找到所需的隐式ActorSystem了 private val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/")) private val graph = outgoing .viaMat(webSocketFlow)(Keep.right) .toMat(incoming)(Keep.both) override def receive: PartialFunction[Any, Unit] = { case Start => println("Start message received.") graph.run() } }
你的主方法代码不需要任何修改,直接运行即可。
方案2:从外部注入隐式参数
如果你更倾向于从外部控制ActorSystem和Materializer的实例,可以把它们作为隐式参数传递给SocketActor的构造函数,同时修改props方法来接收这些隐式参数:
首先修改SocketActor的定义:
object SocketActor { // 声明隐式参数,让外部传入的system和materializer能被构造函数使用 def props(coinApiIdentifier: String)(implicit system: ActorSystem, materializer: ActorMaterializer): Props = Props(new SocketActor(coinApiIdentifier)) case object Start case object Stop } class SocketActor(val ticker: String)(implicit system: ActorSystem, materializer: ActorMaterializer) extends Actor { import SocketActor._ private val incoming: Sink[Message, Future[Done]] = Sink.foreach[Message] { case message: TextMessage.Strict => println(message.text) } private val outgoing = Source.single(TextMessage("hello world!")) private val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("wss://api.com/v1/")) private val graph = outgoing .viaMat(webSocketFlow)(Keep.right) .toMat(incoming)(Keep.both) override def receive: PartialFunction[Any, Unit] = { case Start => println("Start message received.") graph.run() } }
然后修改主方法,把system和materializer声明为隐式值,这样props方法会自动获取它们:
object AkkaQuickstart extends App { // 声明为隐式,让SocketActor的props能自动获取 implicit val system: ActorSystem = ActorSystem("test") implicit val materializer: ActorMaterializer = ActorMaterializer() val socketActor: ActorRef = system.actorOf(SocketActor.props("hello"), "socket-actor") socketActor ! SocketActor.Start }
为什么之前的代码报错?
Http().webSocketClientFlow方法的签名要求作用域内存在隐式的ActorSystem和Materializer(在旧版Akka中,Materializer可以通过ActorSystem派生,但显式声明更稳妥)。你之前的代码里,SocketActor内部没有提供这些隐式值,所以编译器找不到它们,就抛出了那个错误。
内容的提问来源于stack exchange,提问作者Julian Wilson

