I have implemented a custom class loader in Scala that serves to isolate plugins from the main application. At present I require a Java interface to act as the shared root object so my main application can use the plugin code.
Interface acting as shared root (I would like this to be Scala):
public interface Handler {
public List<HandlerInfo> getHandlers();
}
Example plugin:
class MyPlugin extends Handler {
def getHandlers: java.util.List[HandlerInfo] = // get some handlers
}
Usage in application:
val jarFile = new File(System.getProperty("user.dir") + "/plugins/" + jarName)
val cl = new PluginLoader(jarFile, this.getClass.getClassLoader) // my custom classloader
val classToLoad = Class.forName(className, true, cl)
val handler = classToLoad.newInstance.asInstanceOf[Handler]
val handlers = handler.getHandlers
This works fine, but my problem is that I have to keep this one Java class around (and the resulting build configuration). I would like instead to use a Scala trait or abstract class, like this:
trait Handler {
def getHandlers : List[HandlerInfo]
}
Then my plugin could look like this:
class MyPlugin extends Handler {
def getHandlers: List[HandlerInfo] = // no more java.util.List
}
But I can't do this, because this line
val handler = classToLoad.newInstance.asInstanceOf[Handler]
throws a ClassCastException
, presumably because the Scala compiler doesn't generate a nice clean Java interface. Is there any way around this, so I can have a Scala-only project?
The problem is not where you think it is; it's somewhere in your class loader. The default class loader works just fine, even the Java one. Evidence:
// File Handler.scala
trait Handler { def getHandlers: List[String] }
// File MyPlugin.scala
class MyPlugin extends Handler { def getHandlers = List("salmon", "cod") }
// File Interop.java
public class Interop {
public static void main(String[] args) {
try {
Class classToLoad = Class.forName("MyPlugin");
Handler handler = (Handler)classToLoad.newInstance();
System.out.println("Class loader = "+handler.getClass().getClassLoader());
System.out.println(handler.getHandlers());
}
catch (Exception e) { System.out.println("Uh-oh: "+e); }
}
}
And here we run it:
$ java -cp .:/usr/share/scala/2.10/lib/scala-library.jar Interop
Class loader = sun.misc.Launcher$AppClassLoader@1f3e8d89
List(salmon, cod)
$
with the Scala List
and everything, and the new class loaded by the default Java class loader.