Scala反射非常复杂。它包含类型符号和镜像。你能告诉我他们之间的关系吗?
使用Scala Reflection API时,您会遇到比使用Java Reflection API时更多的类型。给定一个示例场景,其中您从包含类的完全限定类名的String
开始,您可能会遇到以下类型:
-
Universe:Scala同时支持运行时和编译时反射。你可以通过从相应的宇宙中导入来选择你正在进行的反射类型。对于运行时反射,这对应于
scala.reflect.runtime
包,对于编译时反射,它对应于scala.reflect.macros
包。这个答案集中在前者。像Java一样,您通常通过选择要反射的ClassLoader类来启动任何反射。Scala提供了使用当前类的
ClassLoader
的快捷方式:scala.reflect.runtime.currentMirror
,这为您提供了Mirror
(稍后将详细介绍镜像)。许多JVM应用程序只使用一个类加载器,因此这是Scala Reflection API的一个常见入口点。既然你是从runtime
导入的,你现在就在宇宙中。 -
符号:符号包含关于您想要反映的内容的静态元数据。这包括你能想到的任何东西:这是一个case类吗,是一个字段吗,是不是一个类,类型参数是什么,是抽象的吗,等等。你可能不会查询任何可能依赖于当前词法范围的东西,例如一个类有什么成员。您也可能不会以任何方式与您所反思的事物进行交互(例如访问字段或调用方法)。您可以直接查询元数据。
词汇范围是在进行反射的地方可以"看到"的一切,不包括隐式范围(有关不同范围的处理方法,请参阅本SO)。一个类的成员如何随着词汇范围的变化而变化?想象一下,一个抽象类只有一个
def foo: String
。名称foo
可能在一个上下文中绑定到def
(如果您查询它,则会给您一个MethodSymbol
),也可能在另一个上下文中将其绑定到val
(给您一个子TermSymbol
)。当使用Symbols时,通常必须明确说明您期望的符号类型,您可以通过方法.asTerm
、.asMethod
、.asClass
等来实现。继续我们开始的
String
示例。您可以使用Mirror
来派生描述类的ClassSymbol
:currentMirror.staticClass(myString)
。 -
Types:Types允许您查询有关符号在当前词汇上下文中引用的类型的信息。您通常将
Type
用于两件事:查询有哪些vars、vals和def,以及查询类型关系(例如,该类型是该类型的子类吗)。有两种方法可以获得Type
。通过TypeSymbol
(ClassSymbol
是TypeSymbol
)或通过TypeTag
。继续这个例子,您将对得到的符号调用
.toType
方法来获得Type
。 -
Scopes:当你向
Type
询问.members
或.decl
时——这就是给你术语(vars和vals)和方法的原因——你会得到当前词汇范围中成员的Symbol
列表。这个列表保存在一个类型MemberScope
中,它只是一个荣耀的List[Symbol]
。在上面的抽象类示例中,根据当前作用域,此列表将包含名称为
foo
的TermSymbol
或MethodSymbol
。 -
名称:名称有两种风格:
TermName
和TypeName
。它只是一个String
的包装。您可以使用类型来确定任何Name
命名的内容 -
镜像:最后,镜像是你用来与"某物"交互的东西。通常从
Symbol
开始,然后使用该符号来派生要与之交互的方法、构造函数或字段的符号。当您拥有所需的符号时,可以使用currentMirror
为这些符号创建镜像。镜像允许您调用构造函数(ClassMirror
)、访问字段(FieldMirror
)或调用方法(MethodMirror
)。您可以不使用镜像来查询有关被反射事物的元数据
因此,将一个反映上述描述的示例放在一起,这就是搜索字段、调用构造函数和读取val
的方法,给定一个具有完全限定类名的String
:
// Do runtime reflection on classes loaded by current ClassLoader
val currentMirror: universe.Mirror = scala.reflect.runtime.currentMirror
// Use symbols to navigate to pick out the methods and fields we want to invoke
// Notice explicit symbol casting with the `.as*` methods.
val classSymbol: universe.ClassSymbol = currentMirror.staticClass("com.example.Foo")
val constructorSymbol: universe.MethodSymbol = classSymbol.primaryConstructor.asMethod
val fooSymbol: Option[universe.TermSymbol] = classSymbol.toType.members.find(_.name.toString == "foo").map(_.asTerm)
// Get mirrors for performing constructor and field invocations
val classMirror: universe.ClassMirror = currentMirror.reflectClass(classSymbol)
val fooInstance: Foo = classMirror.reflectConstructor(constructorSymbol).apply().asInstanceOf[Foo]
val instanceMirror: universe.InstanceMirror = currentMirror.reflect(fooInstance)
// Do the actual invocation
val fooValue: String = instanceMirror.reflectField(fooSymbol.get).get.asInstanceOf[String]
println(fooValue) // Prints the value of the val "foo" of the object "fooInstance"