Scala Reflection Basics – Accessing an Object's Private Field Explained
Join the DZone community and get the full member experience.
Join For FreeToday’s blog post will be on the slightly weird side, because as Scala developers we don’t usually need to reach into reflection to solve problems at hand. It’s easy to fight most problems with the “there’s a monad for that!” tactic, or simply “adding yet another layer solves any software development problem!”.
Sadly today’s task is not easily solvable by just that. The problem we’re going to tackle using Scala’s reflection involves a scala object
and a private field we need to access.
trait MongoFlatSpec extends FlatSpec with BeforeAndAfterAll { var mongo: Mongo = _ // either Mongo or Fongo supplied instance here, starts up during beforeAll() abstract override protected def afterAll() { super.afterAll() // now it's tempting to use MongoDB.close() // NOPE! Won't allow parallel execution, take a look at it's impl above. getMongo.close() } } class ExampleTest extends MongoFlatSpec with ShouldMatchers { // tests... }
Problem statement: We need to support parallel integration tests (sbt running tests in parallel), using Mongo (or Fongo). The MongoDB
object though, contains globaly shared mutable state, in form of the dbs
Map, which is used to determine which MongoAddress
to hit for which entity.
We are able to setup the mongo identifiers up properly for the threads executing the tests, so they don’t interfere with each other, and we won’t focus on the setup part today. Instead let’s focus on what happens during teardown. A naive implementation is a simple (ScalaTest) afterAll
, like this:
trait MongoFlatSpec extends FlatSpec with BeforeAndAfterAll { var mongo: Mongo = _ // either Mongo or Fongo supplied instance here, starts up during beforeAll() abstract override protected def afterAll() { super.afterAll() // now it's tempting to use MongoDB.close() // NOPE! Won't allow parallel execution, take a look at it's impl above. getMongo.close() } } class ExampleTest extends MongoFlatSpec with ShouldMatchers { // tests... }
So sadly the naive implementation with using MongoDB.close()
is not enough for us. Why? Imagine Two threads, running two MongoFlatSpec
s, assume each has it’s own in memory Fongo instance even. There still is that shared MongoDB.dbs
field deep in lift-mongodb’s internals. If we’d use MongoDB.close()
we’ll clean all MongoIdentifier
s, so we might break tests which are still in flight… We could try something among the lines of “wait for the right moment to clean up”, but … why wait!? There’s a ConcurrentHashMap in there, and we just want to remove “the one MongoIdentifier I have been using for this MongoFlatSpec”.
*Reflection to the rescue!* So we’ll have to use reflection, in order to execute such statement: MongoDB.dbs.remove(myMongoIdentifier)
. The first problem is that we’re dealing with a Scala object
, also known as “Module”. Why is it also known as *Module*? Let’s take a look into the ByteCode!
scala> :javap MongoDB public class MongoDB$ extends java.lang.Object { public static final MongoDB$ MODULE$; // actual instance of our `object` public static {}; public MongoDB$(); // ... }
dbs
field, we will have to go through the static final MongoDB$ MODULE$
field of the MongoDB$
class. While it’s certainly doable using plain Java reflection, let’s see how Scala’s (still experimental) reflection can make this a bit nicer:def removeFongoIdentifierFromMongoDBObject(ident: MongoIdentifier) { val ru = scala.reflect.runtime.universe val mirror = ru.runtimeMirror(getClass.getClassLoader) type MongosMap = ConcurrentHashMap[MongoIdentifier, MongoAddress] val mongoModuleSymbol = ru.typeOf[MongoDB.type].termSymbol.asModule val moduleMirror = mirror.reflectModule(mongoModuleSymbol) val instanceMirror = mirror.reflect(moduleMirror.instance) val dbsTerm = ru.typeOf[MongoDB.type].declaration(ru.newTermName("dbs")).asTerm.accessed.asTerm val fieldMirror = instanceMirror.reflectField(dbsTerm) val mongosMap = fieldMirror.get.asInstanceOf[MongosMap] mongosMap remove ident }
Mirror
s instead of explicitly callinggetDeclaredField
and it’s friends on Class<?>
objects like you would in plain Java APIs. Why the hassle? The grand idea behind Mirrors (other than “it sounds so cool – reflection => mirrors, yay!“) is that you can obtain either a runtime
or macros
universe. This is discussed in detail in the environment-universes-mirrors section of the Scala docs if you want to dig deeper. The quick overview though is this: when you think about it, AST and reflection stuff are pretty similar. I mean, we traverse the same things, only at a different time (compile-time vs. runtime), so some things may be not available in the runtime, but why change the entire API if we could find some common ground – that is, Mirror
s. Sure, they will be a bit different if used from the macros
universe than from the runtime
universe, but as we also write compile time macros the benefit is huge – only one API with slight flavoring to learn, instead of two separate ones – depending on the stage of when we apply them.Coming back to our code though, as you can see, we were able to avoid going through the magic MODULE$
constant explicitly, it was abstracted away from us thanks to the types of mirrors we’ve been using – it’s also worth noting that for example thereflectModule(ModuleSymbol)
takes a ModuleSymbol
which is obtained via the “safe cast”, as one might think of it in the line above using the asModule
method. The nice thing about asModule
is that it will fail if the term you’re calling it on is not a module. So the API is both designed to be as typesafe as possible, even in reflection-lang, as well as “fail fast” if it detects you’re doing something wrong.
For more docs or further reference check out the docs about scala-reflection or ScalaDoc about scala.reflect.api.Mirrors.
Published at DZone with permission of Konrad Malawski, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments