ClassLoaders, delegation, and a plugin system

C

Chris Head

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hello everyone,
I'm currently trying to figure out how to write an application that uses
a plugin-type architecture with a custom-built instance of
URLClassLoader to load the plugins. I have run into a few problems.

First, as a minimal example, consider file a.jar containing classes A
and Launcher, and file b.jar containing class B. I launch the virtual
machine with only a.jar in the classpath, invoking the Launcher class.
The Launcher class's main() method instantiates a URLClassLoader, giving
it b.jar as a classpath.

The Launcher class then uses ClassLoader.loadClass() to reflectively
load class B and call a static method on it. This works fine. Class B
can then use Class.forName() (2 args) to reflectively load class A and
call a method on it.

The first problem arises if I try to make this work in the opposite
direction. If I use ClassLoader.loadClass() to reflectively load A
instead and call a static method on it, and that static method uses
Class.forName() (2 args) to load B, the load fails. I believe this is
because ClassLoader.loadClass() is delegating to the system ClassLoader
(as it should), so A is getting loaded by the system ClassLoader, which
cannot find B. Class.forName() (2 args) is defined as using the same
ClassLoader which loaded the calling class (in this case, the system
ClassLoader). I understand that I could solve this by setting my custom
ClassLoader as the thread's context ClassLoader and using it directly in
A, but I don't know if this is the "right" solution, and it doesn't
solve my other problems, described below.

My second problem is what to do if A refers to B statically, rather than
reflectively. In this case, there is no chance for A to use whatever
ClassLoader it wishes. Setting the context ClassLoader has no effect
here. I cannot figure out how to solve this problem. No matter what
happens, A cannot see B, because A is loaded (even if only by
delegation) by the system ClassLoader.

I have a third problem which I haven't really explored very much yet,
but which is probably going to hit me really hard sometime: what the
heck does serialization do to all this? What if I want to load a
serialized version of a B object? How do I make ObjectInputStream use my
custom ClassLoader?

The only solution I can think of so far is to use a kind of proxy
launcher. I have a lightweight launcher.jar file with Launcher in it and
is alone on the command-line classpath. The Launcher in here creates a
custom ClassLoader pointing at both the main application jar and the
plugin jars, then calls the main application's Launcher. This way, the
main application's Launcher is loaded by the custom ClassLoader (the
system ClassLoader can't see it), and my problems "go away". It feels
like jumping through a lot of hoops though. Is there an easier way?

If my explanations are not good enough, I can provide compilable code
samples.

One final note: Including a.jar in my custom URLClassLoader's classpath
changes nothing. URLClassLoader delegates (as it's documented to) to the
system ClassLoader FIRST, so only if the system ClassLoader cannot find
a class does the custom loader start working.

Thank you in advance,
Chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (MingW32)

iD8DBQFC6oCi6ZGQ8LKA8nwRAmEsAJ95vrpmYfUGNsN2l5ermfyd1BA05ACfbm5c
+tnpueT8GKQotZARxlX49Og=
=z8O2
-----END PGP SIGNATURE-----
 
C

Chris Smith

Chris Head said:
My second problem is what to do if A refers to B statically, rather than
reflectively. In this case, there is no chance for A to use whatever
ClassLoader it wishes. Setting the context ClassLoader has no effect
here. I cannot figure out how to solve this problem. No matter what
happens, A cannot see B, because A is loaded (even if only by
delegation) by the system ClassLoader.

I'm not sure what you're trying to do here. If A refers to B
statically, there is no point whatsoever in making A available to the
system classloader without making B available to the system classloader
as well. It will be impossible to ever load the class A, because of
that static reference. Is A part of the plugin, or part of the
framework? If it's part of the plugin, then it should be packaged with
the plugin. If it's part of the framework, then it doesn't make sense
to want to statically reference a class from the plugin. That static
references makes your plugin no longer a plugin at all, and you may as
well not bother with the custom classloader.

I'm racking my brain trying to figure out what you might be trying to do
that would break this. The only thing that comes to mind is this. Are
you requiring that all your plugins define classes with the same fully
qualified name (B)? If so, then you'd be well-advised to abandon that
approach (the dlopen approach to plugins) in favor of letting the plugin
JAR file specify the name of the main plugin class using either a
resource in the JAR file or in the manifest. Then your framework would
never statically reference any plugin class, but would instead call
ClassLoader.loadClass, then Class.newInstance, and then cast the result
to a known superinterface.
I have a third problem which I haven't really explored very much yet,
but which is probably going to hit me really hard sometime: what the
heck does serialization do to all this? What if I want to load a
serialized version of a B object? How do I make ObjectInputStream use my
custom ClassLoader?

Good question! A quick Google search suggests that you should extend
ObjectInputStream and override the resolveClass method... but I don't
really know anything more about this than you do. Try that out, and ask
again if it doesn't work.
The only solution I can think of so far is to use a kind of proxy
launcher.

You should not need to do this. The question is why your framework
(application) code has a static reference to the plugin code. Once
that's settled, the rest will fall into place.

--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
C

Chris Head

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Chris said:
[snip]

I'm not sure what you're trying to do here. If A refers to B
statically, there is no point whatsoever in making A available to the
system classloader without making B available to the system classloader
as well. It will be impossible to ever load the class A, because of
that static reference. Is A part of the plugin, or part of the
framework? If it's part of the plugin, then it should be packaged with
the plugin. If it's part of the framework, then it doesn't make sense
to want to statically reference a class from the plugin. That static
references makes your plugin no longer a plugin at all, and you may as
well not bother with the custom classloader.

Well, the static reference thing was a bit of a tangent actually; I
*don't* plan to have a static reference to a plugin class in my main
app. I agree, that would be silly in just about every case.

[snip]
Good question! A quick Google search suggests that you should extend
ObjectInputStream and override the resolveClass method... but I don't
really know anything more about this than you do. Try that out, and ask
again if it doesn't work.

Thanks. I see that it should be quite straightforward. I hadn't done any
Googling because my code is not yet at the point of serialization. I
just wondered if anyone knew offhand whether it would come and hit me or
if the solution is easy.
You should not need to do this. The question is why your framework
(application) code has a static reference to the plugin code. Once
that's settled, the rest will fall into place.

I'm not 100% confident with my knowledge of ClassLoaders yet. Somehow it
feels "unnatural" passing instances of classes that come from different
loaders to each other, but that's just me. Once I get things organized I
guess it'll all work out fine, using the custom ClassLoader to load all
plugin classes and making sure than any reference from the application
to a plugin uses the appropriate custom ClassLoader.

Thanks again,
Chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (MingW32)

iD8DBQFC6pWg6ZGQ8LKA8nwRAkENAJ40Bfkq9b929SzGlDD5StK2GeKHkwCdH4hA
/LMvZtOGQAQgphf56D5VCmQ=
=7zU/
-----END PGP SIGNATURE-----
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top