Scala - Dynamic object/class loading

user942821 picture user942821 · Jan 15, 2012 · Viewed 23.4k times · Source

In Java, I load external class (in .jar file) by this way:

ClassLoader classLoader = new URLClassLoader(new URL[] {
  new File("module.jar").toURI().toURL()});
Class clazz = classLoader.loadClass("my.class.name");
Object instance = clazz.newInstance();

//check and cast to an interface, then use it
if (instance instanceof MyInterface)
  ...

And it works fine.

====================

Now I want to do the same thing in Scala. I have a trait named Module (Module.scala):

trait Module {
  def name: String
}

object Module {
  lazy val ModuleClassName = "my.module.ExModule"
}

I write a module extending Module, then compile it to module.jar:

package my.module

import Module

object ExModule extends Module {}

Then I load it by this code:

var classLoader = new URLClassLoader(Array[URL](
  new File("module.jar").toURI.toURL))
var clazz = classLoader.loadClass(Module.ModuleClassName)

It works fine. But if I create new instance, I get this exception:

java.lang.InstantiationException: my.module.ExModule

If I test it:

clazz.isInstanceOf[Module]

-> always return false.

So could you help me on this problem?

Edited

I guess it is because ExModule is an object (not class). But when I change it to class, and classLoader.loadClass(...) raises a java.lang.NoClassDefFoundError. I guess it is because ExModule is extended from a trait.

I'm confused. Could anyone please help me?

Edited

clazz.isInstanceOf[Class[Module]]//or Class[Byte], or Class[_]...

returns true.

Answer

user942821 picture user942821 · Jan 15, 2012

Oops... I got the answer.

Learn from:

====================

I guess this way is temporary before Scala team provides right way to load an object/class/trait... from external jar file. Or because I can't find out the right way. But currently this helps me out of the problem.

var classLoader = new java.net.URLClassLoader(
  Array(new File("module.jar").toURI.toURL),
  /*
   * need to specify parent, so we have all class instances
   * in current context
   */
  this.getClass.getClassLoader)

/*
 * please note that the suffix "$" is for Scala "object",
 * it's a trick
 */
var clazzExModule = classLoader.loadClass(Module.ModuleClassName + "$")

/*
 * currently, I don't know how to check if clazzExModule is instance of
 * Class[Module], because clazzExModule.isInstanceOf[Class[_]] always
 * returns true,
 * so I use try/catch
 */
try {
  //"MODULE$" is a trick, and I'm not sure about "get(null)"
  var module = clazzExModule.getField("MODULE$").get(null).asInstanceOf[Module]
} catch {
  case e: java.lang.ClassCastException =>
    printf(" - %s is not Module\n", clazzExModule)
}

That's all :-)

Edited

I'd better design ExModule as a class. After loading it from jar file, I can check it as like as:

var clazz = classLoader.loadClass(Module.ModuleClassName)
if (classOf[Module].isAssignableFrom(clazz))
  ...

Note:

You can not do it the opposite way:

if (clazz.isAssignableFrom(classOf[Module]))

because Module is a trait/object, isAssignableFrom() will not work in this case.