我正在从客户端向服务器端发送一个Class对象。每次服务器需要加载客户端发送的Class对象,而不是通过父委派模型重用它(在第一次迭代期间加载时)。
我正在尝试在服务器端使用自定义类加载器,loadClass(String)
只调用findClass()
而不是使用父层次结构进行检查。
为实现这一目标,我正在做以下事情:
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();
我正在向服务器端发送classBinaryData
。
byte[]
时,通过匹配MD5校验和来验证它是否与客户端相同,然后我创建自定义类加载器的新实例并传递字节数组它可用于从defineClass
内调用findClass
。但是,我得到了其中一个错误(取决于我从.class创建byte []的方式)
Incompatible magic value ..... in class file <Unknown>
OR
com/example/XYZ (wrong name: com/example/XYZ)
来自defineClass
我需要帮助来弄清楚我的方法/代码中的错误。
答案 0 :(得分:3)
你的byte []生成代码看起来很好。
当我使用从代码生成的字节数组加载具有以下类加载器代码的类时,它能够成功加载类。
class CustomClassLoader extends ClassLoader {
public Class loadTheClass(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length);
}
}
像这样使用这个类加载器
CustomClassLoader ccl = new CustomClassLoader();
Class cz = ccl.loadTheClass("com.example.XYZ", classBinaryData);
Object o = cz.newInstance();
'.'
而不是'/'
。答案 1 :(得分:1)
您的代码看起来很好。你的错误在其他地方。
在某种程度上,您将从类加载器返回错误的类文件。
第一个错误意味着字节数组完全乱码;前4个字节是错误的。您可以轻松地检查它们(它们必须是0xCAFEBABE),以便更早地捕获此错误。
我认为,另一个错误意味着您正在返回不同于所请求的类的定义。
答案 2 :(得分:1)
com / example / XYZ(错误名称:com / example / XYZ)来自defineClass
您应该使用点表示法,即 com.example.XYZ
Class clazz = classLoader.loadCustomClass("com.example.XYZ", bytes);
类文件中不兼容的魔法值
您收到上述错误,因为类字节数组的开头已损坏。它通过抛出 java.lang.ClassFormatError 来抱怨不兼容的魔法值。当类加载器在类字节的开头没有找到 0xCAFEBABE (幻数)时,通常会发生这种情况。
这是一个简单的例子,您可以通过它重新创建错误。
com.basaki.model.Book
类文件保存为Base64编码的字符串。testLoadingClassWithCorrectMagicNumber
尝试在将其解码为字节数组后从Base64编码的字符串加载该类。它正常加载而没有任何事故。在方法testLoadingClassWithIncorrectCorrectMagicNumber
中,通过将第一个字符从c
替换为b
,字节数组(在解码Base64字符串之后)已损坏。现在,而不是幻数 0xCAFEBABE ,它是 0xBAFEBABE 。类加载器现在在尝试加载损坏的二进制数组时抛出以下异常
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-AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMY29tL2Jhc2FraS9tb2RlbC9Cb29rOwEACGdldFRpdGxlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhzZXRUaXRsZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEACWdldEF1dGhvcgEACXNldEF1dGhvcgEAClNvdXJjZUZpbGUBAAlCb29rLmphdmEMAAkACgwABgAHDAAIAAcBABVjb20vYmFzYWtpL21vZGVsL0Jvb2sBABBqYXZhL2xhbmcvT2JqZWN0ACEABAAFAAAAAgACAAYABwAAAAIACAAHAAAABQABAAkACgABAAsAAAAvAAEAAQAAAAUqtwABsQAAAAIADAAAAAYAAQAAAAMADQAAAAwAAQAAAAUADgAPAAAAAQAQABEAAQALAAAALwABAAEAAAAFKrQAArAAAAACAAwAAAAGAAEAAAAJAA0AAAAMAAEAAAAFAA4ADwAAAAEAEgATAAEACwAAAD4AAgACAAAABiortQACsQAAAAIADAAAAAoAAgAAAA0ABQAOAA0AAAAWAAIAAAAGAA4ADwAAAAAABgAGAAcAAQABABQAEQABAAsAAAAvAAEAAQAAAAUqtAADsAAAAAIADAAAAAYAAQAAABEADQAAAAwAAQAAAAUADgAPAAAAAQAVABMAAQALAAAAPgACAAIAAAAGKiu1AAOxAAAAAgAMAAAACgACAAAAFQAFABYADQAAABYAAgAAAAYADgAPAAAAAAAGAAgABwABAAEAFgAAAAIAFw==";
@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);
}
}
答案 3 :(得分:0)
如上所述,除了收集字节数组之外,问题似乎还在其他地方。有可能在服务器端没有正确处理字节。我创建了一个非常简单的示例,它与您正在执行的操作类似,但它显示了我如何发送和接收类字节数组。
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();
}
}
}
该类将读取字节数组并将其加载到代码的服务器端。请注意,我使用一个简单的协议来确定通过线路发送的字节数,并确保我已读取正确的字节数。
以下是将通过网络发送信息的客户端代码。它是你上面提到的扩展。
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) {}
}
}
}
}
如您所见,它只是向服务器发送一些信息,让它知道预计会传输多少数据。以下类可与上述类一起使用,以显示其工作原理。
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) {}
}
}
}
}
以下是客户端类
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();
}
}
}
此类将在服务器端运行。
package org.valhalla.client;
public class Main {
public static void main(String args[]) {
Client client = new Client();
client.execute();
}
}
这个班。
package org.valhalla.client;
public class Client {
public void execute() {
System.out.println("######### We are calling the Client class execute method #####");
}
}
希望这有帮助。