book
Article ID: KB0080876
calendar_today
Updated On:
Description
Java classes loaded with .JavaAttachClassPath() can only find classes previously attached, not classes attached in a later call to .JavaAttachClassPath(). Customers will discover that a call to .JavaMethod() returns a java.lang.ClassNotFoundException, even though missing class was attached using .JavaAttachClassPath().
Issue/Introduction
Understanding .JavaAttachClassPath()
Environment
Product: TIBCO Spotfire S+
Version: All supported versions
OS: All supported operating systems
--------------------
Resolution
Adding a JAR or class to an existing JVM instance is fairly complicated. Once the JVM is started, the system ClassLoader cannot be modified. When the JVM is started (when Spotfire S+ is started), the ClassLoader contains the base Java classes, along with anything found in the CLASSPATH.
.JavaAttachClassPath() actually creates a new extension ClassLoader that extends the main system ClassLoader, and adds your new classpath(s) to this new class loader. This is what allows S-PLUS to add new classes at runtime, rather than having to add the classes to the classpath and restart S-PLUS (and the JVM). Successive calls to .JavaAttachClassPath() chain upon the previous extension ClassLoader, each time adding on the new classpath(s).
This works fine, except that chained Java ClassLoaders are finicky, and only know about those classes loaded further up (prior) in the chain. So if a class imports classes that are loaded later after the base class, it won't be able to see them at all. This is the reason for the behavior you are seeing.
In the Spotfire S+ scenario described:
1) class Simple is loaded in the system classpath, therefore is part of the base system ClassLoader. It is visible from .JavaField and .JavaMethod and can see all other classes in the system ClassLoader.
System ClassLoader [CLASSPATH]
^
|
Simple
2) class somepackage/SomeClass is attached as an extension to the system ClassLoader using .JavaAttachClassPath(). It is visible from .JavaField and .JavaMethod as well, because they always use the bottom-most extension ClassLoader to find the referenced package.
System ClassLoader [CLASSPATH]
^
|
Simple
^
|
somepackage/SomeClass
^
|
S-PLUS .JavaField/.JavaMethod
3) .JavaField call to somepackage/SomeClass works because S-PLUS can see somepackage/SomeClass.
4) .JavaMethod call to Simple fails, because Simple imports somepackage/SomeClass, but Simple can only see those classes loaded in its own (System) ClassLoader, or any parent ClassLoader.
Note that the reason the .JavaField call works and the .JavaMethod call doesn't is because the .JavaField is calling somepackage/SomeClass directly, but the .JavaMethod call is calling Simple directly, but somepackage/SomeClass indirectly. .JavaField and .JavaMethod, in fact, use exactly the same ClassLoader, so what's visible to one is always visible to the other.
The simple solution to this chaining issue is to make sure that all required classes are loaded at the same time or before the dependent class. In this example, if Simple were not included in the system CLASSPATH, loading it at runtime should work as expected:
# neither Simple.class nor somepackage/SomeClass in classpath.
.JavaAttachClassPath("/path/to/somepackage.jar");
.JavaAttachClassPath("/path/containing/Simple.class/");
System ClassLoader [CLASSPATH]
^
|
somepackage/SomeClass
^
|
Simple
^
|
S-PLUS .JavaField/.JavaMethod
.JavaMethod("Simple", ...) # works because somepackage.jar is attached above Simple.