I am trying to write my own small plugin loader:
package some.package.plugins
import org.clapper.classutil.{ClassFinder, ClassInfo}
import wvlet.log.LogSupport
import java.io.File
import java.net.{URL, URLClassLoader}
object PluginManager extends LogSupport {
val PLUGIN_DIRECTOR = "./plugins/"
val PLUGIN_INTERFACE = "package.Plugin"
val jars: List[File] = new File(PLUGIN_DIRECTOR).listFiles.filter(_.getPath.endsWith(".jar")).toList
private val pluginMap : Map[String,(ClassInfo,File)] = {
jars.flatMap {
jar =>
val finder = ClassFinder(List(jar))
println(finder.getClasses().map(_.interfaces).toList)
finder.getClasses().filter(_.interfaces.contains(PLUGIN_INTERFACE)).map {
info =>
info.name.split("\\.").last -> (info, jar)
}
}.toMap
}
def getPlugins : Set[String] = pluginMap.keySet
def loadPlugins(name : String) : AnalysisPlugin = {
val classInfo : (ClassInfo,File) = pluginMap.getOrElse(name,throw new IllegalArgumentException(s"the plugin $name does not exist"))
val urls : List[URL] = List(classInfo._2.toURI.toURL)
val childClassLoader = URLClassLoader.newInstance(urls.toArray, ClassLoader.getSystemClassLoader)
info(s"loading plugin ${classInfo._1.name}")
val c: Class[_] = childClassLoader.loadClass(classInfo._1.name)
assert(c.getConstructors.length == 1)
c.getConstructors.head.newInstance().asInstanceOf[AnalysisPlugin]
}
}
I use ClassFinder and ClassInfo to identify all classes in a .jar that implement the PLUGIN_INTERFACE.
The output confirms that I do find the class that does. I then use the URLClassLoader provided with exactly the same jars to load the corresponding class using childClassLoader.loadClass but I get a NoClassDefFound exception.
The issue here seems to be the interface class, i.e., die interface that is supposed to enable different plugins being transparent for the plugin manager. package.Plugin is the class that cannot be loaded.
How can I load a class contained in a .jar using URLClassLoader.
I do not understand the underlying logic but you need to provide a different parent class loader to the
URLClassLoader. After some fuzzing I successfully ran the code using the class loader of the object containing the main function: