[英]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: 为实现这一目标,我正在做以下事情:
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
发送到服务器端。
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();
'.'
'.'
instead of '/'
in the name when you are loading the class at server side. '/'
。 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. 我认为,另一个错误意味着您将返回不同于所请求的类的定义。
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);
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. 这是一个简单的示例,您可以通过它重新创建错误。
com.basaki.model.Book
class file is saved as a Base64 encoded string. com.basaki.model.Book
类文件保存为Base64编码的字符串。 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.