无法访问可执行Jar包中的资源

8
请问我在这里做错了什么。
我的天气应用程序可以生成并发送HTML电子邮件。使用下面的代码时,当我从Eclipse中运行它时一切正常。我的电子邮件被生成,它能够访问我的图像资源,并且它发送带有附件的电子邮件。
但是,当我通过运行"mvn install"来构建可执行的jar文件,并且使用"java -jar NameOfMyJar.jar"运行该jar文件时,对于我的图像资源,我会得到java.io.FileNotFound异常。
我知道我在访问图像资源方面肯定做错了什么,只是不理解为什么在未打包时可以正常工作,但打包成jar文件后就失败了。
非常感谢任何建议。
我的项目布局 enter image description here
我如何访问我的图像资源
//Setup the ATTACHMENTS
        MimeBodyPart attachmentsPart = new MimeBodyPart();
        try {
            attachmentsPart.attachFile("resources/Cloudy_Day.png");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   

堆栈跟踪

    Exception in thread "main" java.lang.RuntimeException: javax.mail.MessagingException: IOException while sending message;
  nested exception is:
    java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
    at Utilities.SendEmailUsingGmailSMTP.SendTheEmail(SendEmailUsingGmailSMTP.java:139)
    at Utilities.SendEmailUsingGmailSMTP.SendWeatherEmail(SendEmailUsingGmailSMTP.java:66)
    at Weather.Main.start(Main.java:43)
    at Weather.Main.main(Main.java:23)
Caused by: javax.mail.MessagingException: IOException while sending message;
  nested exception is:
    java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
    at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1167)
    at javax.mail.Transport.send0(Transport.java:195)
    at javax.mail.Transport.send(Transport.java:124)
    at Utilities.SendEmailUsingGmailSMTP.SendTheEmail(SendEmailUsingGmailSMTP.java:134)
    ... 3 more
Caused by: java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:146)
    at javax.activation.FileDataSource.getInputStream(FileDataSource.java:97)
    at javax.activation.DataHandler.writeTo(DataHandler.java:305)
    at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1485)
    at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:865)
    at javax.mail.internet.MimeMultipart.writeTo(MimeMultipart.java:462)
    at com.sun.mail.handlers.multipart_mixed.writeTo(multipart_mixed.java:103)
    at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:889)
    at javax.activation.DataHandler.writeTo(DataHandler.java:317)
    at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1485)
    at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1773)
    at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1119)
    ... 6 more

请尝试使用 /resources/Cloudy_Day.png(路径前面加上 / - Jens
你当前的工作目录是什么? - jmj
谢谢大家的建议!今晚回家后,我会尝试一下并告诉大家进展如何。 - Chiefwarpaint
4个回答

9

其他人使用getResourceAsStream是正确的,但路径有点棘手。你看到了resources文件夹中的小包图标吗?这表示resource文件夹中的所有文件都将放置在类路径的根目录下。就像src/main/java中的所有包一样放置在根目录下。因此,你需要从路径中去掉resources

InputStream is = getClass().getResourceAsStream("/Cloudy_Day.png");

一个旁注:Maven有一个文件结构约定。类路径资源通常放在src/main/resources中。如果您在src/main中创建一个resources目录,Eclipse应该会自动捕捉到它,并为路径src/main/resource创建一个小的包图标,您应该在项目浏览器中看到它。这些文件也将进入根目录,并且可以以相同的方式访问。我会修复文件结构以遵循这个约定。
注意:MimeBodyPart可以从InputStream构造(如Bill Shannon建议的那样,这是不正确的)。正如他在下面的评论中提到的那样:“您还可以使用”
mbp.setDataHandler(new DataHandler(new ByteArrayDataSource(
          this.getClass().getResourceAsStream("/Cloudy_Day.png", "image/png"))));

注意:从流构造MimeBodyPart并不是你想象的那样。它会读取整个MIME部分 - 包括头和数据 - 而不仅仅是你想要作为附件的数据。 - Bill Shannon
@BillShannon 我知道了,谢谢你提醒。我会把它删掉的。我对这个API不太熟悉。你认为应该如何处理OP的用例,使用InputStream? - Paul Samsotha
1
@peeskillet和BillShannon非常感谢你们!!!我修改了我的项目结构并使用setDataHandler()方法访问我的资源文件,现在一切都正常了。很抱歉让你们等了几天,但最近我很忙。无论如何,非常感谢你们的帮助。 - Chiefwarpaint

3

无法像访问文件一样访问JAR文件内的资源,只能将其作为InputStream读取:getResourceAsStream()

由于MimeBodyPart没有针对InputStream的attach()方法,最简单的方法应该是将您的资源读取并写入临时文件,然后附加这些文件。


您还可以使用mbp.setDataHandler(new DataHandler(new ByteArrayDataSource(this.getClass().getResourceAsStream("/Cloudy_Day.png", "image/png"))));附加数据。 - Bill Shannon

1
尝试这个。
new MimeBodyPart().attachFile(new File(this.getClass().getClassLoader().getResource("resources/Cloudy_Day.png").toURI());

0

我不知道这是否会对任何人有所帮助。但是,我有一个与OP类似的情况,并通过使用递归函数在类路径中找到文件来解决了该问题。想法是当另一个开发人员决定将资源移动到另一个文件夹/路径时,只要名称仍然相同,它仍将被找到。

例如,在我的工作中,我们通常将资源放在jar外面,然后将该资源路径添加到我们的类路径中,因此资源的类路径将根据其位置而异。

这就是我的代码发挥作用的地方,无论文件放在哪里,只要它在类路径中,它就会被找到。

以下是我的代码示例:

import java.io.File;

public class FindResourcesRecursive {

    public File findConfigFile(String paths, String configFilename) {
        for (String p : paths.split(File.pathSeparator)) {
            File result = findConfigFile(new File(p), configFilename);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    private File findConfigFile(File path, String configFilename) {
        if (path.isDirectory()) {
            String[] subPaths = path.list();
            if (subPaths == null) {
                return null;
            }
            for (String sp : subPaths) {
                File subPath = new File(path.getAbsoluteFile() + "/" + sp);
                File result = findConfigFile(subPath, configFilename);
                if (result != null && result.getName().equalsIgnoreCase(configFilename)) {
                    return result;
                }
            }
            return null;
        } else {
            File file = path;
            if (file.getName().equalsIgnoreCase(configFilename)) {
                return file;
            }
            return null;
        }
    }

}

这里我有一个测试用例,它与我的test/resources文件夹中的文件“test.txt”耦合。该文件的内容为:

A sample file

现在,这是我的测试案例:

import org.junit.Test;

import java.io.*;

import static org.junit.Assert.fail;

public class FindResourcesRecursiveTest {

    @Test
    public void testFindFile() {
        // Here in the test resources I have a file "test.txt"
        // Inside it is a string "A sample file"
        // My Unit Test will use the class FindResourcesRecursive to find the file and print out the results.
        File testFile = new FindResourcesRecursive().findConfigFile(
                System.getProperty("java.class.path"),
                "test.txt"
        );

        try (FileInputStream is = new FileInputStream(testFile)) {
            int i;
            while ((i = is.read()) != -1) {
                System.out.print((char) i);
            }
            System.out.println();
        } catch (IOException e) {
            fail();
        }

    }
}

现在,如果你运行这个测试,它会打印出 "A sample file" 并且测试将会通过。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接