loading a class whose bytecode comes in a byte[]

L

Luca Rosellini

Hi everybody, I have this problem with classloaders:

Suppose to have two nodes A and B, node A creates a new class C1 (in the package "Package") which implements an interface (call it myInterface) known to both node A and B.
A retrieves the bytecode for C1, stores it in a byte[] and send it in a datagram to node B.

In order to properly load the class on B's JVM I actually inherit from ClassLoader and override loadClass method in this way:

class myClassLoader extends ClassLoader{
...
...

public Class loadClass(String className, boolean res){
// check if the clas has not yet been loaded
// blah blah

byte[] classData = buffer_containing_bytecode;

Class result = defineClass(className, classData, 0, classData.length);
...
...
return result;
}

}

when I need to load the class in my program I do the following (in a try-catch clause):

myClassLoader cl = new myClassLoader();
Class myClass = cl.loadClass("P.C1",true);

loadClass seems to return successfully, if I use reflection on myClass object to get informations on methods or fields of the class I get correct results.
This leads me to think that class is loaded properly.
However if I try to create an Instance from myClass using myClass.newInstance() I get an InstantiationException.

Any of you knows what I am doing wrong?



P.S.: There's another solution that works, but I don't like it, it looks much more like a workaround than a solution and a write on disk is needed:
when the bytecode arrives on node B I dump it in a file called "C1.class" in a folder called"Package" using a File a object and calling deleteOnExit() on that File object.
when tring to create a new instance from object myClass everything as expected.
 
J

John C. Bollinger

Luca said:
Hi everybody, I have this problem with classloaders:

Suppose to have two nodes A and B, node A creates a new class C1
(in the package "Package") which implements an interface (call it
myInterface) known to both node A and B.
OK.

A retrieves the bytecode for C1, stores it in a byte[] and send
it in a datagram to node B.
Fine.

In order to properly load the class on B's JVM I actually inherit
from ClassLoader and override loadClass method in this way:

class myClassLoader extends ClassLoader{
...
...

public Class loadClass(String className, boolean res){
// check if the clas has not yet been loaded
// blah blah

byte[] classData = buffer_containing_bytecode;

Class result = defineClass(className, classData, 0, classData.length);
...
...
return result;
}

}

You should be overriding findClass(String), not loadClass(String,
boolean). That will allow the the base implementation to handle more of
the details for you (such as checking whether the class is already
loaded), and will allow your ClassLoader to participate in the standard
ClassLoader delegation model, which is probably where your current
version is falling down. When defining a custom ClassLoader it is
almost always findClass that you want to override. Refer to
ClassLoader's API docs for details.
when I need to load the class in my program I do the following (in a try-catch clause):

myClassLoader cl = new myClassLoader();

Assuming your custom ClassLoader were set up correctly for delegation,
that instantiation would make your custom ClassLoader delegate to the
system ClassLoader. That might be OK, and perhaps it would even be the
expressly desired behavior, but it is more likely that you want to
delegate to this.getClass().getClassLoader().
Class myClass = cl.loadClass("P.C1",true);

That should work. It may not be especially useful to tell the class
loader to link the class immediately -- I would just use the one-arg
form of loadClass unless it is important to your application that the
class be linked immediately.
loadClass seems to return successfully, if I use reflection on myClass object to
get informations on methods or fields of the class I get correct results.

To the extent you checked, as far as you could determine.
This leads me to think that class is loaded properly.
However if I try to create an Instance from myClass using myClass.newInstance() I
get an InstantiationException.

Which, according to the API docs, tells you that the system thinks the
Class in question represents an abstract class or an interface. I think
that may be a red herring in your case, though.
Any of you knows what I am doing wrong?

P.S.: There's another solution that works, but I don't like it, it looks
much more like a workaround than a solution and a write on disk is needed:
when the bytecode arrives on node B I dump it in a file called "C1.class"
in a folder called"Package" using a File a object and calling deleteOnExit()
on that File object. when tring to create a new instance from object
myClass everything as expected.

This is pretty strong evidence that your ClassLoader implementation is
what's causing the trouble. Rewrite it to correctly make use of the
ClassLoader delegation model (as described above) and instantiate it
with the right delegation parent (probably the ClassLoader of the
instantiating class). If that doesn't solve the problem then come back
with the full stack trace of your exception and complete code for the
ClassLoader and attempted class instantiation. If there is Java code
for the class being transferred and its dependencies then that might be
helpful too.


John Bollinger
(e-mail address removed)
 
L

Luca Rosellini

On Thu, 18 Nov 2004 08:57:54 -0500

JCB> If that doesn't solve the problem then come back
JCB> with the full stack trace of your exception and complete code for the
JCB> ClassLoader and attempted class instantiation. If there is Java code
JCB> for the class being transferred and its dependencies then that might be
JCB> helpful too.

Thanks for the answer John,

I tried to follow your suggestions, but unfortunately still doesn't want to work.
Before pasting the stacktrace I'll briefly explain the structure of the program to let you understand better:

there's a thread server that listens for incoming datagrams, when a new message arrives a new thread is spawned to handle it.
The incoming message contains an instance of class SubscriptionParameters which implements the Externalizable interface.
Filter is an interface known to both node A and B (sender and receiver) in package "filters".
The class I want to load on the receiver is an implementation of Filter (implemented by node A, the sender):

//SubscriptionParameters.java
package datatypes;
class SubscriptionParameters implements Externalizable {
...
private byte[] evFilter;
Filter filterInstance;

...

public void writeExternal(java.io_ObjectOutput objectOutput)
throws java.io.IOException {
...

// the name of the class serialized by sender is something like
// filter.TempFilter, filter is a package present on bot node A and B
objectOutput.writeObject( filterInstance.getClass().getName() );
objectOutput.writeObject( evFilter );
objectOutput.writeObject( filterInstance );
}

public void readExternal(java.io_ObjectInput objectInput)
throws java.io.IOException, java.lang.ClassNotFoundException {

...
String className = (String)objectInput.readObject();
evFilter = (byte[])objectInput.readObject();

try {
FilterClassLoader fcl = new FilterClassLoader( this.getClass().getClassLoader(), evFilter);
Class loadedFilter = fcl.loadClass( className );

/* the exception is raised here when trying to deserialize the instance
* of the class that should be already loaded
*/
line83: filterInstance = (Filter)objectInput.readObject();
}
}

}

This is the inherited ClassLoader:

//FilterClassLoader.java
package filters;
public class FilterClassLoader extends ClassLoader {
private byte[] classData;

public FilterClassLoader(ClassLoader parent, byte[] data) {
super(parent);
classData = data;
}

public Class findClass(String className) {
return defineClass(className, classData, 0, classData.length);
}
}

and here is the stacktrace:

java.lang.ClassNotFoundException: filters.TempFilter
at java.net.URLClassLoader$1.run(URLClassLoader.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:219)
at java.io_ObjectInputStream.resolveClass(ObjectInputStream.java:558)
at java.io_ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1513)
at java.io_ObjectInputStream.readClassDesc(ObjectInputStream.java:1435)
at java.io_ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1626)
at java.io_ObjectInputStream.readObject0(ObjectInputStream.java:1274)
at java.io_ObjectInputStream.readObject(ObjectInputStream.java:324)
at datatypes.SubscriptionParameters.readExternal(SubscriptionParameters.java:83)
at java.io_ObjectInputStream.readExternalData(ObjectInputStream.java:1686)
at java.io_ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1644)
at java.io_ObjectInputStream.readObject0(ObjectInputStream.java:1274)
at java.io_ObjectInputStream.defaultReadFields(ObjectInputStream.java:1845)
at java.io_ObjectInputStream.readSerialData(ObjectInputStream.java:1769)
at java.io_ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1646)
at java.io_ObjectInputStream.readObject0(ObjectInputStream.java:1274)
at java.io_ObjectInputStream.readObject(ObjectInputStream.java:324)
at transport.PublishSubscribeDatagramSocket.receive(PublishSubscribeDatagramSocket.java:85)
at nodes.DispatcherServerThread.run(DispatcherServerThread.java:79)


I tryied to put a System.out.println(...) in my findClass(...) to check if it is properly called, and it is.
Hope this helps you to point me what I am doing wrong.

Best
Luca
 
C

Chris Uppal

Luca said:
FilterClassLoader fcl = new FilterClassLoader(
this.getClass().getClassLoader(), evFilter);
Class loadedFilter = fcl.loadClass( className );

/* the exception is raised here when trying to deserialize the instance
* of the class that should be already loaded
*/
line83: filterInstance = (Filter)objectInput.readObject();

Assuming that 'objectInput' is a java.io_ObjectInputStream, rather than some
custom subclass of your own, I don't think that readObject() will use the'fcl'
classloader to find the named class as it scans the input data.

I haven't ever tried doing this myself, but I think you need to create your own
subclass of ObjectInputStream, probably overriding resolveClass() to use your
classloader. See the javadocs for ObjectInputStream.

-- chris
 
J

John C. Bollinger

Luca said:
public void readExternal(java.io_ObjectInput objectInput)
throws java.io.IOException, java.lang.ClassNotFoundException {

...
String className = (String)objectInput.readObject();
evFilter = (byte[])objectInput.readObject();

try {
FilterClassLoader fcl = new FilterClassLoader( this.getClass().getClassLoader(), evFilter);
Class loadedFilter = fcl.loadClass( className );

/* the exception is raised here when trying to deserialize the instance
* of the class that should be already loaded
*/
line83: filterInstance = (Filter)objectInput.readObject();
}
}

}

This is the inherited ClassLoader:

//FilterClassLoader.java
package filters;
public class FilterClassLoader extends ClassLoader {
private byte[] classData;

public FilterClassLoader(ClassLoader parent, byte[] data) {
super(parent);
classData = data;
}

public Class findClass(String className) {
return defineClass(className, classData, 0, classData.length);
}
}

That's a better ClassLoader implementation, but you're sure going to
have a lot of ClassLoaders hanging around if you do that.

and here is the stacktrace:

java.lang.ClassNotFoundException: filters.TempFilter
at java.net.URLClassLoader$1.run(URLClassLoader.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:187)

[...]

Which is a different exception than you reported before, and also a
different usage mode than you described before.

The problem here is in your readExternal method. The strategy you have
adopted cannot work. You successfully load the class, yes, but classes
are scoped to the ClassLoader that loads them. When your
ObjectInputStream tries to tries to deserialize the Externalizable, it
asks its own ClassLoader for the object's class. That ClassLoader does
not know the class (even though it has a descendant that does), and none
of its ancestors does either, thus you get a ClassNotFoundException.

To make a scheme like this work, you would need to insert a more
flexible custom ClassLoader somewhere much deeper in the application's
ClassLoader tree, perhaps [for the sake of convenience] as the
ClassLoader for everything except an "application launcher" class. You
would obviously not be able to use a one-off ClassLoader for such a purpose.

Alternatively, do you realize that Java already has a built-in mechanism
for this sort of thing? Look into Java RMI; your problem falls squarely
within its scope.


John Bollinger
(e-mail address removed)
 
L

Luca Rosellini

John C. Bollinger ha scritto:
The problem here is in your readExternal method. The strategy you have
adopted cannot work. You successfully load the class, yes, but classes
are scoped to the ClassLoader that loads them. When your
ObjectInputStream tries to tries to deserialize the Externalizable, it
asks its own ClassLoader for the object's class. That ClassLoader does
not know the class (even though it has a descendant that does), and none
of its ancestors does either, thus you get a ClassNotFoundException.

To make a scheme like this work, you would need to insert a more
flexible custom ClassLoader somewhere much deeper in the application's
ClassLoader tree, perhaps [for the sake of convenience] as the
ClassLoader for everything except an "application launcher" class. You
would obviously not be able to use a one-off ClassLoader for such a
purpose.

Is this really possible to achieve?
I mean, I could write my own classloader, but it would still need to
delegate to {system|extension|bootstrap}classloader the loading of core
classes. Since ObjectInput is a core class, it's classLoader doesn't
know how to find the class even though my own global app classloader does.

Unfortunately RMI are not usable for this kind of application.

Best
Luca
 
J

John C. Bollinger

Luca said:
John C. Bollinger ha scritto:
To make a scheme like this work, you would need to insert a more
flexible custom ClassLoader somewhere much deeper in the application's
ClassLoader tree, perhaps [for the sake of convenience] as the
ClassLoader for everything except an "application launcher" class.
You would obviously not be able to use a one-off ClassLoader for such
a purpose.


Is this really possible to achieve?

I think so.
I mean, I could write my own classloader, but it would still need to
delegate to {system|extension|bootstrap}classloader the loading of core
classes. Since ObjectInput is a core class, it's classLoader doesn't
know how to find the class even though my own global app classloader does.

As I understand it (and my understanding may be flawed) classes keep
track of the ClassLoader from which they were originally requested, even
if that ClassLoader delegated the actual loading. Otherwise Java would
have to be considerably more magical than I think it is. Why don't you
write a small application to test it? It wouldn't take much code to
simulate the specific kind of usage paradigm we're talking about here.
Unfortunately RMI are not usable for this kind of application.

Well, you know your own requirements.


John Bollinger
(e-mail address removed)
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top