Scala反射:使用protobuf编译akka演员

rnmwe5a2  于 2022-09-18  发布在  Java
关注(0)|答案(1)|浏览(147)

我试图用Scala反射工具箱编译一个带有DynamicMessage的Actor
演员代码如下

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox
val toolbox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val actorCode =
  """
    |import akka.actor._
    |import com.google.protobuf._
    |class SimpleActor extends Actor {
    |override def receive: Receive = {
    | case dynamicMessage: DynamicMessage => println("Dynamic message received!")
    | case _  => println("Whatever!")  // the default, catch-all
    |  }
    |}
    |object SimpleActor {
    |def props() : Props = Props(new SimpleActor())
    |}
    |
    |
    |return SimpleActor.props()
    |""".stripMargin
val tree = toolbox.parse(actorCode)
toolbox.compile(tree)().asInstanceOf[Props]

我得到了错误

reflective compilation has failed:

illegal cyclic reference involving type T
scala.tools.reflect.ToolBoxError: reflective compilation has failed:

illegal cyclic reference involving type T

如果我在工具箱之外运行代码,它会编译并正常工作。错误是从行中给出的

case dynamicMessage: DynamicMessage => println("Dynamic message received!")

有谁知道这个错误的性质以及如何修复它?

ljsrvy3e

ljsrvy3e1#

在Scala中,即使没有反射编译,也有结合Scala java互操作和F-有界多态性的bugs
scalac reports error on valid Java class: illegal cyclic reference involving type T
以及其他。
com.google.protobuf.DynamicMessage的父母探索F-bounded polymorphism

DynamicMessage 
  <: AbstractMessage
       <: AbstractMessageLite[_,_] (such inheritance is allowed in Java but not in Scala) 
            [M <: AbstractMessageLite[M, B],
             B <: AbstractMessageLite.Builder[M, B]]
                                        [M <: AbstractMessageLite[M, B],
                                         B <: AbstractMessageLite.Builder[M, B]]
                                                                    <: MessageLite.Builder
                                                                                     <: MessageLiteOrBuilder
                                                                                     <: Cloneable
            <: MessageLite
                 <: MessageLiteOrBuilder
       <: Message
            <: MessageLite...
            <: MessageOrBuilder
                 <: MessageLiteOrBuilder

但是如果没有反射编译,代码将编译。因此,这是反射编译、scala java互操作和F-bounded多态性结合的缺陷。
解决方法是使用真实编译器而不是工具箱:

import akka.actor.{ActorSystem, Props}
// libraryDependencies += "com.github.os72" % "protobuf-dynamic" % "1.0.1"
import com.github.os72.protobuf.dynamic.{DynamicSchema, MessageDefinition}
import com.google.protobuf.DynamicMessage
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.nsc.{Global, Settings}

val actorCode = """
  |import akka.actor._
  |import com.google.protobuf._
  |
  |class SimpleActor extends Actor {
  |  override def receive: Receive = {
  |    case dynamicMessage: DynamicMessage => println("Dynamic message received!")
  |    case _  => println("Whatever!")  // the default, catch-all
  |  }
  |}
  |
  |object SimpleActor {
  |    def props() : Props = Props(new SimpleActor())
  |}
  |""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
compileCode(actorCode, List(), directory)
val props = runObjectMethod("SimpleActor", runtimeMirror, "props")
  .asInstanceOf[Props]

val actorSystem = ActorSystem("actorSystem")
val actor = actorSystem.actorOf(props, "helloActor")

val msg = makeDynamicMessage()

actor ! "hello" // Whatever!
actor ! msg // Dynamic message received!

actorSystem.terminate()

//see (*)
def makeDynamicMessage(): DynamicMessage = {
  val schemaBuilder = DynamicSchema.newBuilder
  schemaBuilder.setName("PersonSchemaDynamic.proto")

  val msgDef = MessageDefinition.newBuilder("Person")
    .addField("required", "int32", "id", 1)
    .build

  schemaBuilder.addMessageDefinition(msgDef)
  val schema = schemaBuilder.build

  val msgBuilder = schema.newMessageBuilder("Person")
  val msgDesc = msgBuilder.getDescriptorForType
  msgBuilder
    .setField(msgDesc.findFieldByName("id"), 1)
    .build
}

def compileCode(
  code: String, 
  classpathDirectories: List[AbstractFile], 
  outputDirectory: AbstractFile
): Unit = {
  val settings = new Settings
  classpathDirectories.foreach(dir => settings.classpath.prepend(dir.toString))
  settings.outputDirs.setSingleOutput(outputDirectory)
  settings.usejavacp.value = true
  val global = new Global(settings)
  (new global.Run).compileSources(List(new BatchSourceFile("(inline)", code)))
}

def runObjectMethod(
  objectName: String,
  runtimeMirror: Mirror, 
  methodName: String, 
  arguments: Any*
): Any = {
  val objectSymbol         = runtimeMirror.staticModule(objectName)
  val objectModuleMirror   = runtimeMirror.reflectModule(objectSymbol)
  val objectInstance       = objectModuleMirror.instance
  val objectType           = objectSymbol.typeSignature
  val methodSymbol         = objectType.decl(TermName(methodName)).asMethod
  val objectInstanceMirror = runtimeMirror.reflect(objectInstance)
  val methodMirror         = objectInstanceMirror.reflectMethod(methodSymbol)
  methodMirror(arguments: _*)
}

def createRuntimeMirror(directory: AbstractFile, parentMirror: Mirror): Mirror = {
  val classLoader = new AbstractFileClassLoader(directory, parentMirror.classLoader)
  universe.runtimeMirror(classLoader)
}

Tensorflow in Scala reflection(这里有一个类似的情况,在反射编译、Scala Java互操作和路径依赖类型的组合中出现了一个bug)
Dynamic compilation of multiple Scala classes at runtime
How to eval code that uses InterfaceStability annotation (that fails with "illegal cyclic reference involving class InterfaceStability")?(在反射编译期间也称为“非法循环引用”)
Scala Presentation Compiler - Minimal Example
(*)Protocol buffer objects generated at runtime

相关问题