下面是代码
package TestPackage;
import com.ice.tar.TarArchive;
import com.ice.tar.TarEntry;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class mimeExceptionExample {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
File archive=new File("/tmp/java-11.0.9-linux-x649971467672330028230.tmp/java-11.0.9-linux-x64.tar");
System.out.println("---1--------");
URL jarURL = new URL("file:" + "C:\\Users\\Test\\Downloads\\javax.activation-1.2.0.jar");
URLClassLoader classLoader = new URLClassLoader(new URL[]{jarURL});
// Load a class from the JAR using the new class loader
try {
classLoader.loadClass("javax.activation.MimeTypeParseException");
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("javax.activation.MimeTypeParseException",true,classLoader);
TarArchive tar = new TarArchive(new FileOutputStream(archive));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("---2--------");
// Start the new thread
}
}
当我们运行上面的代码时,我们得到如下输出
---1--------
Exception in thread "main" java.lang.NoClassDefFoundError: javax/activation/MimeTypeParseException
at TestPackage.mimeExceptionExample.main(mimeExceptionExample.java:26)
Caused by: java.lang.ClassNotFoundException: javax.activation.MimeTypeParseException
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 1 more
我的要求是我不想直接在 Eclipse 的构建路径中添加 javax.activation-1.2.0.jar 。需要从代码中加载它。那么有人可以帮忙吗?
正如 Jorn 所说,在运行时设置类路径会更好。如果可能的话,你应该这样做。
但是你的问题似乎表明你不能,所以......
第一个问题是 URL 允许包含的字符集有限。 URL 不允许包含反斜杠字符。
因此,这一行是不正确的:
URL jarURL = new URL("file:" + "C:\\Users\\Test\\Downloads\\javax.activation-1.2.0.jar");
为什么没有抛出异常?因为java.net.URL是一个非常古老的类。它是 Java 1.0 的一部分。它没有对语法进行严格的检查。
他们为什么不修复它?因为在面向对象的设计中,您“永远”不会破坏使用公共类和公共类成员的现有代码。因此,Java 添加了一个新类,java.net.URI,它确实执行严格的语法检查。这段代码会抛出异常,正如它应该的那样:
URI uri = new URI("file:" + "C:\\Users\\Test\\Downloads\\javax.activation-1.2.0.jar");
将文件名转换为 URL 的正确方法是:
Path path = Path.of("C:\\Users\\Test\\Downloads\\javax.activation-1.2.0.jar");
assert Files.isReadable(path) : "File \"" + path + "\" does not exist!";
URL activationJarURL = path.toUri().toURL();
第二个问题是Java不使用线程的上下文类加载器。因此,调用
Thread.currentThread().setContextClassLoader(classLoader);
基本上没有任何作用。确实存在一些库和一些 Java SE API 使用上下文类加载器,但常规类加载不会使用它。
这确实有效:Class.forName("javax.activation.MimeTypeParseException", true, classLoader);
但是下一行根本没有使用新的类加载器:
// Error --- not using classLoader, so cannot find javax.activation package
TarArchive tar = new TarArchive(new FileOutputStream(archive));
您需要的是一个单独的类,可以通过
classLoader
加载,进而调用
new TarArchive
:public class ArchiveWriter
implements Runnable {
private final File archive;
public ArchiveWriter(File archive) {
this.archive = Objects.requireNonNull(archive,
"Archive file cannot be null.");
}
@Override
public void run() {
try {
TarArchive tar = new TarArchive(new FileOutputStream(archive));
// etc.
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
现在您的
main
方法可以使用自定义 ClassLoader 加载该类。但是,该自定义 ClassLoader 无法使用默认的父 ClassLoader,因为该父 ClassLoader 已经加载了您的应用程序(很可能来自您构建的 .jar 文件),并且该 ClassLoader 加载的 ArchiveWriter 类不知道您的外部激活 jar。
因此,当您创建新的类加载器时,您需要添加当前类加载器的所有条目,而不显式使用该类加载器:List<URL> urls = new ArrayList<>();
urls.add(activationJarURL);
Module mod = mimeExceptionExample.class.getModule();
if (mod.isNamed()) {
List<ModuleLayer> layers = List.of(mod.getLayer());
while (!layers.isEmpty()) {
List<ModuleLayer> parentLayers = new ArrayList<>();
for (ModuleLayer layer : layers) {
for (ResolvedModule r : layer.configuration().modules()) {
Optional<URI> location = r.reference().location();
if (location.isPresent()) {
urls.add(location.get().toURL());
}
}
parentLayers.addAll(layer.parents());
}
layers = parentLayers;
}
} else {
String[] systemClassPathEntries =
System.getProperty("java.class.path").split(File.pathSeparator);
for (String classpathEntry : systemClassPathEntries) {
urls.add(Path.of(classpathEntry).toUri().toURL());
}
}
URLClassLoader classLoader = new URLClassLoader(
urls.toArray(URL[]::new),
ClassLoader.getPlatformClassLoader());
现在我们可以用它来加载类了:
Class<? extends Runnable> archiveWriterClass =
Class.forName(loaderClassName, true, classLoader).asSubclass(Runnable.class);
Runnable archiveWriter =
archiveWriterClass.getConstructor(File.class).newInstance(archive);
archiveWriter.run();
如果需要传递更多信息和/或返回信息,可以使用除 Runnable 之外的任何接口。您甚至可以创建自己的界面。例如:
public interface Archiver {
int createArchive(Path archive,
Path... sourceDirs)
throws IOException;
}
当然,ArchiveWriter 可以有任何你想要的构造函数,只要你在调用
getConstructor
时正确指定构造函数的签名即可。