java - 无法加载 uber jar 中没有的类

标签 java jar classloader

我正在尝试从 jar in jar (uber jar) 中加载类,并且我遵循了从 org.eclipse.jdt.internal.jarinjarloader 加载此类类的方式。 。我检查了其他资源和SO thread我想出了以下 URLStreamHandlerFactory 实现(主要是 JBoss's implementation )。

import java.lang.reflect.Constructor;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

public class JarInJarURLStreamHandlerFactory implements URLStreamHandlerFactory {

    private ClassLoader classLoader;

    public JarInJarURLStreamHandlerFactory(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    private static final String PACKAGE_PREFIX = "com.mycompany.project.classloader";
    private static final String PROTOCOL_PATH_PROPERTIES = "java.protocol.handler.pkgs";
    private static final String JDK_PACKAGE_PREFIX =  "sun.net.www.protocol";   

    private static Map<String, URLStreamHandler> handlerMap = Collections.synchronizedMap(new HashMap<String, URLStreamHandler>());
    private static ThreadLocal<String> createURLStreamHandlerProtocol = new ThreadLocal<String>();

    private String lastHandlerPackages = PACKAGE_PREFIX;
    private String[] handlerPackages = { PACKAGE_PREFIX, JDK_PACKAGE_PREFIX };      


    public URLStreamHandler createURLStreamHandler(final String protocol) {     
        URLStreamHandler handler = handlerMap.get(protocol);

        if (handler != null) {
            return handler;
        }

        String prevProtocol = createURLStreamHandlerProtocol.get();

        if (prevProtocol != null && prevProtocol.equals(protocol)) {
            return null;
        }

        createURLStreamHandlerProtocol.set(protocol);
        checkHandlerPackages();

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

        for (int p = 0; p < handlerPackages.length; p++) {
            try {
                String classname = handlerPackages[p] + "." + protocol + ".Handler";
                Class<?> type = null;

                try {
                    type = contextClassLoader.loadClass(classname);
                } catch (ClassNotFoundException ignore1) {
                    try {
                        type = Class.forName(classname);
                    } catch (Exception ignore2) {
                    }                   
                }

                if (type != null) {
                    if (handlerPackages[p].equals(PACKAGE_PREFIX)) {
                        Constructor<?> constructor = type.getConstructor(ClassLoader.class);
                        handler = (URLStreamHandler) constructor.newInstance(classLoader);
                    } else {
                        handler = (URLStreamHandler) type.newInstance();
                    }

                    handlerMap.put(protocol, handler);

                }
            } catch (Throwable ignore) {
            }
        }

        createURLStreamHandlerProtocol.set(null);
        return handler;
    }

    private synchronized void checkHandlerPackages() {
        String packagePrefixList = AccessController.doPrivileged(new PrivilegedAction<String>() {

            @Override
            public String run() {
                return System.getProperty(PROTOCOL_PATH_PROPERTIES);
            }
        });

        if (packagePrefixList != null && !packagePrefixList.equals(lastHandlerPackages)) {
            StringTokenizer tokeninzer = new StringTokenizer(packagePrefixList, "|");
            Set<String> packageList = new HashSet<String>();

            while (tokeninzer.hasMoreTokens()) {
                String pkg = tokeninzer.nextToken().intern();
                packageList.add(pkg);

            }

            if (!packageList.contains(PACKAGE_PREFIX)) {
                packageList.add(PACKAGE_PREFIX);
            }

            handlerPackages = new String[packageList.size()];
            packageList.toArray(handlerPackages);
            lastHandlerPackages = packagePrefixList;
        }
    }
}

这是Handler类:

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

import com.mycompany.project.classloader.connection.JarInJarURLConnection;
import com.mycompany.project.classloader.constants.JarInJarConstants;

public class Handler extends URLStreamHandler {

    private ClassLoader classLoader;

    public Handler(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        return new JarInJarURLConnection(url, classLoader);
    }

    @Override
    protected void parseURL(URL url, String spec, int start, int limit) {       
        if (spec.startsWith(JarInJarConstants.INTERNAL_URL_PROTOCOL_WITH_COLON))  {
            String file = spec.substring(JarInJarConstants.INTERNAL_URL_PROTOCOL_WITH_COLON.length());
            setURL(url, JarInJarConstants.INTERNAL_URL_PROTOCOL, "", -1, null, null, file, null, null);
            return;
        }

        super.parseURL(url, spec, start, limit);
    }
}

我已经修改了Handler来调用super.parseURL,如上面提到的SO线程中所述。

URLConnection 类的实现是:

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;

import com.mycompany.project.classloader.constants.JarInJarConstants;

public class JarInJarURLConnection extends URLConnection {

    private ClassLoader classLoader;

    public JarInJarURLConnection(URL url, ClassLoader classLoader) {
        super(url);
        this.classLoader = classLoader;
    }

    @Override
    public void connect() throws IOException {

    };

    @Override
    public InputStream getInputStream() throws IOException {
        String fileName = URLDecoder.decode(getURL().getFile(), JarInJarConstants.UTF8_ENCODING);
        InputStream stream = classLoader.getResourceAsStream(fileName);
        return stream;
    }
}

常量是:

public final class JarInJarConstants {

    private JarInJarConstants() {

    }

    public static final String LIBRARY_NAME = "Lib";
    public static final String LAUNCHER_CLASS = "Launcher-Class";
    public static final String MAIN_METHOD_NAME = "main";
    public static final String PATH_TO_MANIFEST = "META-INF/MANIFEST.MF";
    public static final String JAR_WITH_COLON = "jar:";
    public static final String JAR_EXTENSION = "jar";
    public static final String INTERNAL_URL_PROTOCOL_WITH_COLON = "jarinjar:";
    public static final String INTERNAL_URL_PROTOCOL = "jarinjar";
    public static final String PATH_SEPARATOR = "/";
    public static final String EXCLAMATION = "!";
    public static final String UTF8_ENCODING = "UTF-8";
}

Init 类是:

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import com.mycompany.project.classloader.JarInJarClassLoader;
import com.mycompany.project.classloader.constants.JarInJarConstants;
import com.mycompany.project.classloader.factory.JarInJarURLStreamHandlerFactory;

public class Init {

    public static void main(String[] args) throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();    
        ManifestEntry manifestEntry = getManifestEntry(contextClassLoader);;
        String jarDirectoryName = manifestEntry.jarDirectoryName;
        String launcherClassName = manifestEntry.launcherClassName;

        List<String> jarFiles = getJarFiles(contextClassLoader, jarDirectoryName);

        ClassLoader parentClassLoader = Init.class.getClassLoader();
        URLStreamHandlerFactory urlStreamHandlerFactory = new JarInJarURLStreamHandlerFactory(parentClassLoader);
        URL.setURLStreamHandlerFactory(urlStreamHandlerFactory);

        URL[] urls = new URL[jarFiles.size()];
        int idx = 0;

        for(String jarFile : jarFiles) {
            urls[idx++] = new URL(JarInJarConstants.INTERNAL_URL_PROTOCOL_WITH_COLON + jarDirectoryName + JarInJarConstants.PATH_SEPARATOR + jarFile);
        }               

        ClassLoader jarInJarClassLoader = new JarInJarClassLoader(urls, null, urlStreamHandlerFactory);
        Thread.currentThread().setContextClassLoader(jarInJarClassLoader);

        Class<?> clazz = Class.forName(launcherClassName, true, jarInJarClassLoader);
        Method main = clazz.getMethod(JarInJarConstants.MAIN_METHOD_NAME, new Class[]{args.getClass()});
        main.invoke(null, new Object[]{args});
    }

    private static ManifestEntry getManifestEntry(ClassLoader contextClassLoader) throws IOException {
        URL url = contextClassLoader.getResource(JarInJarConstants.PATH_TO_MANIFEST);
        InputStream stream = url.openStream();
        Manifest manifest = new Manifest(stream);
        Attributes mainAttributes = manifest.getMainAttributes();
        String jarDirectoryName = mainAttributes.getValue(JarInJarConstants.LIBRARY_NAME);
        String launcherClassName = mainAttributes.getValue(JarInJarConstants.LAUNCHER_CLASS);
        return new ManifestEntry(jarDirectoryName, launcherClassName);
    }

    private static List<String> getJarFiles(ClassLoader contextClassLoader, String jarDirectoryName) throws UnsupportedEncodingException, IOException {
        URL dirURL = contextClassLoader.getResource(jarDirectoryName);
        String jarPath = dirURL.getPath().substring(JarInJarConstants.JAR_WITH_COLON.length() + 1, dirURL.getPath().indexOf(JarInJarConstants.EXCLAMATION));
        JarFile jar = new JarFile(URLDecoder.decode(jarPath, JarInJarConstants.UTF8_ENCODING));
        Enumeration<JarEntry> entries = jar.entries();
        List<String> jarFiles = new ArrayList<String>();

        while (entries.hasMoreElements()) {
            String name = entries.nextElement().getName();

            if (name.startsWith(jarDirectoryName) & name.endsWith(JarInJarConstants.JAR_EXTENSION)) {
                jarFiles.add(name.substring((jarDirectoryName + JarInJarConstants.PATH_SEPARATOR).length()));
            }
        }

        return jarFiles;
    }

    private static class ManifestEntry {
        String jarDirectoryName;
        String launcherClassName;

        public ManifestEntry(String jarDirectoryName, String launcherClassName) {
            this.jarDirectoryName = jarDirectoryName;
            this.launcherClassName = launcherClassName;
        }               
    }
}

现在 jar 结构是:

enter image description here

第 3 方库捆绑在 lib 文件夹中,我的项目的类位于 com 文件夹中。

我在加载主类时遇到java.lang.ClassNotFoundException,但该类存在于 com 文件夹中。如果我将项目的类打包为 jar 并放置在 lib 中,那么它就可以工作。但我只想在库中包含第 3 方 jar。

我想知道我是否加载了 sun.net.www.protocol 包的所有 java 处理程序,为什么我会收到此错误?

此外,Init类与我的主类位于同一个包中,但Init是由ClassLoader加载的,但为什么主类类不是。任何建议都会非常有帮助。

最佳答案

您尝试使用 JarInJarClassLoader 加载主类,它是 UrlClassloader
但是您的类路径不在您传递给加载程序的 URL 数组内
这就是为什么可以加载所有 jar,但不能加载主类,如果主类不在 jar 内
您的 Init 类可以被加载,因为它是由当前线程类加载器或其父类加载的。

所以你有两个选择
将您的类文件夹添加到 URL 数组

URL[] urls = new URL[jarFiles.size() + 1];
int idx = 0;
for(String jarFile : jarFiles) {
    urls[idx++] = new URL(JarInJarConstants.INTERNAL_URL_PROTOCOL_WITH_COLON + jarDirectoryName + JarInJarConstants.PATH_SEPARATOR + jarFile);
}        
urls[urls.length-1] = new URL("file:///C:/Users/TapasB/<INSERT CORRECT PATH>/com/");

或者使用另一个类加载器来加载主类
例如用于加载 Init 类的类

Class<?> clazz = Class.forName(launcherClassName, true, Init.class.getClassLoader());

关于java - 无法加载 uber jar 中没有的类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27419022/

相关文章:

java - 无限列表中的搜索策略

java - 用于调用 Java 方法的 XSLT 处理器

java - 从文本文件到 arrayList

eclipse - 在 Eclipse 中,如何在 Maven 管理的依赖项中搜索类?

java - 确保从非 spring 上下文加载 spring bean

java - tomcat如何加载一个jar文件?

java - 正则表达式模式语法异常

java - 如何将 MANIFEST.MF 注入(inject) jar

java - 在 Intellij 13 上,创建的可执行 jar 无效或损坏

java - 从所有 JVM 获取所有 Java 类位置的列表?