问题
I want to dynamically generate some kafka stream code when the program is running, but I get an exception when I compile the following code for kafka stream with scala toolbox eval:
val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val code =
"""
|import org.apache.kafka.streams.scala.kstream.KStream
|import org.apache.kafka.streams.scala.StreamsBuilder
|
|class ClassA {
| def createStream(): KStream[String, String] = {
| import org.apache.kafka.streams.scala.ImplicitConversions._
| import org.apache.kafka.streams.scala.Serdes._
| new StreamsBuilder().stream("TestTopic")
| }
|}
|scala.reflect.classTag[ClassA].runtimeClass
""".stripMargin
toolbox.eval(toolbox.parse(code))
errors:
reflective compilation has failed:
illegal cyclic reference involving class InterfaceStability
scala.tools.reflect.ToolBoxError: reflective compilation has failed:
illegal cyclic reference involving class InterfaceStability
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:331)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.wrapInPackageAndCompile(ToolBoxFactory.scala:213)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:267)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.$anonfun$compile$13(ToolBoxFactory.scala:444)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:370)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:437)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:459)
It seems that the kafka InterfaceStability class annotated itself:
package org.apache.kafka.common.annotation;
@InterfaceStability.Evolving
public class InterfaceStability {
...
}
Scala version:"2.12.8"
kafkaVersion: "2.1.0"
Sbt version : "1.2.7"
回答1:
After switching to the api below the scala.tools.nsc package, I can compile successfully.
refer to https://eknet.org/main/dev/runtimecompilescala.html [broken link]
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}
import scala.collection.mutable
import java.security.MessageDigest
import java.math.BigInteger
class Compiler(targetDir: Option[File]) {
val target = targetDir match {
case Some(dir) => AbstractFile.getDirectory(dir)
case None => new VirtualDirectory("(memory)", None)
}
val classCache = mutable.Map[String, Class[_]]()
private val settings = new Settings()
settings.deprecation.value = true // enable detailed deprecation warnings
settings.unchecked.value = true // enable detailed unchecked warnings
settings.outputDirs.setSingleOutput(target)
settings.usejavacp.value = true
private val global = new Global(settings)
private lazy val run = new global.Run
val classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader)
/**Compiles the code as a class into the class loader of this compiler.
*
* @param code
* @return
*/
def compile(code: String) = {
val className = classNameForCode(code)
findClass(className).getOrElse {
val sourceFiles = List(new BatchSourceFile("(inline)", wrapCodeInClass(className, code)))
run.compileSources(sourceFiles)
findClass(className).get
}
}
/** Compiles the source string into the class loader and
* evaluates it.
*
* @param code
* @tparam T
* @return
*/
def eval[T](code: String): T = {
val cls = compile(code)
cls.getConstructor().newInstance().asInstanceOf[() => Any].apply().asInstanceOf[T]
}
def findClass(className: String): Option[Class[_]] = {
synchronized {
classCache.get(className).orElse {
try {
val cls = classLoader.loadClass(className)
classCache(className) = cls
Some(cls)
} catch {
case e: ClassNotFoundException => None
}
}
}
}
protected def classNameForCode(code: String): String = {
val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes)
"sha"+new BigInteger(1, digest).toString(16)
}
/*
* Wrap source code in a new class with an apply method.
*/
private def wrapCodeInClass(className: String, code: String) = {
"class " + className + " extends (() => Any) {\n" +
" def apply() = {\n" +
code + "\n" +
" }\n" +
"}\n"
}
}
来源:https://stackoverflow.com/questions/53976254/how-to-eval-code-that-uses-interfacestability-annotation-that-fails-with-illeg