我正在尝试从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结构是:
第三方库捆绑在lib文件夹中,我的项目的类位于com文件夹中。
我正在java.lang.ClassNotFoundException
加载主类,但此类存在于com文件夹中。如果我将我的项目类打包为jar并放在lib中,那么它就可以了。但是我想在lib中只有第三方罐子。
我想知道是否加载了sun.net.www.protocol
包的所有java处理程序,为什么我收到此错误?
同样,Init
类与我的主类在同一个包中,但Init
由ClassLoader
加载,但主类不是。{1}}。任何建议都会非常有用。
答案 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)