反序列化 动态加载jar的里的类报ClassNotFoundException解决办法

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值