反序列化 動態載入jar的裡的類報ClassNotFoundException解決辦法
阿新 • • 發佈:2019-02-20
1.背景
自己在寫一個RPC框架時,碰到第一個麻煩就是做動態載入載入jar包後,在進行反序列化(不要吐槽為啥用java原生的序列化方案,一步一步來,框架寫完能跑後在優化)時報CNF錯誤,當時感覺應該是原生的序列化方案中使用的ClassLoader是應用載入器AppCloassLoader,而我使用的URLClassLoader載入的外部jar包,導致沒有找到。
關於java的類載入器,如果知道雙親委託機制就可以往下接著看了,如果不瞭解,我幫大家找了一篇。
雙親委託機制總的來說就是,如果一個classloader要載入一個類,先問問他的父級classloader沒有載入這個類,父級也會在去問它的父級,直至bootstrap classloader,如果其父級載入器無法載入該類,才會交由它本身載入。
2.解決方案
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());
3.利用Hessian序列化
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