The Kotlin JSR-223 for plugins, the engine's class loader


How do we create a plugin architecture using the Kotlin JSR-223 implementation? In order to compile code (like via .compile()) it seems like there must be a class loader instantiated somewhere; and a common pattern with plugins is to load code in a new class loader, start a thread, and then set Thread.currentThread().contextClassLoader = theNewClassLoader to run the plugin. This allows code from the main application to load classes in the plugin, as it might need to do for serialisation.

Apache BEAM is one example of a framework that uses the context class loader to find the user classes. One way to build a plugin for a pluggable BEAM application, is to compile Kotlin code to a Jar and then load the Jar, setting the context class loader as above. Instantiating a new class loader is a natural part of loading a Jar, so it is easy to set the context class loader. It stands to reason that the REPL infrastructure must also have a new class loader somewhere inside it, though. Would reusing this class loader not be better than generating a Jar from the application, only to load it back into the same application?

Kind Regards,

In the end, I found an alternative: (a) use K2JVMCompiler to build a JAR and (b) load the JAR with URLClassLoader and (c) set that class loader as the contextClassLoader in a new Thread.

A good example to work from is Kobalt’s com.beust.kobalt.plugin.kotlin.KotlinCompiler.

It should not be necessary to actually create the jar. A classloader can get the bytecode from whereever it wants. It is however necessary to cater for the fact that one source file may produce multiple class files (even true for Java with inner classes). In addition, you probably want to fail with compilation issues at load time, not somewhere halfway through where a class is compiled on-demand. By default a class uses the classloader it was loaded by itself to load referenced classes, so generally loading the plugin instance class with the new class loader should suffice.

The script case is more suited for the context in which there is only a single compilation unit (the script) where sequential processing will allow the just compiled class to be directly loaded into the class loader. If you want a proper plugin architecture you must make sure that this class loader calls defineClass itself and doesn’t delegate it to the default class loader.