Loading custom class loader to load a transferred

2019-08-12 19:05发布

问题:

I transfer a whole .class file (and it's declared and anonymous classes) as a byte[] and want to be able to define it using the class loader on another computer that receives the byte[].

I have found a solution at Java: How to load Class stored as byte[] into the JVM?, however, I don't understand how to use this ByteClassLoader for my purpose. I just post the code here again:

public class ByteClassLoader extends URLClassLoader {
private final Map<String, byte[]> extraClassDefs;

public ByteClassLoader(URL[] urls, ClassLoader parent, Map<String, byte[]> extraClassDefs) {
    super(urls, parent);
    this.extraClassDefs = new HashMap<String, byte[]>(extraClassDefs);
}

@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
    byte[] classBytes = this.extraClassDefs.remove(name);
    if (classBytes != null) {
        return defineClass(name, classBytes, 0, classBytes.length);
    }
    return super.findClass(name);
}

}

The question is: how do I have to instantiate it?

I tried to use it by simply instantiating it as ByteClassLoader b = new ByteClassLoader(...); but obviously, this is not working (as expected).

I found a way of loading a new class loader at the beginning of the program at https://analyzejava.wordpress.com/2014/09/25/java-classloader-loading-a-custom-classloader-on-jvm-start/ using -Djava.system.class.loader =...ByteClassLoader, however, this did not work either (it did not find the class ByteClassLoader. It would be very helpful to me if somebody could point me into the right direction of how to use the referenced class loader (I'm very new to class loaders and do not yet fully understand how they work).

Edit: I have a class SerializeUtils with a method deserialize() where I instantiate the ByteClassLoader. As it did not work with the referenced implementation, I tried to do ByteClassLoader extends ClassLoader (I did not know where to get the URL[] from) and then change the ContextClassLoader of that thread:

public static Map<String, Class<?>> deserialize(Map<String, byte[]> classesToDefine) {

    // SerializeUtils.class.getClassLoader().
    ByteClassLoader l = new ByteClassLoader(Thread.currentThread().getContextClassLoader(), classesToDefine); // TODO this
                                                                // may be a
                                                                // problem
    Thread.currentThread().setContextClassLoader(l);
    Map<String, Class<?>> classes = new HashMap<>();
    for (String className : classesToDefine.keySet()) {
        try {
            System.out.println("ClassName in deserialize: "+ className);
            Class<?> c = ((ByteClassLoader)Thread.currentThread().getContextClassLoader()).findClass(className);
            System.out.println("Class found is : " + c.getName());
            classes.put(className, c);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    return classes;
}

What I hoped to achieve is that the converted byte array is now available for the execution, such that I may be able to instantiate it. I need this as I am also passing the actual data of the object as a serialized byte array using and, right after I loaded the class, would decode this object and use it using:

public static Object decodeJavaObject(byte[] me, int offset, int length) throws ClassNotFoundException,
        IOException {
    ByteArrayInputStream bais = new ByteArrayInputStream(me, offset, length);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object obj = ois.readObject();
    // no need to call close of flush since we use ByteArrayInputStream
    return obj;
}

So I call SerializeUtils.deserialize() to deserialize the .class file in the hope of having it available afterwards. Right after that, I call

So the complete deserialization process looks as follows:

public static Job deserialize(JobTransferObject jobToDeserialize) throws ClassNotFoundException, IOException {
    Job job = new Job();
    for (TransferObject taskTransferObject : jobToDeserialize.taskTransferObjects()) {
        SerializeUtils.deserialize(taskTransferObject.classFiles());
        Task task = (Task) Utils.decodeJavaObject(taskTransferObject.data(), 0, taskTransferObject.data().length);
        job.addTask(task);
    }

    return job;
}

So I first call SerializeUtils.deserialize() to deserialize the class files in the hope of having them available for the next invocation of Utils.decodeJavaObject where I try to deserialize the byte[] of the actual object I transferred (so I send the class files of the class I want to instantiate and then decode an object of that class I also sent over the network). Task is an abstract class that the class file I deserialize extends, so Task is known to the program that needs to deserialize the implementation of it). BTW, TransferObject looks like this:

public class TransferObject implements Serializable{
/**
 * 
 */
private static final long serialVersionUID = 8971732001157216939L;
private byte[] data;
private Map<String, byte[]> classFiles;
private String className;

public TransferObject(byte[] data, Map<String, byte[]> classFiles, String className) {
    this.data = data;
    this.classFiles = classFiles;
    this.className = className;
}

public byte[] data() {
    return this.data;
}

public Map<String, byte[]> classFiles() {
    return this.classFiles;
}

public String className() {
    return this.className;
}

}

(data = serialised object extending Task on another computer, classFiles = the serialised class files of that extended object, and className the actual name of the extended class)

And extension of Task on another computer looks e.g. like this:

Task initShutdown = new Task(writeTask.currentId(), NumberUtils.next()) {

        /**
         * 
         */
        private static final long serialVersionUID = -5543401293112052880L;

        @Override
        public void broadcastReceiver(NavigableMap<Number640, Data> input, DHTConnectionProvider dht)
                throws Exception {
            dht.shutdown(); 
        }

    };

And the Task abstract class is simply the following code:

public abstract class Task implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9198452155865807410L;
private final Number640 previousId;
private final Number640 currentId;

public Task(Number640 previousId, Number640 currentId) {
    this.previousId = previousId;
    this.currentId = currentId;
}

public abstract void broadcastReceiver(NavigableMap<Number640, Data> input, DHTConnectionProvider dht)
        throws Exception;


public Number640 currentId() {
    return this.currentId;
}

public Number640 previousId() {
    return this.previousId;
}}

Sorry for the complicated explanation...

回答1:

Without having tested it, here is my suggestion:

Create a single classloader per definition map:

public static Job deserialize(JobTransferObject jobToDeserialize) throws ClassNotFoundException, IOException {
    Job job = new Job();
    for (TransferObject taskTransferObject : jobToDeserialize.taskTransferObjects()) {
        ClassLoader cl = new ByteClassLoader(new URL[0], Task.class.getClassLoader(), taskTransferObject.classFiles());
        Task task = (Task)Utils.decodeJavaObject(taskTransferObject.data(), 0, taskTransferObject.data().length, cl);
        job.addTask(task);
    }
    return job;
}

and extend ObjectInputStream to use this classloader in deserialization:

public static Object decodeJavaObject(byte[] me, int offset, int length, final ClassLoader cl) throws ClassNotFoundException, IOException {
    ByteArrayInputStream bais = new ByteArrayInputStream(me, offset, length);
    ObjectInputStream ois = new ObjectInputStream(bais) {
        @Override
        protected Class<?> resolveClass(ObjectStreamClass objectStreamClass)
                throws IOException, ClassNotFoundException {

            Class<?> clazz = Class.forName(objectStreamClass.getName(), false, cl);
            if (clazz != null) {
                return clazz;
            } else {
                return super.resolveClass(objectStreamClass);
            }
        }

        @Override
        protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
            Class<?>[] interfaceClasses = new Class[interfaces.length];
            for (int i = 0; i < interfaces.length; i++) {
                interfaceClasses[i] = Class.forName(interfaces[i], false, cl);
            }
            try {
                return Proxy.getProxyClass(cl, interfaceClasses);
            } catch (IllegalArgumentException e) {
                return super.resolveProxyClass(interfaces);
            }
        }
    };
    Object obj = ois.readObject();
    return obj;
}