简体   繁体   中英

Issue with loading class from jar file represented as byte array

I'm trying to create an instance of a class from jar file loaded on a byte array.

I'm receiving two args:
1. byte[] which represents jar file with required class
2. Qualified class name

When I'm testing it locally it works as expected, but when I upload exactly the same jar file with the same qualified class name remotely (using web application implemented with Spring MVC for back and AngularJS for front end deployed in Tomcat server) It can't find the required class:

java.lang.ClassNotFoundException

When I was debugging, it turned out, that classloader is properly invoked but no one class is loaded from jar.

I would be grateful if anyone can tell what can be the reason of that difference or how can I implement this functionality in other ways.


A method which loads class and returns an instance of it:

public static <T> T getInstanceOfLoadedClass(byte[] jarFileBytes, String qualifiedClassName) throws ClassFromJarInstantiationException {
    LOGGER.info("Getting instance of class from loaded jar file. Class name: " + qualifiedClassName);
    try {
        return (T) Class.forName(qualifiedClassName, true, new ByteClassLoader(jarFileBytes)).newInstance();
    } catch (InstantiationException | IllegalAccessException | IOException | ClassNotFoundException | NoSuchFieldException e) {
        LOGGER.error("Exception was thrown while reading jar file for " + qualifiedClassName + "class.", e);
        throw new ClassFromJarInstantiationException(e);
    }
}

Custom ByteClassLoader:

public class ByteClassLoader extends ClassLoader {
    private static final Logger LOGGER = Logger.getLogger(ByteClassLoader.class);
    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) {
            LOGGER.error("ByteClassLoader threw exception while reading jar byte stream for resource: "+resourceName, e);
            e.printStackTrace();
        } finally {
            if (zipInputStream != null && !found) {
                try {
                    zipInputStream.close();
                } catch (IOException e) {
                    LOGGER.error("ByteClassLoader threw exception while closing jar byte stream for resource: "+resourceName, e);
                    e.printStackTrace();
                }
            }
        }
        return null;
    } }

The problem was that the class required to be loaded was in a range of classloader while it was tested.
Hope it helps someone in solving this problem because it is really easy to miss.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM