简体   繁体   English

Java自定义类加载器问题

[英]Java custom class loader issue

I'm sending a Class object from client to server side. 我正在从客户端向服务器端发送一个Class对象。 Every time the server needs to load the Class object sent by the client instead of reusing it by parent delegation model (when it was loaded during the 1st iteration). 每次服务器需要加载客户端发送的Class对象, 而不是通过父委托模型重用它(当它在第一次迭代期间加载时)。

I'm trying to use a custom class loader on the server side whose loadClass(String) simply calls findClass() instead of checking with parent hierarchy. 我正在尝试在服务器端使用自定义类加载器,其loadClass(String)只调用findClass()而不是使用父层次结构进行检查。 To achieve this, I'm doing following: 为实现这一目标,我正在做以下事情:

  1. Generate byte[] by reading the .class file on the client side as following: 通过读取客户端上的.class文件生成byte [],如下所示:
 Class cl = com.example.XYZ.class; String path = cl.getName().replace('.', '/') + ".class"; InputStream is = cl.getClassLoader().getResourceAsStream(path); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int data = -1; while((data=is.read())!=-1) bos.write(data); byte[] classBinaryData = bos.toByteArray(); 

I'm sending classBinaryData to the server side. 我正在将classBinaryData发送到服务器端。

  1. On the server side, every time I retrieve the byte[] , verify if it's the same as on client side by matching MD5 checksum, then I create a new instance of my custom class loader and pass the byte array so it could be used in calling defineClass from within findClass . 在服务器端,每次检索byte[] ,通过匹配MD5校验和来验证它是否与客户端相同,然后我创建自定义类加载器的新实例并传递字节数组,以便它可以用于从findClass调用defineClass

However, I'm getting either of the errors (depending on the way I create byte[] out of .class) 但是,我得到了任何一个错误(取决于我从.class创建byte []的方式)

Incompatible magic value ..... in class file <Unknown>

OR 要么

com/example/XYZ (wrong name: com/example/XYZ) coming from defineClass 来自defineClass com/example/XYZ (wrong name: com/example/XYZ)

I need help in figuring out the mistake in my approach/code. 我需要帮助来弄清楚我的方法/代码中的错误。

Your byte[] generation code looks fine. 你的byte []生成代码看起来很好。

When I used the byte array generated form your code to load the class with following class loader code, it was able to load the class successfully. 当我使用从代码生成的字节数组加载具有以下类加载器代码的类时,它能够成功加载类。

class CustomClassLoader extends ClassLoader {

    public Class loadTheClass(String name, byte[] bytes) {

        return defineClass(name, bytes, 0, bytes.length);
    }
}

Using this classloader like this 像这样使用这个类加载器

CustomClassLoader ccl = new CustomClassLoader();
        Class cz = ccl.loadTheClass("com.example.XYZ", classBinaryData);
        Object o = cz.newInstance();
  1. I think you must use '.' 我认为你必须使用'.' instead of '/' in the name when you are loading the class at server side. 当您在服务器端加载类时,而不是名称中的'/'
  2. And ensure that the byte array data is not changed in your other code. 并确保您的其他代码中的字节数组数据不会更改。

Your code looks fine. 你的代码看起来很好。 Your error is somewhere else. 你的错误在别的地方。

You are, in some way, returning bad class files from your class loader. 在某种程度上,您从类加载器返回错误的类文件。

The first error means the byte array is totally garbled; 第一个错误意味着字节数组完全乱码; the first 4 bytes are wrong. 前4个字节是错误的。 You can check them easily (they have to be 0xCAFEBABE), to catch this error earlier. 您可以轻松地检查它们(它们必须是0xCAFEBABE),以便更早地捕获此错误。

The other error, I think, means that you are returning the definition of a different class than was requested. 我认为,另一个错误意味着您将返回不同于所请求的类的定义。

1. Missing Dot Notation 1.缺少点符号

com/example/XYZ (wrong name: com/example/XYZ) coming from defineClass 来自defineClass的com / example / XYZ(错误名称:com / example / XYZ)

You should be using dot notation, ie, com.example.XYZ 您应该使用点表示法,即com.example.XYZ

Class clazz = classLoader.loadCustomClass("com.example.XYZ", bytes);

2. Invalid Magic Number (Corrupt Class Bytes) 2.无效的幻数(损坏的类字节)

Incompatible magic value ..... in class file 类文件中不兼容的魔法值.....

You are getting the above error because the start of the class byte array is corrupted . 您收到上述错误,因为类字节数组开头已损坏 It's complaining about Incompatible magic value by throwing a java.lang.ClassFormatError . 它通过抛出java.lang.ClassFormatError来抱怨不兼容的魔法值 It usually happens when the class loader doesn't find 0xCAFEBABE (magic number) at the beginning of the class bytes. 当类加载器在类字节的开头没有找到0xCAFEBABE (幻数)时,通常会发生这种情况。

Here is a simple example by which you can recreate the error. 这是一个简单的示例,您可以通过它重新创建错误。

  • In this example, the com.basaki.model.Book class file is saved as a Base64 encoded string. 在此示例中, com.basaki.model.Book类文件保存为Base64编码的字符串。
  • The method testLoadingClassWithCorrectMagicNumber tries to load the class from the Base64 encoded string after decoding it to a byte array. 方法testLoadingClassWithCorrectMagicNumber尝试在将其解码为字节数组后从Base64编码的字符串加载该类。 It loads normally without any incident. 它正常加载而没有任何事故。
  • In method testLoadingClassWithIncorrectCorrectMagicNumber , the byte array (after the Base64 string is decoded) is corrupted by replacing the first character from c to b . 在方法testLoadingClassWithIncorrectCorrectMagicNumber ,通过将第一个字符从c替换为b来破坏字节数组(在解码Base64字符串之后)。 Now instead of the magic number being 0xCAFEBABE , it is 0xBAFEBABE . 现在,而不是幻数是0xCAFEBABE ,它是0xBAFEBABE The class loader now throws the following exception while trying to load the corrupt binary array, 类加载器现在在尝试加载损坏的二进制数组时抛出以下异常,

    java.lang.ClassFormatError: Incompatible magic value 3137256126 in class file com/basaki/model/Book java.lang.ClassFormatError:类文件中的不兼容的魔法值3137256126 com / basaki / model / Book

     public class LoadingBookFromBinaryArrayTest { private static class MyCustomClassLoader extends ClassLoader { public Class loadCustomClass(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } } public static String BOOK_CLAZZ = "yv66vgAAADQAHQoABQAYCQAEABkJAAQAGgcAGwcAHAEABXRpdGxlAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGYXV0aG9yAQAGPGluaXQ-=="; @Test public void testLoadingClassWithCorrectMagicNumber() throws IllegalAccessException, InstantiationException, DecoderException { byte[] bytes = Base64.getUrlDecoder().decode(BOOK_CLAZZ); MyCustomClassLoader classLoader = new MyCustomClassLoader(); Class clazz = classLoader.loadCustomClass("com.basaki.model.Book", bytes); } @Test(expected = ClassFormatError.class) public void testLoadingClassWithIncorrectCorrectMagicNumber() throws IllegalAccessException, InstantiationException, DecoderException { byte[] bytes = Base64.getUrlDecoder().decode(BOOK_CLAZZ); String hex = Hex.encodeHexString(bytes); System.out.println(hex); // changing magic number 0xCAFEBABE to invalid 0xBAFEBABE String malHex = "b" + hex.substring(1, hex.length()); System.out.println(malHex); byte[] malBytes = Hex.decodeHex(malHex.toCharArray()); MyCustomClassLoader classLoader = new MyCustomClassLoader(); Class clazz = classLoader.loadCustomClass("com.basaki.model.Book", bytes9); } } 

As has been stated above the issue seems to be somewhere else besides the gathering of the byte array. 如上所述,除了收集字节数组之外,问题似乎还在其他地方。 It is possible that the bytes are not being processed properly on the server side. 有可能在服务器端没有正确处理字节。 I've created a fairly simple example that is similar to what you are doing but it shows how I send and receive the class byte array. 我创建了一个相当简单的示例,它与您正在执行的操作类似,但它显示了我如何发送和接收类字节数组。

package org.valhalla.classloader;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class RemoteClassLoader extends ClassLoader {

    private Socket socket;
    private DataOutputStream dos;
    private DataInputStream dis;

    public RemoteClassLoader(Socket socket, ClassLoader parent) {
        super(parent);
        this.socket = socket;
        OutputStream os;
        InputStream is;
        try {
            os = socket.getOutputStream();
            is = socket.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException("Unable to get Socket output stream", e);
        }
        dos = new DataOutputStream(os);
        dis = new DataInputStream(is);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clz = null;
        System.out.println("Looking up class: " + name);
        synchronized(this.getClassLoadingLock(name)) {
            try {
                System.out.println("Sending request for class: " + name);
                dos.writeUTF(name);
                boolean success = dis.readBoolean();
                System.out.println("Action was " + success);
                if (success) {
                    // Get bytes;
                    System.out.println("Reading size of class file");
                    int len = dis.readInt();
                    System.out.println("Size of class is " + len);
                    byte data[] = new byte[len];
                    int cur, size = 0;
                    for (cur = 0 ; cur < len ;  cur += size) {
                        size = dis.read(data, cur, len - cur);
                        System.out.println("Read size: " + size);
                    }
                    System.out.println("Completed reading class file for class " + name);
                    return defineClass(name, data, 0, len);
                }
            } catch (IOException e) {
                throw new ClassNotFoundException("Class: " + name + " was not found", e);
            }
        }
        return clz;
    }

    public void close() {
        try {
            if (socket != null && socket.isClosed() == false) {
                this.socket.close();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}

This class will read the byte array and load it within the server side of the code. 该类将读取字节数组并将其加载到代码的服务器端。 Note that I use a simple protocol to determine how many bytes are being sent over the wire and insure that I have read the correct amount of bytes. 请注意,我使用一个简单的协议来确定通过线路发送的字节数,并确保我已读取正确的字节数。

Here is the client side code that will send the information over the wire. 这是客户端代码,它将通过网络发送信息。 It is an extension of what you mentioned above. 它是你上面提到的扩展。

package org.valhalla.client;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.Socket;

public class ClientConnection {

    private Socket socket;

    public ClientConnection(Socket socket) {
        this.socket = socket;
    }

    public void process() {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            String name = null;
            while ((name = dis.readUTF()) != null && name.length() > 0) {
                System.out.println("Looking up class: " + name);
                InputStream resource = ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + ".class");
                if (resource == null) {
                    System.out.println("Class not found: " + name);
                    dos.writeBoolean(false);
                    continue;
                }
                System.out.println("Found class: " + name);
                try {
                    byte buf[] = new byte[1024];
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    int size = 0;
                    while ((size = resource.read(buf)) > 0) {
                        bos.write(buf, 0, size);
                    }
                    byte clz[] = bos.toByteArray();
                    dos.writeBoolean(true);
                    System.out.println("Sendding class size: " + clz.length);
                    dos.writeInt(clz.length);
                    System.out.println("Sending class bytes");
                    dos.write(clz);
                    System.out.println("Sent class bytes");
                } catch (Throwable t) {
                    t.printStackTrace();
                    dos.writeBoolean(false);
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            if (socket != null && socket.isClosed() == false) {
                try { socket.close(); } catch(Throwable t) {}
            }
        }
    }

}

As you see it just sends some information to the server that lets it know how much data is expected to be transferred. 如您所见,它只是向服务器发送一些信息,让它知道预计会传输多少数据。 The following classes can be used with the above classes to show how this works. 以下类可以与上面的类一起使用来说明它是如何工作的。

package org.valhalla.classloader;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;

public class RemoteClassLoaderServer {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("syntax error:  missing port");
            System.exit(1);
        }
        int port = 0;
        try {
            port = Integer.parseInt(args[0]);
        } catch(NumberFormatException nfe) {
            System.out.println("Invalid port number: " + args[1]);
            System.exit(2);
        }

        if (port < 0) {
            System.out.println("Port cannot be negative: " + port);
        }

        ServerSocket server = null;

        try {
            server = new ServerSocket(port);
        } catch (IOException e) {
            System.out.println("Unable to create server socket for port: " + port);
            System.exit(3);
        }

        Socket s = null;
        try {
            s = server.accept();
            InputStream is = s.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            System.out.println("Waiting for class name");
            String name = dis.readUTF();
            System.out.println("Received class name: " + name);
            RemoteClassLoader rcl = new RemoteClassLoader(s, RemoteClassLoaderServer.class.getClassLoader());
            System.out.println("Finding class: " + name);
            Class<?> clz = rcl.loadClass(name);
            Method m = clz.getMethod("main", String[].class);
            System.out.println("Executing main method");
            m.invoke(null, new Object[] { new String[0] });
            System.out.println("done");
            new DataOutputStream(s.getOutputStream()).writeUTF("");
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (s != null && s.isClosed() == false) {
                try { s.close(); } catch(Throwable t) {}
            }
        }
    }

}

Here are the client side classes 这是客户端类

package org.valhalla.client;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class ClientMain {

    public static void main(String[] args) {
        int port = Integer.parseInt(args[0]);
        try {
            Socket socket = new Socket("localhost", port);
            System.out.println("Opened socket at port: " + port);
            String name = Main.class.getName();
            new DataOutputStream(socket.getOutputStream()).writeUTF(name);
            System.out.println("Sent Class name: " + name);
            ClientConnection conn = new ClientConnection(socket);
            conn.process();
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

This class will be run within the server side. 该类将在服务器端运行。

package org.valhalla.client; 包org.valhalla.client;

public class Main {

    public static void main(String args[]) {
        Client client = new Client();
        client.execute();
    }

}

with this class. 与这堂课。

package org.valhalla.client; 包org.valhalla.client;

public class Client {

    public void execute() {
        System.out.println("######### We are calling the Client class execute method #####");
    }

}

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM