无法加载不在超级罐的罐子里的类

时间:2014-12-11 09:10:35

标签: java jar classloader

我正在尝试从jar(uber jar)中的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

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

我正在java.lang.ClassNotFoundException加载主类,但此类存在于com文件夹中。如果我将我的项目类打包为jar并放在lib中,那么它就可以了。但是我想在lib中只有第三方罐子。

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

同样,Init类与我的主类在同一个包中,但InitClassLoader加载,但主类不是。{1}}。任何建议都会非常有用。

2 个答案:

答案 0 :(得分:1)

您尝试使用JarInJarClassLoader UrlClassloader加载主要课程 但您的类路径不在您传递给装载程序的URL数组中 这就是所有罐子都可以加载的原因,但不是你的主类,如果主类不在罐子里面
您的 Init 类可以加载,因为它是由当前线程类加载器或其父级加载的。

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

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());

答案 1 :(得分:0)

answer发布的Joachim给了我一个提示,将uber jar本身添加到ClassLoader(添加/ com /文件夹或{{1}的相对路径} class不起作用)。所以我修改了他的选择-1如下:

Launcher

它有效:)。