Question

Let's say I have:

object GLOBAL_OBJECT{
  var str = ""
}

class A(_str: String){
  GLOBAL_OBJECT.str = _str 
}

and I would like to create 2 copies of GLOBAL_OBJECT (for tests), so I am using different classloader to create obj2:

    val obj1 = new A("1")

    val class_loader = new CustomClassLoader()
    val clazz = class_loader.loadClass("my.packagename.A")

    val obj2 = clazz.getDeclaredConstructor(classOf[String]).newInstance("2")

    println("obj1.getSecret() == " + obj1.getSecret())                 // Expected: 1
    println("obj2.getSecret() == " + obj2.asInstanceOf[A].getSecret()) // Expected: 2

which results following error: my.packagename.A cannot be cast to my.packagename.A.

IntelliJ Idea seems to do it correctly, I can run obj2.asInstanceOf[A].getSecret() in "expression" window during debug process without errors.

PS. I have seen similar questions, but I could not find any not regarding loading class from .jarfile.

Was it helpful?

Solution

You're not going to be able to get around Java's class casting, which requires strict typing, within the same ClassLoader. Same with traits/interfaces.

However, Scala comes to the rescue with structural typing (a.k.a. Duck Typing, as in "it quacks like a duck.") Instead of casting it to type A, cast it such that it has the method you want.

Here's an example of a function which uses structural typing:

def printSecret(name : String,  secretive : { def getSecret : String } ) {
  println(name+".getSecret = "+secretive.getSecret)
}

And here's sample usage:

printSecret("obj1", obj1) // Expected: 1
printSecret("obj2", obj2.asInstanceOf[ {def getSecret : String} ]) // Expected: 2

You could, of course, just call

println("secret: "+ obj2.asInstanceOf[ {def getSecret : String} ].getSecret

Here's full sample code that I wrote and tested.

Main code:

object TestBootstrap {
def createClassLoader() = new URLClassLoader(Array(new URL("file:///tmp/theTestCode.jar")))
}

trait TestRunner {
  def runTest()
}

object RunTest extends App {
  val testRunner = TestBootstrap.createClassLoader()
    .loadClass("my.sample.TestCodeNotInMainClassLoader")
    .newInstance()
    .asInstanceOf[TestRunner]

  testRunner.runTest()
}

In the separate JAR file:

  object GLOBAL_OBJECT {
    var str = ""
  }

  class A(_str: String) {
    println("A classloader: "+getClass.getClassLoader)
    println("GLOBAL classloader: "+GLOBAL_OBJECT.getClass.getClassLoader)
    GLOBAL_OBJECT.str = _str

    def getSecret : String = GLOBAL_OBJECT.str
  }


  class TestCodeNotInMainClassLoader extends TestRunner {
    def runTest() {
      println("Classloader for runTest: " + this.getClass.getClassLoader)
      val obj1 = new A("1")

      val classLoader1 = TestBootstrap.createClassLoader()
      val clazz = classLoader1.loadClass("com.vocalabs.A")
      val obj2 = clazz.getDeclaredConstructor(classOf[String]).newInstance("2")

      def printSecret(name : String,  secretive : { def getSecret : String } ) {
        println(name+".getSecret = "+secretive.getSecret)
      }

      printSecret("obj1", obj1) // Expected: 1
      printSecret("obj2", obj2.asInstanceOf[ {def getSecret : String} ]) // Expected: 2
    }
  }

Structural typing can be used for more than one method, the methods are separated with semicolons. So essentially you create an interface for A with all the methods you intend to test. For example:

type UnderTest = { def getSecret : String ; def myOtherMethod() : Unit }

OTHER TIPS

One workaround to actually run some method from dynamically delivered object instead of casting it is to use reflection in order to extract particular method, from new class and then invoke it on our new object instance:

val m2: Method = obj2.getClass.getMethod("getSecret")
m2.invoke(obj2)

The class file that contains obj2.asInstanceOf[A].getSecret() should be reloaded by CustomClassLoader, too.

And you must not use any class that references to A unless you reload the class by the same class loader that reloads A.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top