简体   繁体   中英

Load classes from jar file in sub-folder in zip file

I have a zip file with the following structure

plugin
   data
      abc.jar
      xyz.jar

I want to create a URLClassLoder instance using these jars. One approach is to uzip this file and then use that file system paths to create the URLClassLoaders. Can I do the same in memory?

Ok, you can check this answer https://stackoverflow.com/a/2614154/90313

and this library http://truezip.java.net/

Together you can use them to implement the required functionality.

The following is working solution. You can modify as per your requirement.

Reference for zip unpacking : recursively unzip archive in memory

Reference for classloading : Java: How to load Class stored as byte[] into the JVM?

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public final class ZippedJarClassReader {

    private final static String JAR_EXTENSION = ".jar";

    public static Map<String, byte[]> loadZip(final File file) throws IOException {
        final byte[]          bytes = Files.readAllBytes(file.toPath());
        final ByteArrayOutputStream baos  = new ByteArrayOutputStream();
        baos.write(bytes);
        return unzip(baos);
    }

    public static final Map<String, byte[]> unzip(final ByteArrayOutputStream baos) {
        final Map<String, byte[]> result = new HashMap<String, byte[]>();
        try(final ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
            ZipEntry       entry;
            while ((entry = in.getNextEntry()) != null) {
                final ByteArrayOutputStream os = new ByteArrayOutputStream();
                if (!entry.isDirectory()) {
                    os.write(in.readAllBytes());
                    if (entry.getName().toLowerCase().endsWith(JAR_EXTENSION)) {
                        result.putAll(unzip(os));
                    } else if (entry.getName().toLowerCase().endsWith(".class")) {
                        result.put(entry.getName().replaceAll("/", ".").substring(0, entry.getName().length() - 6), os.toByteArray());
                    }
                }
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;

public final class ZipClassLoader extends URLClassLoader {

    private final Map<String, byte[]> classes;

    public ZipClassLoader(URL[] urls, final File zipFile) throws IOException {
        super(urls);
        classes = ZippedJarClassReader.loadZip(zipFile);
    }

    @Override
    public final Class<?> findClass(String name) throws ClassNotFoundException {
        final byte[] bytes = classes.get(name);
        if(bytes != null) return defineClass(name, bytes, 0, bytes.length);
        return super.findClass(name);
    }
}

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.zip.ZipException;

public class Test {

    @SuppressWarnings("resource")
    public static void main(String[] args) throws ZipException, IOException, ClassNotFoundException {
        final File zipFile = new File("zipfile.zip");
        final URLClassLoader loader = new ZipClassLoader(new URL[] {}, zipFile);
        final Class<?> classz = loader.loadClass("org.util.nanolog.Test");
        System.out.println(classz);
    }
}

Okay, jar: URI scheme does not seem to work, but nio does. Here's the example which loads Jetifier (Android build tool) without unzipping everything.

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystem;
import java.security.SecureClassLoader;

import static java.nio.file.FileSystems.newFileSystem;
import static java.nio.file.Files.list;
import static java.nio.file.Files.readAllBytes;
import static java.nio.file.Paths.get;

class Scratch {
public static void main(String[] args) throws Exception {
    String applicationZip = "/home/mike/Downloads/jetifier-standalone.zip";
    String pathToJars = "/jetifier-standalone/lib/";
    String mainClassName = "com.android.tools.build.jetifier.standalone.Main";

    FileSystem zip = newFileSystem(get(applicationZip));
    FileSystem[] jars = list(zip.getPath(pathToJars))
            .map(jar -> {
                try { return newFileSystem(jar); }
                catch (IOException e) { throw new UncheckedIOException(e); }
            })
            .toArray(FileSystem[]::new);

    new SecureClassLoader(Scratch.class.getClassLoader()) {
        @Override protected Class<?> findClass(String name)
                throws ClassNotFoundException {
            String fileName = '/' + name.replace('.', '/') + ".class";
            for (FileSystem jar : jars) {
                try {
                    byte[] bytes = readAllBytes(jar.getPath(fileName));
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException ignored) {
                }
            }
            throw new ClassNotFoundException(name);
        }
    }.loadClass(mainClassName)
            .getMethod("main", String[].class)
            .invoke(null, new Object[] { new String[0] });
}
}

This works and says hello from Jetifier successfully. I use JDK 13.

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