自己在写一个RPC框架时,碰到第一个麻烦就是做动态加载加载jar包后,在进行反序列化(不要吐槽为啥用java原生的序列化方案,一步一步来,框架写完能跑后在优化)时报CNF错误,当时感觉应该是原生的序列化方案中使用的ClassLoader是应用加载器AppCloassLoader,而我使用的URLClassLoader加载的外部jar包,导致没有找到。
关于java的类加载器,如果知道双亲委托机制就可以往下接着看了,如果不了解,我帮大家找了一篇。
双亲委托机制总的来说就是,如果一个classloader要加载一个类,先问问他的父级classloader没有加载这个类,父级也会在去问它的父级,直至bootstrap classloader,如果其父级加载器无法加载该类,才会交由它本身加载。
java反序列化代码一般为下面这样
public static Object deserialize(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
log.error("反序列化错误", e);
}
return null;
}
查看ObjectInputStream源码找到了类加载的地方
protected Class> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException
{
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class> cl = primClasses.get(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}
可以看到Class.forName中使用的是AppClassLoader,解决方案其实就是复写该方法,使用我们动态加载的ClassLoader
public class ObjectInputStreamWithLoader extends ObjectInputStream {
private ClassLoader loader;
public ObjectInputStreamWithLoader(InputStream in, ClassLoader loader)
throws IOException, StreamCorruptedException {
super(in);
if (loader == null) {
throw new IllegalArgumentException("Illegal null argument to ObjectInputStreamWithLoader");
}
this.loader = loader;
}
/**
* Use the given ClassLoader rather than using the system class
*/
@SuppressWarnings("rawtypes")
protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
String cname = classDesc.getName();
return loader.loadClass(cname);
}
}
可以看到在实例化该类的时候,需传入一个classloader
动态加载我使用的是URLClassLoader,代码如下
URL[] urls = new URL[2];
urls[0] = new URL("file:///D:/jar/a.jar");
urls[1] = new URL("file:///D:/jar/a.impl.jar");
URLClassLoader classLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
public static byte[] serialize(Object obj) throws IOException {
if (obj == null)
throw new NullPointerException();
ByteArrayOutputStream os = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(os);
ho.writeObject(obj);
return os.toByteArray();
}
public static Object deserialize(byte[] by, ClassLoader classLoader) throws IOException {
if (by == null)
throw new NullPointerException();
ByteArrayInputStream is = new ByteArrayInputStream(by);
ClassLoader old = null;
if (classLoader != null) {
old = Thread.currentThread().getContextClassLoader();
// 切换当前线程classloader,保证动态加载的类不会报CNF
Thread.currentThread().setContextClassLoader(classLoader);
}
HessianInput hi = new HessianInput(is);
Object obj = hi.readObject();
if (classLoader != null) {
Thread.currentThread().setContextClassLoader(old);
}
return obj;
}
在反序列时,修改其当前线程上下文类加载器,保证能加载到该类。
最近打算从头开始写一个RPC框架,欢迎有兴趣的同学一块交流
https://github.com/yangzhenkun/krpc