有时候我们想定义一个字符串的方法,然后通过scala的动态创建class,然后反射调用方法,在很多情景下是在学有用的,比较动态自定义spark的
mapParations
,当然了,每个人的需求都不一样,但是底层原理是一样的。
画饼
运行
object CreateTest{
def main(args: Array[String]): Unit = {
val cim = ClassCreateUtils("def toUps(str:String):String = str.toUpperCase")
val value = cim.methods("toUps").invoke(cim.instance, "hello")
println(value) // method1
println(cim.invoke("World")) // method2
}
}
输出
HELLO
WORLD
工具类实现
依赖
compile group: 'org.scala-lang', name: 'scala-library', version: '2.12.8'
compile group: 'org.scala-lang', name: 'scala-reflect', version: '2.12.8'
compile group: 'org.scala-lang', name: 'scala-compiler', version: '2.12.8'
compile group: 'org.apache.spark', name: 'spark-sql_2.12', version: '2.4.0'
compile group: 'org.apache.spark', name: 'spark-core_2.12', version: '2.4.0'
compile group: 'org.apache.spark', name: 'spark-repl_2.12', version: '2.4.0'
源码
import java.lang.reflect.Method
import java.util
import java.util.UUID
import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox
case class ClassInfo(clazz: Class[_], instance: Any, defaultMethod: Method, methods: Map[String, Method]) {
def invoke[T](args: Object*): T = {
defaultMethod.invoke(instance, args: _*).asInstanceOf[T]
}
}
object ClassCreateUtils {
private val clazzs = new util.HashMap[String, ClassInfo]()
private val classLoader = scala.reflect.runtime.universe.getClass.getClassLoader
private val toolBox = universe.runtimeMirror(classLoader).mkToolBox()
def apply(func: String): ClassInfo = this.synchronized {
var clazz = clazzs.get(func)
if (clazz == null) {
val (className, classBody) = wrapClass(func)
val zz = compile(prepareScala(className, classBody))
val defaultMethod = zz.getDeclaredMethods.head
val methods = zz.getDeclaredMethods
clazz = ClassInfo(
zz,
zz.newInstance(),
defaultMethod,
methods = methods.map { m => (m.getName, m) }.toMap
)
clazzs.put(func, clazz)
}
clazz
}
def compile(src: String): Class[_] = {
val tree = toolBox.parse(src)
toolBox.compile(tree).apply().asInstanceOf[Class[_]]
}
def prepareScala(className: String, classBody: String): String = {
classBody + "\n" + s"scala.reflect.classTag[$className].runtimeClass"
}
def wrapClass(function: String): (String, String) = {
val className = s"dynamic_class_${UUID.randomUUID().toString.replaceAll("-", "")}"
val classBody =
s"""
|class $className extends Serializable{
| $function
|}
""".stripMargin
(className, classBody)
}
}