1. Scala 的 反射
关于 Scala 反射的具体内容,可以参考官方文档:https://docs.scala-lang.org/overviews/reflection/overview.html
这篇文章写一点自己的理解:
- 由于 Scala 编译出来的内容是与 Java 相同的字节码文件,所以可以使用 Java 反射的相关方法来实现 Scala 代码的反射。
- Scala 自己写了一套基于 Scala 的反射,具体的实现在 scala.reflect 这个 package 下面。
- 这篇文章主要介绍,反射 Scala 中的 class 和 object 类的方法。
先看基础代码:
package com.personal object ProvisioningApp { val strInObj = "123" def sayHello(): Unit = println("say hello") def sayHello2(from: String, to: String): Unit = println(from + " say hello, " + to) } class ProvisioningApp { val strInClazz = "234" def sayGoodbye(): Unit = println("say goodbye") }
2. Java Style
2.1 使用 Java 的方式反射 Scala class
和反射 Java 的 class 步骤完全一致,所以不赘述,直接贴代码:
test("Should reflect Scala class in Java style") { val app = new ProvisioningApp val field = classOf[ProvisioningApp].getDeclaredField("strInClazz") field.setAccessible(true) field.set(app, "789") app.strInClazz shouldBe "789" val method = classOf[ProvisioningApp].getDeclaredMethod("sayGoodbye") method.setAccessible(true) method.invoke(app) }
2.2 使用 Java 的方式反射 Scala object
Scala 中的 object,称之为 “伴生类”,想要反射获取它的类或者方法,首先要知道它编译之后是个什么东西:
通过 jd-gui,我们知道了 Scala object 的具体实现:
- 编译之后的类名是 "类名+$" 形式
- 属性的名字,有时会和在代码中定义的不同(在这个例子里面没有显现,具体原因我还不知道,比如这个 "strInObj", 有时候这个类名会变成 “$com.$personal.$$strInObj” 这样)
- 可以发现,这是一个使用静态代码块模式的单例
- 因此,暂时没有找到使用 Java 方式反射 Scala object 的方法
3. Scala style
- Scala 反射的核心是 scala.reflect.runtime.universe
- 很多东西自己也没有特别搞懂
- 核心是通过 universe 对象,去依次获得 “Mirror” 这个东西
3.1 使用 Scala 方式反射 Scala class
步骤如下:
- 通过 universe 和 classLoader 找到 JavaMirror
- 通过 JavaMirror 和 实例化对象 获取 InstanceMirror
- 通过 universe 获取目标类的 TypeTag
- 通过 TypeTag 获取目标类的 TermSymbol or MethodSymbol
- 获取 FieldMirror or MethodMirror
- 反射执行
test("Should reflect Scala class in Scala type") { import scala.reflect.runtime.universe val app = new ProvisioningApp() // JavaMirror val classMirror = universe.runtimeMirror(getClass.getClassLoader) // InstanceMirror val instanceMirror = classMirror.reflect(app) // TypeTag val typeTag = universe.typeOf[ProvisioningApp] // TermSymbol val strInClazzSymbol = typeTag.decl(universe.TermName("strInClazz")).asTerm val fieldMirror = instanceMirror.reflectField(strInClazzSymbol) fieldMirror.set("789") app.strInClazz shouldBe "789" // MethodSymbol val sayGoodbyeSymbol = typeTag.decl(universe.TermName("sayGoodbye")).asMethod // MethodMirror and invoke action val result = instanceMirror.reflectMethod(sayGoodbyeSymbol)() // No input parameters here result shouldBe "Goodbye" }
3.2 使用 Scala 方式反射 Scala object
与 Scala class 不同,反射 Scala object 核心是通过 staticModule 获取 ModuleMirror:
test("Should reflect Scala object in Scala style") { import scala.reflect.runtime.universe // JavaMirror val classMirror = universe.runtimeMirror(getClass.getClassLoader) // The ModuleSymbol for object val staticMirror = classMirror.staticModule("com.personal.ProvisioningApp") // ModuleMirror can be used to create instances of the class val moduleMirror = classMirror.reflectModule(staticMirror) // ObjectMirror can be used to reflect the members of the object val objectMirror = classMirror.reflect(moduleMirror.instance) // TermSymbol represents val, var, def and object declarations val strInObjSymbol = moduleMirror.symbol.typeSignature.member(universe.TermName("strInObj")).asTerm // FieldMirror can be used to get and set the value of the field val fieldMirror = objectMirror.reflectField(strInObjSymbol) fieldMirror.set("789") ProvisioningApp.strInObj shouldBe "789" val sayHelloSymbol = moduleMirror.symbol.typeSignature.member(universe.TermName("sayHello")).asMethod val sayHelloSymbol2 = moduleMirror.symbol.typeSignature.member(universe.TermName("sayHello2")).asMethod // MethodMirror and invoke action objectMirror.reflectMethod(sayHelloSymbol)() objectMirror.reflectMethod(sayHelloSymbol2)("Sai", "Gerrard") // Pass the input parameters one by one }