[英]How to load the bean XML file from another jar using ClassLoader
[英]Creating a ClassLoader to load a JAR file from a byte array
我正在寻找一个自定义类加载器,它将从自定义网络加载JAR
文件。 最后,我必须使用的是JAR
文件的字节数组。
我无法将字节数组转储到文件系统上并使用URLClassLoader
。
我的第一个计划是从流或字节数组创建一个JarFile
对象,但它只支持File
对象。
我已经写了一些使用JarInputStream
东西:
public class RemoteClassLoader extends ClassLoader {
private final byte[] jarBytes;
public RemoteClassLoader(byte[] jarBytes) {
this.jarBytes = jarBytes;
}
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.writeTo(in, out);
byte[] bytes = out.toByteArray();
clazz = defineClass(name, bytes, 0, bytes.length);
if (resolve) {
resolveClass(clazz);
}
} catch (Exception e) {
clazz = super.loadClass(name, resolve);
}
}
return clazz;
}
@Override
public URL getResource(String name) {
return null;
}
@Override
public InputStream getResourceAsStream(String name) {
try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes))) {
JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (entry.getName().equals(name)) {
return jis;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
这可能适用于小型JAR
文件,但我尝试加载一个2.7MB
jar文件,包含近2000
类,并且需要大约160 ms
来迭代所有条目,更不用说加载它找到的类了。
如果有人知道的解决方案比每次加载类时迭代JarInputStream
的条目更快,请分享!
首先,您不需要使用JarInputStream
,因为它只将清单的支持添加到类ZipInputStream
,我们在这里并不关心。 您不能将您的条目放入缓存中(除非您直接存储每个条目的内容,这在内存消耗方面会很糟糕),因为ZipInputStream
不是要共享的,因此无法同时读取它。 您可以做的最好的事情是将条目的名称存储到缓存中,以便在我们知道条目存在时仅迭代条目。
代码可能是这样的:
public class RemoteClassLoader extends ClassLoader {
private final byte[] jarBytes;
private final Set<String> names;
public RemoteClassLoader(byte[] jarBytes) throws IOException {
this.jarBytes = jarBytes;
this.names = RemoteClassLoader.loadNames(jarBytes);
}
/**
* This will put all the entries into a thread-safe Set
*/
private static 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 name) {
// Check first if the entry name is known
if (!names.contains(name)) {
return null;
}
// I moved the JarInputStream declaration outside the
// try-with-resources statement as it must not be closed otherwise
// the returned InputStream won't be readable as already closed
boolean found = false;
ZipInputStream jis = null;
try {
jis = new ZipInputStream(new ByteArrayInputStream(jarBytes));
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
if (entry.getName().equals(name)) {
found = true;
return jis;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// Only close the stream if the entry could not be found
if (jis != null && !found) {
try {
jis.close();
} catch (IOException e) {
// ignore me
}
}
}
return null;
}
}
使用JarInputStream
访问zip条目显然不是这样做的方法,因为您需要迭代条目以找到它不是可扩展的方法,因为性能将取决于jar文件中的条目总数。
为了获得最佳性能,您需要使用ZipFile
才能直接访问条目,这要归功于getEntry(name)
方法,无论您的存档大小如何。 不幸的是, ZipFile
类没有提供任何构造函数来接受存档的内容作为byte
数组(这不是一个好的做法,因为如果文件太大你可能会面对OOME)但只是作为File
,所以你会需要更改类的逻辑,以便将zip的内容存储到临时文件中,然后将此临时文件提供给ZipFile
,以便能够直接访问该条目。
代码可能是这样的:
public class RemoteClassLoader extends ClassLoader {
private final ZipFile zipFile;
public RemoteClassLoader(byte[] jarBytes) throws IOException {
this.zipFile = RemoteClassLoader.load(jarBytes);
}
private static ZipFile load(byte[] jarBytes) throws IOException {
// Create my temporary file
Path path = Files.createTempFile("RemoteClassLoader", "jar");
// Delete the file on exit
path.toFile().deleteOnExit();
// Copy the content of my jar into the temporary file
try (InputStream is = new ByteArrayInputStream(jarBytes)) {
Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
}
return new ZipFile(path.toFile());
}
...
@Override
public InputStream getResourceAsStream(String name) {
// Get the entry by its name
ZipEntry entry = zipFile.getEntry(name);
if (entry != null) {
// The entry could be found
try {
// Gives the content of the entry as InputStream
return zipFile.getInputStream(entry);
} catch (IOException e) {
// Could not get the content of the entry
// you could log the error if needed
return null;
}
}
// The entry could not be found
return null;
}
}
我将遍历该类一次并缓存条目。 我还会看一下URLClassLoader的源代码,看看它是如何做到的。 如果失败,请将数据写入临时文件并通过普通的类加载器加载。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.