The Scala reflection is really complicated. It contains type symbol and mirror. Could you tell me the relationship between them?
When working with the Scala Reflection API you encounter a lot more types that what you're used to if you've used the Java Reflection API. Given an example scenario where you start with a String
containing a fully qualified class name of a class, these are the types you are likely to encounter:
Universe: Scala supports both runtime and compile time reflection. You choose what kind of reflection you're doing by importing from the corresponding universe. For runtime reflection, this corresponds to the scala.reflect.runtime
package, for compile time reflection it corresponds to the scala.reflect.macros
package. This answer focus on the former.
Like Java you typically start any reflection by choosing which ClassLoader's classes you want to reflect on. Scala provides a shortcut for using the ClassLoader
of the current class: scala.reflect.runtime.currentMirror
, this gives you a Mirror
(more on mirrors later). Many JVM applications use just a single class loader, so this is a common entry point for the Scala Reflection API. Since you're importing from runtime
you're now in that Universe.
Symbols: Symbols contain static metadata about whatever you want to reflect. This includes anything you can think of: is this thing a case class, is it a field, is it a class, what are the type parameters, is it abstract, etc. You may not query anything that might depend on the current lexical scope, for example what members a class has. You may also not interact with the thing you reflect on in any way (like accessing fields or calling methods). You can just query metadata.
Lexical scope is everything you can "see" at the place you're doing reflection, excluding implicit scope (see this SO for a treatment of different scopes). How can members of a class vary with lexical scope? Imagine an abstract class with a single def foo: String
. The name foo
might be bound to a def
in one context (giving you a MethodSymbol
should you query for it) or it could be bound to a val
in another context (giving you a TermSymbol
). When working with Symbols it's common to explicitly have to state what kind of symbol you expect, you do this through the methods .asTerm
, .asMethod
, .asClass
etc.
Continuing the String
example we started with. You use the Mirror
to derive a ClassSymbol
describing the class: currentMirror.staticClass(myString)
.
Types: Types lets you query information about the type the symbol refers to in the current lexical context. You typically use Type
s for two things: querying what vars, vals and defs there are, and querying type relationships (e.g. is this type a subclass of that type). There are two ways of getting hold of a Type
. Either through a TypeSymbol
(ClassSymbol
is a TypeSymbol
) or through a TypeTag
.
Continuing the example you would invoke the .toType
method on the symbol you got to get the Type
.
Scopes: When you ask a Type
for .members
or .decl
—this is what gives you terms (vars and vals) and methods—you get a list of Symbol
s of the members in the current lexical scope. This list is kept in a type MemberScope
, it's just a glorified List[Symbol]
.
In our example with the abstract class above, this list would contain a TermSymbol
or a MethodSymbol
for the name foo
depending on the current scope.
TermName
and TypeName
. It's just a wrapper of a String
. You can use the type to determine what is named by any Name
.Symbol
and then use that symbol to derive symbols for the methods, constructors or fields you want to interact with. When you have the symbols you need, you use currentMirror
to create mirrors for those symbols. Mirrors lets you call constructors (ClassMirror
), access fields (FieldMirror
) or invoke methods (MethodMirror
). You may not use mirrors to query metadata about the thing being reflected.So putting an example together reflecting the description above, this is how you would search for fields, invoke constructors and read a val
, given a String
with the fully qualified class name:
// 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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With