Class.forName() produces different results to ClassLoader.loadClass() in certain circumstances [SPR-2611] #7300
Labels
in: core
Issues in core modules (aop, beans, core, context, expression)
type: enhancement
A general enhancement
Milestone
Steve Barham opened SPR-2611 and commented
I've been converting an application to use Spring, and have had a smooth
time of it so far. I have hit somewhat of a stumbling block, however, when
it comes to converting a plugin system which was implemented for the
application.
Background: previously, a new classloader would be created when a plugin was
initialised or updated. This classloader would be used to load a declared
class of a certain interface, instantiate it and call a plug() method.
Conversely, when the plugin was unplugged, an unplug() method would be
called, and the classloader released so that it could be GCed. So far, so
straightforward.
My aim in converting the plugin system was to use nested
ApplicationContexts, so that plugins could have access to beans declared in
the main system without having to specify any number of cumbersome
activation interfaces, and generally to benefit from using Spring. This
seemed to be a straightforward change, until I tested the plugin reloading
functionality. This monitors the JAR from which the plugin is deployed, and
when the filesize or timestamp changes triggers a reload by:
references
classloader
The problem is that the old class definition for the plugin was being used,
rather than the new class definition. I have confirmed through profiling
that no standard references to the plugin classloader exist after the plugin
has been unplugged; a weak reference is held to it by the
CachedIntrospectionResults class.
I have traced the 'problem' down to a single line of code in Spring, in
ClassUtils, line 160, where the following code is executed:
return Class.forName(name, false, classLoader);
Where name is the class of my plugin, and classLoader is confirmed to be the
new plugin classloader which points to the updated plugin classes.
When this is executed for the reloaded plugin application context, the
result is a class whose classloader is not the new plugin classloader passed
in the third parameter to Class.forName() - it is the old, supposedly
collectable instance of the plugin classloader. By constrast, if this line
is replaced with:
return classLoader.loadClass(name);
the correct behaviour is exhibited, i.e. the new plugin classloader which is
passed to ClassUtils.forName() is used to load the class.
I have tried to find a full description of the varying semantics of
Class.forName vs. classLoader.loadClass(), but have yet to find anything of
sufficient detail for me to follow this problem further. I can only confirm
that the old classloader is used by Class.forName despite it being available
for collection, as it is only referenced weakly by
CachedIntrospectionResults.
I can simulate the correct behaviour by invoking System.gc() some large
number of times when a plugin is unplugged, which seems to finalise the old
classloader and make Class.forName use the new one. This is obviously not a
desirable solution! :)
Affects: 2.0 RC3
Attachments:
Issue Links:
The text was updated successfully, but these errors were encountered: