Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Scala Reflection Basics – Accessing an Object's Private Field Explained

DZone's Guide to

Scala Reflection Basics – Accessing an Object's Private Field Explained

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

Today’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 MongoAddressto 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 MongoFlatSpecs, 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 MongoIdentifiers, 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$();
    // ...
}
Since we want the  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
}
The first thing to notice here is the use of  Mirror s instead of explicitly calling getDeclaredField  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.

Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:

Published at DZone with permission of Konrad Malawski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}