2

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.

IProblemFactory
  • 9,551
  • 8
  • 50
  • 66
  • You could inject VM specific values. – Basilevs May 11 '14 at 15:15
  • 1
    I can't post a full answer now, but you'll need multiple classloaders. – chrylis -cautiouslyoptimistic- May 11 '14 at 15:38
  • @chrylis, wow, I thought no one could be more bold. – Basilevs May 11 '14 at 16:00
  • @chrylis : I will accept it as an answer if you could provide some simple example for my case. – IProblemFactory May 11 '14 at 17:59
  • It can be done, but nothing with custom class loading is going to be "simple", and what you need will depend very much on the structure of the application or components you need to test. – chrylis -cautiouslyoptimistic- May 11 '14 at 18:27
  • @chrylis well, I suppose I have very simple case: I just want to create some another `GLOBAL_OBJECT` instance and force `a2` to use it (instead of using that one `GLOBAL_OBJECT` using by `a1`) – IProblemFactory May 11 '14 at 18:49
  • @ProblemFactory The problem is that the types that `a1` and `a2` are expecting (say `MyClass`) need to be kept distinct, meaning that the `a1` and `a2` objects are going to need to be created under the separate classloaders themselves. The key is that in Java, a class is a combination of the fully-qualified name *and* the classloader that loaded it. The best I can recommend is probably to read a summary of OSGi and servlet classloading. – chrylis -cautiouslyoptimistic- May 11 '14 at 18:51
  • A class in jvm runtime is identified by the pair of ``, where `ClassLoader` is the one finally loads the Class file. I guess, in your jvm process, there are two class A, `` and ``, they are different, so the latter cannot be cast to former. That's all depending on how you implement the `CustomeClassLoader`. Any code snip for your customized class loader? – Evans Y. May 15 '14 at 09:29
  • This article can help to explain what I said above. http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html – Evans Y. May 15 '14 at 09:37
  • @EvansY I undertood already why I got this error, I am looking for solution :) Currently I use Classloader from here http://www.xinotes.net/notes/note/444/ – IProblemFactory May 15 '14 at 10:05

3 Answers3

1

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)
IProblemFactory
  • 9,551
  • 8
  • 50
  • 66
1

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 }
David Leppik
  • 3,194
  • 29
  • 18
0

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.

Yang Bo
  • 3,586
  • 3
  • 22
  • 35
  • Class which contains that code is top-level class, and it is run as a test, so I think it is not possible. – IProblemFactory May 14 '14 at 12:48
  • but you are close, IntelliJ Idea seems to do it, I can run `obj2.asInstanceOf[A].getSecret()` in "expression" window without errors. – IProblemFactory May 14 '14 at 12:50
  • I didn't ask "why", I just wrote what I want to accomplish and in bounty description I wrote that I need sample code, your answer is incorrect therefore I don't care about your points. – IProblemFactory May 14 '14 at 13:58