我试图在运行时从JAR中加载一个表示为byte []数组的类
我知道要加载课程的两件事:
1.它实现" RequiredInterface"
2.我知道它的合格名称:" sample.TestJarLoadingClass"
我找到了solution,我必须在其中扩展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!";
}
}
答案 0 :(得分:2)
在我看来,我们有两个问题:
首先,您应该覆盖包含加载类的实际逻辑的findClass
方法。这里的主要挑战是找到包含您的类的字节数组的一部分 - 因为您将整个jar作为字节数组,您将需要使用JarInputStream
来扫描您的类的字节数组。
但这可能还不够,因为您的RequiredInterface
未知ByteClassLoader
- 所以您将能够自己读取类,但类的定义包含它实现的信息{{1}这对你的类加载器来说是一个问题。这个很容易修复,你只需要将常规的类加载器作为构造函数参数传递给你的那个并使用RequiredInterface
。
这是我的版本:
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);
}
}
请注意,我的实现假定文件名=类名,因此例如找不到顶级的类。当然,一些细节可能更精确(如异常处理)。