尝试在运行时从外部JAR加载类时发生ClassNotFoundException [英] ClassNotFoundException while trying to load class from external JAR at runtime

查看:137
本文介绍了尝试在运行时从外部JAR加载类时发生ClassNotFoundException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在运行时从JAR加载一个表示为byte []数组的类.
我知道要加载的类有两件事:

1.它实现"RequiredInterface"
2.我知道它的限定名称:"sample.TestJarLoadingClass"

我找到了解决方案在其中我必须扩展ClassLoader但它会抛出:

I'm trying to load a class from JAR represented as a byte[] array at runtime.
I know two things about class to load:

1. it implements "RequiredInterface"
2. I know it's qualified name: "sample.TestJarLoadingClass"

I found the solution in which I have to extend ClassLoader but it throws:

Exception in thread "main" java.lang.ClassNotFoundException: sample.TestJarLoadingClass
    at java.lang.ClassLoader.findClass(ClassLoader.java:530)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at tasks.Main.main(Main.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

每当我要加载类时.

造成这种情况的原因是什么?如何消除这种情况?
任何帮助,高度赞赏

whenever I want to load the class.

What can be the reason of this situation and how can I get rid of that?
Any help highly appreciated

主要方法:

public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
    Path path = Paths.get("src/main/java/tasks/sample.jar");
    RequiredInterface requiredInterface = (RequiredInterface) Class.forName("sample.TestJarLoadingClass", true, new ByteClassLoader(Files.readAllBytes(path))).newInstance();
}

自定义类加载器:

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipInputStream;

    public class ByteClassLoader extends ClassLoader {
        private final byte[] jarBytes;
        private final Set<String> names;

        public ByteClassLoader(byte[] jarBytes) throws IOException {
            this.jarBytes = jarBytes;
            this.names = loadNames(jarBytes);
        }

        private Set<String> loadNames(byte[] jarBytes) throws IOException {
            Set<String> set = new HashSet<>();
            try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
                ZipEntry entry;
                while ((entry = jis.getNextEntry()) != null) {
                    set.add(entry.getName());
                }
            }
            return Collections.unmodifiableSet(set);
        }

        @Override
        public InputStream getResourceAsStream(String resourceName) {
            if (!names.contains(resourceName)) {
                return null;
            }
            boolean found = false;
            ZipInputStream zipInputStream = null;
            try {
                zipInputStream = new ZipInputStream(new ByteArrayInputStream(jarBytes));
                ZipEntry entry;
                while ((entry = zipInputStream.getNextEntry()) != null) {
                    if (entry.getName().equals(resourceName)) {
                        found = true;
                        return zipInputStream;
                    }
                }
            } catch (IOException e) {;
                e.printStackTrace();
            } finally {
                if (zipInputStream != null && !found) {
                    try {
                        zipInputStream.close();
                    } catch (IOException e) {;
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }
}

RequiredInterface:

RequiredInterface:

public interface RequiredInterface {
    String method();
}

JAR文件中的类:

package sample;
public class TestJarLoadingClass implements RequiredInterface {
    @Override
    public String method() {
        return "works!";
    }
}

推荐答案

在我看来,这里有两个问题:

In my opinion we have two problems here:

首先,您应该覆盖包含加载类实际逻辑的findClass方法.这里的主要挑战是找到包含您的类的字节数组的一部分-由于您将整个jar都作为字节数组,因此您将需要使用JarInputStream扫描类的字节数组.

First of all you should override findClass method that contains actual logic of loading class. The main challenge here is to find part of byte array that contains your class - since you have whole jar as a byte array, you will need to use JarInputStream to scan your byte array for your class.

但这可能还不够,因为ByteClassLoader对于您的ByteClassLoader是未知的-因此您将能够读取类本身,但是类的定义包含其实现RequiredInterface的信息,这对于您的类加载器.这个很容易修复,您只需要将常规的类加载器作为构造函数参数传递给您的那个,并使用super(parentClassLoader).

But this might not be enough because your RequiredInterface is unknown for your ByteClassLoader - so you will be able to read class itself, but the definition of class contains information that it implements RequiredInterface which is a problem for your class loader. This one is easy to fix, you just need to pass regular class loader as a constructor parameter to your one and use super(parentClassLoader).

这是我的版本:

public class ByteClassLoader extends ClassLoader {
    private final byte[] jarBytes;

    public ByteClassLoader(ClassLoader parent, byte[] jarBytes) throws IOException {
        super(parent);
        this.jarBytes = jarBytes;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        // read byte array with JarInputStream
        try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes))) {
            JarEntry nextJarEntry;

            // finding JarEntry will "move" JarInputStream at the begining of entry, so no need to create new input stream
            while ((nextJarEntry = jis.getNextJarEntry()) != null) {

                if (entryNameEqualsClassName(name, nextJarEntry)) {

                    // we need to know length of class to know how many bytes we should read
                    int classSize = (int) nextJarEntry.getSize();

                    // our buffer for class bytes
                    byte[] nextClass = new byte[classSize];

                    // actual reading
                    jis.read(nextClass, 0, classSize);

                    // create class from bytes
                    return defineClass(name, nextClass, 0, classSize, null);
                }
            }
            throw new ClassNotFoundException(String.format("Cannot find %s class", name));
        } catch (IOException e) {
            throw new ClassNotFoundException("Cannot read from jar input stream", e);
        }
    }


    private boolean entryNameEqualsClassName(String name, JarEntry nextJarEntry) {

        // removing .class suffix
        String entryName = nextJarEntry.getName().split("\\.")[0];

        // "convert" fully qualified name into path
        String className = name.replace(".", "/");

        return entryName.equals(className);
    }
}

和用法

    RequiredInterface requiredInterface = (RequiredInterface)Class.forName("com.sample.TestJarLoadingClass", true, new ByteClassLoader(ByteClassLoader.class.getClassLoader(), Files.readAllBytes(path))).newInstance();
    System.out.println(requiredInterface.method());

请注意,我的实现假定文件名=类名,因此,例如,将找不到非顶级类.当然,某些细节可能会更完善(例如异常处理).

Please be aware that my implementation assumes that file name = class name, so for example classes that are not top level will not be found. And of course some details might be more polished (like exception handling).

这篇关于尝试在运行时从外部JAR加载类时发生ClassNotFoundException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆