Java 从内存动态加载JAR

Java 从内存动态加载JAR,java,jar,classloader,Java,Jar,Classloader,我想动态加载一个JAR,直接加载内存。 比方说,我有一个包含JAR的缓冲区,我想加载JAR中的所有类,或者至少列出JAR中存在的所有文件。(课程、图片等…) 例如,如果我加载的第一个类依赖于第二个类,我该怎么办? java知道如何处理这个问题吗?或者我自己处理这个问题?您应该使用自定义的类加载器并将JAR文件设置为其类路径。 类始终是惰性加载的,您不会显式加载它们。一旦JAR位于类加载器的类路径上,您就可以解析资源。您可能需要先将JAR写入磁盘,然后使用以下方法将其添加到类路径: 如果要枚举不在

我想动态加载一个JAR,直接加载内存。
比方说,我有一个包含JAR的缓冲区,我想加载JAR中的所有类,或者至少列出JAR中存在的所有文件。(课程、图片等…)
例如,如果我加载的第一个类依赖于第二个类,我该怎么办?
java知道如何处理这个问题吗?或者我自己处理这个问题?

您应该使用自定义的
类加载器
并将JAR文件设置为其类路径。


类始终是惰性加载的,您不会显式加载它们。一旦JAR位于
类加载器的类路径上,您就可以解析资源。

您可能需要先将JAR写入磁盘,然后使用以下方法将其添加到类路径:

如果要枚举不在类路径中的jar内容,可以始终将其视为zip文件:


在这里,您可以使用URLClassloader加载jar。 在这个例子中,我有一个jar,它有一个名为com.x.Test的类,它有一个print方法,下面的代码描述了如何加载类和调用方法,这只是一个例子,但是更好地使用接口、工厂方法和反射可以使代码更好

File jarFile = new File("myspace\\my.jar");
URLClassLoader loader = new URLClassLoader(new URL[]{jarFile.toURL()});
Class<?> clazz = loader.loadClass("com.x.Test");
clazz.getMethod("print").invoke(clazz.getConstructor().newInstance(), args);
File jarFile=new文件(“myspace\\my.jar”);
URLClassLoader=新URLClassLoader(新URL[]{jarFile.toURL()});
clazz类=loader.loadClass(“com.x.Test”);
调用(clazz.getConstructor().newInstance(),args);

既然您说过“至少列出JAR中存在的所有文件”,那么让我们从这个相当简单的任务开始

假设您的JAR文件位于字节数组中,
byte[]buffer

try(JarInputStream is=new JarInputStream(new ByteArrayInputStream(buffer))) {
    for(;;) {
        JarEntry nextEntry = is.getNextJarEntry();
        if(nextEntry==null) break;
        System.out.println(nextEntry);
    }
}
从这样一个表示加载类并不是现成的,因为标准的
ClassLoader
实现依赖于
JarFile
实现,后者依赖于物理文件而不是抽象

因此,除非您只是将缓冲区写入一个临时文件,否则它最终将实现您自己的
类加载器
。由于JRE只支持如上所示的流访问,因此您必须线性扫描以找到请求的资源/类,或者迭代一次并将条目存储到
映射中

实现
ClassLoader
的一种替代方法是实现一个自定义
URL
处理程序,与
URLClassLoader
一起使用,这样可以减少上述查找任务:

final Map<String,byte[]> map=new HashMap<>();
try(JarInputStream is=new JarInputStream(new ByteArrayInputStream(buffer))) {
    for(;;) {
        JarEntry nextEntry = is.getNextJarEntry();
        if(nextEntry==null) break;
        final int est=(int)nextEntry.getSize();
        byte[] data=new byte[est>0? est: 1024];
        int real=0;
        for(int r=is.read(data); r>0; r=is.read(data, real, data.length-real))
            if(data.length==(real+=r)) data=Arrays.copyOf(data, data.length*2);
        if(real!=data.length) data=Arrays.copyOf(data, real);
        map.put("/"+nextEntry.getName(), data);
    }
}
URL u=new URL("x-buffer", null, -1, "/", new URLStreamHandler() {
    protected URLConnection openConnection(URL u) throws IOException {
        final byte[] data = map.get(u.getFile());
        if(data==null) throw new FileNotFoundException(u.getFile());
        return new URLConnection(u) {
            public void connect() throws IOException {}
            @Override
            public InputStream getInputStream() throws IOException {
                return new ByteArrayInputStream(data);
            }
        };
    }
});
try(URLClassLoader cl=new URLClassLoader(new URL[]{u})) {
    cl.loadClass( « a class from your JarFile buffer »);
}
final Map=new HashMap();
try(JarInputStream is=new-JarInputStream(new-ByteArrayInputStream(buffer))){
对于(;;){
JarEntry nextEntry=is.getNextJarEntry();
如果(nextEntry==null)中断;
final int est=(int)nextEntry.getSize();
字节[]数据=新字节[est>0?est:1024];
int实数=0;
for(int r=is.read(data);r>0;r=is.read(data,real,data.length real))
如果(data.length==(real+=r))data=Arrays.copyOf(data,data.length*2);
如果(real!=data.length)data=Arrays.copyOf(data,real);
map.put(“/”+nextEntry.getName(),数据);
}
}
URL u=新URL(“x-buffer”,null,-1,“/”,新的URLStreamHandler(){
受保护的URL连接openConnection(URL u)引发IOException{
最后一个字节[]data=map.get(u.getFile());
如果(data==null)抛出新的FileNotFoundException(u.getFile());
返回新的URL连接(u){
public void connect()引发IOException{}
@凌驾
公共InputStream getInputStream()引发IOException{
返回新的ByteArrayInputStream(数据);
}
};
}
});
try(URLClassLoader cl=newurlclassloader(newurl[]{u})){
cl.loadClass(«来自JarFile缓冲区的类»);
}

@Maksym这并没有回答我的问题,因为我问过如何直接从内存而不是从路径执行。不管它放在哪里……看看@Chris at using JCL的解决方案——其中有一个从任意输入流(在您的例子中是从内存加载的字节流)加载类的示例@基本问题是JCL本身就是一个JAR,所以我必须先加载它,然后才能开始使用它。因为我想直接从内存加载JAR中的类,所以我想我不能在类路径中设置JAR。还是我错了?你说的“因为我想直接从内存加载JAR中的类”是什么意思?这些是在JAR中,还是在内存中?内存中有一个缓冲区,其结构类似于JAR文件。它从磁盘加载JAR,而不是从内存加载。很好的解决方案,不幸的是它不处理资源,因此这不是一个功能齐全的类加载器。使用“类路径”添加其他URL协议允许通过ClasspathURLStreamHandler提供资源,但前提是该类加载器设置为contextClassLoader。@tporeba我刚刚验证了对该类加载器或使用该加载器加载的类调用
getResource
getResourceAsStream
是否有效。因为Java不关心上下文类加载器,所以您的问题似乎源于另一个软件、应用程序或框架,使用上下文类加载器进行资源查找。如果您想遵循该约定,则必须将该类装入器设置为这样。但这适用于所有类加载器,并不会使其成为不完整的类加载器。查找您自己的资源的正确方法是
YourClass.class.getResource[AsStream](…)
…是的,您是对的,我不够精确。当您通过
getResource
getresourceastream
直接使用类加载器时,您可以正确地获取资源。但是,如果您将这个类加载器放入一个框架中,该框架处理
getResource
返回的
java.net.URL
实例,那么它的工作方式就不同于普通的类加载器。在我的例子中,URL被转换成字符串,然后不可能重新创建
java.net.URL
ins
try(JarInputStream is=new JarInputStream(new ByteArrayInputStream(buffer))) {
    for(;;) {
        JarEntry nextEntry = is.getNextJarEntry();
        if(nextEntry==null) break;
        System.out.println(nextEntry);
    }
}
final Map<String,byte[]> map=new HashMap<>();
try(JarInputStream is=new JarInputStream(new ByteArrayInputStream(buffer))) {
    for(;;) {
        JarEntry nextEntry = is.getNextJarEntry();
        if(nextEntry==null) break;
        final int est=(int)nextEntry.getSize();
        byte[] data=new byte[est>0? est: 1024];
        int real=0;
        for(int r=is.read(data); r>0; r=is.read(data, real, data.length-real))
            if(data.length==(real+=r)) data=Arrays.copyOf(data, data.length*2);
        if(real!=data.length) data=Arrays.copyOf(data, real);
        map.put("/"+nextEntry.getName(), data);
    }
}
URL u=new URL("x-buffer", null, -1, "/", new URLStreamHandler() {
    protected URLConnection openConnection(URL u) throws IOException {
        final byte[] data = map.get(u.getFile());
        if(data==null) throw new FileNotFoundException(u.getFile());
        return new URLConnection(u) {
            public void connect() throws IOException {}
            @Override
            public InputStream getInputStream() throws IOException {
                return new ByteArrayInputStream(data);
            }
        };
    }
});
try(URLClassLoader cl=new URLClassLoader(new URL[]{u})) {
    cl.loadClass( « a class from your JarFile buffer »);
}