【问题标题】:How to load resources from other JAR file如何从其他 JAR 文件加载资源
【发布时间】:2014-06-21 10:10:55
【问题描述】:

我无法从其他运行的 jar 加载资源。这是我的设置

resource.jar  # contains resources I want to load
`-res/hwview/file1

engine.jar    # my application which need resources
`-res/hwview/file2

有趣的是,使用下面的代码我可以加载 file2(在我运行的 jar 中),但不能加载 file1

String dir = "res/hwview";
Enumeration<URL> e = getClass().getClassLoader().getResources(dir);
while(e.hasMoreElements()) {
    // prints only file1 from engine.jar 
    // (actually it's in classes directory because I run it from my IDE)
    System.out.println(e.nextElement());
}

[OUTPUT]
/path/to/my/project/SiHwViewUiModel/classes/res/hwview

所以我想也许这个 jar 没有被 ClassLoader 拾取,所以我打印了加载的内容

ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader)cl).getURLs();
for(URL url: urls){
    System.out.println(url.getFile());
}

[OUTPUT]
/path/to/my/project/SiHwViewUiModel/classes/
/path/to/my/project/Resources/deploy/resources.jar
... and other not so important jars

有什么想法吗? 感谢您的帮助!

【问题讨论】:

  • @hd1 不确定,我没有内罐
  • 同理,先生,您必须使用 Class.forName(class-within-resource-jar) 加载与该资源对应的 Class 对象,然后您可以使用它做您想做的事

标签: java jar classloader


【解决方案1】:

我找到了解决方案。 getResources() 方法和类似方法的问题是不能给定目录,而只能给定一个特定的文件。这意味着如果我想在整个类路径中搜索特定结构,我需要在基目录中创建 marker 文件。

示例:我想进入my/path目录->创建marker.info(名称无关紧要)文件然后搜索。

resources.jar
`- my/path/
   |- my/directories
   `- marker.info

resources2.jar
`- my/path/
   |- my/other/directories
   `- marker.info

# search
Enumeration<URL> urls = getClass().getClassLoader().getResources("my/path/marker.info"); 

# print
print(urls);
/path/to/resources.jar!/my/path/marker.info
/path/to/resources2.jar!/my/path/marker.info

【讨论】:

    【解决方案2】:

    如果 JAR 文件位于类路径中,则无需执行任何特殊操作。将找到资源。

    如果它们不在类路径中,您需要创建一个URLClassLoader 并使用它的getResource() 方法。

    【讨论】:

    • 我使用了 URLClassLoader,我无法获取 InputStream。 URL url = new URL("文件", null, "/home/solr-cell-4.8.0-test.jar" ); URLClassLoader urlClassLoader = new URLClassLoader( 新 URL[] { url } ); InputStream 输入 = urlClassLoader.getResourceAsStream(classAsPath); BufferedReader dis = new BufferedReader(new InputStreamReader(input));
    • 类似“/META-INF/NOTICE.txt”的东西
    • 'Something like' 对任何人都没有多大用处。
    • 你是对的。我使用: String classAsPath = "/org/apache/MANIFEST.MF";我在那个包下放了一个 MANIFEST.MF 文件。
    【解决方案3】:

    在Spring中,它可以从classpath中的所有jar文件中加载xml文件:

    ApplicationContext context = new ClassPathXmlApplicationContext(
            "classpath*:**/applicationContext*.xml");
    

    您可以查看 Spring 源代码以了解 Spring 是如何实现的。

    【讨论】:

    • 我需要在没有 Spring 的情况下这样做
    【解决方案4】:
    public final class JarResource
    {
    private String jarFileName;
    private Map<String, Long> hashSizes = new HashMap<String, Long>();
    private Map<String, Object> hashJarContents = new HashMap<String, Object>();
    
    public JarResource(String jarFileName) throws Exception
    {
        this.jarFileName = jarFileName;
        ZipFile zipFile = new ZipFile(this.jarFileName);
    
        Enumeration<ZipEntry> e = (Enumeration<ZipEntry>) zipFile.entries();
        while (e.hasMoreElements())
        {
            ZipEntry zipEntry = e.nextElement();
            if(!zipEntry.isDirectory())
            {
                hashSizes.put(getSimpleName(zipEntry.getName()), zipEntry.getSize());
            }
        }
        zipFile.close();
    
        // extract resources and put them into the hashMap.
        FileInputStream fis = new FileInputStream(jarFileName);
        BufferedInputStream bis = new BufferedInputStream(fis);
        ZipInputStream zis = new ZipInputStream(bis);
        ZipEntry ze = null;
    
        while ((ze = zis.getNextEntry()) != null)
        {
            if (ze.isDirectory())
            {
                continue;
            }
            else
            {
                long size = (int) ze.getSize();
                // -1 means unknown size.
                if (size == -1)
                {
                    size = hashSizes.get(ze.getName());
                }
    
                byte[] b = new byte[(int) size];
                int rb = 0;
                int chunk = 0;
                while (((int) size - rb) > 0)
                {
                    chunk = zis.read(b, rb, (int) size - rb);
                    if (chunk == -1)
                    {
                        break;
                    }
                    rb += chunk;
                }
    
                hashJarContents.put(ze.getName(), b);
            }
        }
        zis.close();
    }
    
    public byte[] getResource(String name)
    {
        return (byte[]) hashJarContents.get(name);
    }
    
    private String getSimpleName(String entryName)
    {
        // Remove ".jar" extension
        int index = entryName.indexOf("/");
        String fileNameWithoutExt = entryName.substring(index, entryName.length());
    
        return fileNameWithoutExt;
    }
    }
    

    然后使用这个类来加载你的资源:

    public static void main(String[] args) throws Exception
    {
        JarResource jr = new JarResource("/home/mjiang/Downloads/solr-4.8.0/dist/solr-cell-4.8.0-test.jar");
        byte[] resource = jr.getResource("/META-INF/NOTICE.txt");
    
        InputStream input = new ByteInputStream(resource, resource.length);
    
        BufferedReader dis = new BufferedReader(new InputStreamReader(input));
    
        String line = "";
        while((line = dis.readLine()) != null)
        {
            System.out.println(line);
        }
    
        dis.close();
    }
    

    【讨论】:

    • 这会读取 JAR 文件两次,并将 JAR 文件的全部内容加载到内存中。实际上没有必要在这种史诗般的规模上浪费时间和空间。
    • 你是对的。我应该跳过加载任何会大大减少时间和空间的类文件。
    • 您应该跳过加载所有内容,直到实际请求某些内容。您应该将其作为 ClassLoader 提供。在这种情况下,您将只是复制 URLClassLoader。
    • 正确的是,URLClassLoader 已经做到了这一切,而且做得更好。
    猜你喜欢
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    相关资源
    最近更新 更多