[英]ClassNotFoundException when trying to load classes from JAR
[英]ClassNotFoundException while trying to load class from external JAR at runtime
我試圖在運行時從JAR加載一個表示為byte []數組的類。
我知道有關要加載的類的兩件事:
1.實現“ RequiredInterface”
2.我知道它的限定名稱:“ sample.TestJarLoadingClass”
我找到了必須擴展ClassLoader的解決方案 ,但它拋出:
Exception in thread "main" java.lang.ClassNotFoundException: sample.TestJarLoadingClass
at java.lang.ClassLoader.findClass(ClassLoader.java:530)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at tasks.Main.main(Main.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
每當我想加載課程時。
造成這種情況的原因可能是什么?如何消除這種情況?
任何幫助,高度贊賞
主要方法:
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
Path path = Paths.get("src/main/java/tasks/sample.jar");
RequiredInterface requiredInterface = (RequiredInterface) Class.forName("sample.TestJarLoadingClass", true, new ByteClassLoader(Files.readAllBytes(path))).newInstance();
}
自定義類加載器:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ByteClassLoader extends ClassLoader {
private final byte[] jarBytes;
private final Set<String> names;
public ByteClassLoader(byte[] jarBytes) throws IOException {
this.jarBytes = jarBytes;
this.names = loadNames(jarBytes);
}
private Set<String> loadNames(byte[] jarBytes) throws IOException {
Set<String> set = new HashSet<>();
try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
set.add(entry.getName());
}
}
return Collections.unmodifiableSet(set);
}
@Override
public InputStream getResourceAsStream(String resourceName) {
if (!names.contains(resourceName)) {
return null;
}
boolean found = false;
ZipInputStream zipInputStream = null;
try {
zipInputStream = new ZipInputStream(new ByteArrayInputStream(jarBytes));
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().equals(resourceName)) {
found = true;
return zipInputStream;
}
}
} catch (IOException e) {;
e.printStackTrace();
} finally {
if (zipInputStream != null && !found) {
try {
zipInputStream.close();
} catch (IOException e) {;
e.printStackTrace();
}
}
}
return null;
}
}
RequiredInterface:
public interface RequiredInterface {
String method();
}
JAR文件中的類:
package sample;
public class TestJarLoadingClass implements RequiredInterface {
@Override
public String method() {
return "works!";
}
}
我認為這里有兩個問題:
首先,您應該重寫findClass
方法,該方法包含加載類的實際邏輯。 這里的主要挑戰是找到包含您的類的字節數組的一部分-由於您將整個jar都作為字節數組,因此您將需要使用JarInputStream
掃描類的字節數組。
但是因為你,這可能是不夠的RequiredInterface
是您未知ByteClassLoader
因此您將能夠讀取類本身,而是類的定義中包含的信息,它實現- RequiredInterface
這是你的類加載器的問題。 這個很容易解決,您只需要將常規類加載器作為構造函數參數傳遞給您的super(parentClassLoader)
並使用super(parentClassLoader)
。
這是我的版本:
public class ByteClassLoader extends ClassLoader {
private final byte[] jarBytes;
public ByteClassLoader(ClassLoader parent, byte[] jarBytes) throws IOException {
super(parent);
this.jarBytes = jarBytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// read byte array with JarInputStream
try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes))) {
JarEntry nextJarEntry;
// finding JarEntry will "move" JarInputStream at the begining of entry, so no need to create new input stream
while ((nextJarEntry = jis.getNextJarEntry()) != null) {
if (entryNameEqualsClassName(name, nextJarEntry)) {
// we need to know length of class to know how many bytes we should read
int classSize = (int) nextJarEntry.getSize();
// our buffer for class bytes
byte[] nextClass = new byte[classSize];
// actual reading
jis.read(nextClass, 0, classSize);
// create class from bytes
return defineClass(name, nextClass, 0, classSize, null);
}
}
throw new ClassNotFoundException(String.format("Cannot find %s class", name));
} catch (IOException e) {
throw new ClassNotFoundException("Cannot read from jar input stream", e);
}
}
private boolean entryNameEqualsClassName(String name, JarEntry nextJarEntry) {
// removing .class suffix
String entryName = nextJarEntry.getName().split("\\.")[0];
// "convert" fully qualified name into path
String className = name.replace(".", "/");
return entryName.equals(className);
}
}
和用法
RequiredInterface requiredInterface = (RequiredInterface)Class.forName("com.sample.TestJarLoadingClass", true, new ByteClassLoader(ByteClassLoader.class.getClassLoader(), Files.readAllBytes(path))).newInstance();
System.out.println(requiredInterface.method());
請注意,我的實現假定文件名=類名,因此,例如,將找不到非頂級類。 當然,某些細節可能會更完善(例如異常處理)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.