为什么将此代码编译成jar文件后,显示图像会出现“错误”?

huangapple go评论61阅读模式
英文:

Why does this code displaying an image give an ""error"" when build into jar?

问题

public class ImageHQ extends JLabel {

    BufferedImage img;
    
    int xOffset = 0;
    int yOffset = 0;

    
    public ImageHQ(String path, int xOffset, int yOffset) {
        try {
            try {
                img = ImageIO.read(new File(getClass().getResource(path).toURI()));
            } catch (URISyntaxException ex) {
                Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
                errorMsg(ex.getMessage());
            }
        } catch (IOException ex) {
            Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
            errorMsg(ex.getMessage());
        }
        
        this.xOffset = xOffset;
        this.yOffset = yOffset;

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.drawImage(img, 0 + xOffset, 0 + yOffset, null);

        repaint();
    }
    
    public void errorMsg(String msg) {
        JOptionPane.showMessageDialog(null, msg, "Fehler", JOptionPane.ERROR_MESSAGE);
    }

}
英文:

I want to display an Image in a JLabel by drawing a BufferedImage on it.

The x/yOffset is to draw a smaller image in the middel of the JLabel.

If I run the code in my IDE it works fine and displays the image on my JFrame.

If I now build the Class into a jar file it does not work anymore.

I tried it with setting the Image as an icon for the JLabel without using BufferedImage but thats not what I want to do.

Here is the Code of my Image Class:

public class ImageHQ extends JLabel {
BufferedImage img;
int xOffset=0;
int yOffset=0;
public ImageHQ(String path, int xOffset, int yOffset) {
try {
try {
img = ImageIO.read(new File(getClass().getResource(path).toURI()));
} catch (URISyntaxException ex) {
Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
errorMsg(ex.getMessage());
}
} catch (IOException ex) {
Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
errorMsg(ex.getMessage());
}
this.xOffset = xOffset;
this.yOffset = yOffset;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(img, 0+xOffset, 0+yOffset, null);
repaint();
}
public void errorMsg(String msg) {
JOptionPane.showMessageDialog(null, msg, "Fehler", JOptionPane.ERROR_MESSAGE);
}

}

PS: The errorMsg method also does not give me an error.

答案1

得分: 3

问题出在这里:

new File(getClass().getResource(path).toURI())

应用程序资源并不保证是一个单独的文件。一个 .jar 文件中的条目只是压缩存档的一部分。它并不是硬盘上的单独文件。这就是为什么你不能使用 File 来读取它。

正确的读取资源的方式是根本不要试图将其转换为文件。getResource 返回一个 URL;你可以直接将这个 URL 传递给 接受 URL 参数的 ImageIO.read 方法

img = ImageIO.read(ImageHQ.class.getResource(path));

请注意使用类字面量 ImageHQ.class,而不是 getClass()。这可以确保你的资源是相对于你自己的类来读取的,而不是可能位于不同包或不同 模块 中的子类。

从 InputStream 中读取

一般来说,可能会有一些情况下 URL 并不足够。你还可以使用 getResourceAsStream 来获得一个从资源读取的打开的 InputStream。在你的情况下,你可以这样做:

try (InputStream stream = ImageHQ.class.getResource(path)) {
    img = ImageIO.read(stream);
}

但这不是最佳选择,因为 URL 可以提供 InputStream 无法提供的信息,比如文件名、内容类型和图片数据长度的提前知识。

资源路径

你传递给 getResourcegetResourceAsStream 的字符串参数实际上并不是一个文件名。它是相对 URL 的路径部分。这意味着以 C:\ 开头的参数将始终失败。

因为这个参数是一个 URL,所以它在所有平台上都使用正斜杠 (/) 来分隔路径组件。通常情况下,它会根据调用 getResource* 方法的类对象的包来解析;所以,如果 ImageHQcom.example 包中,以下代码:

ImageHQ.class.getResource("logo.png")

会在 .jar 文件中查找 com/example/logo.png。

你还可以选择以斜杠开头,强制它相对于 .jar 文件的根目录。上面的例子可以写成:

ImageHQ.class.getResource("/com/example/logo.png")

ClassLoader 也有 getResource* 方法,但不应该使用这些方法。始终使用 Class.getResource 或 Class.getResourceAsStream。ClassLoader 方法在 Java 8 及更早版本中在功能上类似,但从 Java 9 开始,由于 Class.getResource 不会违反模块封装,因此在模块化程序中更安全。(ClassLoader.getResource 不允许在其字符串参数开头使用 /,并且始终假定参数是相对于 .jar 文件的根目录。)

空返回值

如果路径参数在 .jar 文件中不存在资源(或者资源在不允许读取的模块中),所有的 getResource* 方法都会返回 null(或者抛出 NullPointerException 或 IllegalArgumentException 异常)。例如,如果在 .jar 文件中与 ImageHQ 类位于同一包中的位置没有 logo.png,调用 getResource 将返回 null,将这个 null 传递给 ImageIO.read 将会导致 IllegalArgumentException,正如 ImageIO.read 文档中所述。

如果出现这种情况,你可以通过列出 .jar 文件的内容来排除问题。有多种方法可以实现这一点:

  • 每个集成开发环境的文件浏览器或文件树都可以检查 .jar 文件的内容。
  • 如果你的 JDK 在你的 Shell Path 中,你可以简单地执行 jar tf /path/to/myapplication.jar
  • 在 Unix 和 Linux 中,unzip -v /path/to/myapplication.jar 也可以工作,因为 .jar 文件实际上是一个带有一些 Java 特定条目的 zip 文件。
  • 在 Windows 中,你可以复制 .jar 文件,将复制的扩展名更改为 .zip,并用任何 zip 工具(包括 Windows 文件浏览器)打开它。

回到这个例子,如果你的类位于 com.example 包中,而你的代码正在执行 ImageHQ.class.getResource("logo.png"),你应该检查 .jar 文件的内容是否包含 com/example/logo.png 条目。如果没有,getResource 方法将返回 null。

关于打印错误消息

ex.toString() 替换 ex.getMessage()。通常情况下,异常的消息本身是没有意义的。你还应该在每个 catch 块中添加 ex.printStackTrace();(或者添加记录堆栈跟踪的日志语句),这样你就会知道问题发生的确切位置。

关于绘图

永远不要在 paintComponent 方法中调用 repaint()。这会创建一个无限循环,因为 repaint() 将强制 Swing 绘图系统再次调用 paintComponent 方法。

英文:

The problem lies here:

new File(getClass().getResource(path).toURI())

An application resource is not guaranteed to be a separate file. A .jar entry is just part of a compressed archive. It’s not a separate file on the hard drive. That is why you cannot use File to read it.

The correct way to read a resource is by not attempting to convert it to a file at all. getResource returns a URL; you can just pass that URL directly to the ImageIO.read method that takes a URL:

img = ImageIO.read(ImageHQ.class.getResource(path));

Note the use of a class literal, ImageHQ.class, instead of getClass(). This guarantees that your resource is read relative to your own class, and not a subclass which might be in a different package or different module.

Reading from an InputStream

Generally speaking, there may be cases where a URL isn’t sufficient. You can also use getResourceAsStream to obtain an open InputStream that reads from the resource. In your case, you could do:

try (InputStream stream = ImageHQ.class.getResource(path)) {
img = ImageIO.read(stream);
}

But this would be sub-optimal, since a URL can provide information that an InputStream cannot, like a file name, content type, and advance knowledge of the image data length.

Resource paths

The String argument you pass to getResource and getResourceAsStream is not actually a file name. It’s the path portion of a relative URL. This means an argument that starts with, say, C:\ will always fail.

Because the argument is a URL, it always uses forward slashes (/) to separate path components, on all platforms. Normally, it is resolved against the package of the class object whose getResource* method is called; so, if ImageHQ were in the com.example package, this code:

ImageHQ.class.getResource("logo.png")

would look for com/example/logo.png in the .jar file.

You can optionally start the String argument with a slash, which will force it to be relative to the root of the .jar file. The above could be written as:

ImageHQ.class.getResource("/com/example/logo.png")

There are also getResource* methods in ClassLoader, but those should not be used. Always use Class.getResource or Class.getResourceAsStream instead. The ClassLoader methods are functionally similar in Java 8 and earlier, but as of Java 9, Class.getResource is safer in modular programs because it won’t run afoul of module encapsulation. (ClassLoader.getResource does not allow / at the start of its String argument, and always assumes the argument is relative to the root of a .jar file.)

null return values

All getResource* methods will return null if the path argument does not name a resource that actually exists in the .jar file (or if the resource is in a module that doesn’t allow it to be read). A NullPointerException or IllegalArgumentException is a common symptom of this. For instance, if no logo.png was in the same package as the ImageHQ class in the .jar file, getResource would return null, and passing that null to ImageIO.read would result in an IllegalArgumentException, as stated in the ImageIO.read documentation.

If this occurs, you can troubleshoot the .jar file by listing its contents. There are a number of ways to do this:

  • Every IDE’s file explorer or file tree can examine the contents of a .jar file.
  • If your JDK is in your shell’s Path, you can simply do jar tf /path/to/myapplication.jar.
  • In Unix and Linux, unzip -v /path/to/myapplication.jar will also work, since a .jar file is actually a zip file with a few Java-specific entries.
  • In Windows, you can make a copy of the .jar file, change the copy’s extension to .zip, and open it with any zip tool, including the Windows file explorer.

Going back to the example, if your class were in the com.example package and your code were doing ImageHQ.class.getResource("logo.png"), you would check the contents of the .jar file for a com/example/logo.png entry. If it’s not there, the getResource method will return null.

Regarding printing the error message

Replace ex.getMessage() with ex.toString(). It is often the case that an exception’s message is meaningless by itself. You should also add ex.printStackTrace(); to each catch block (or add a logging statement that records the stack trace), so you’ll know exactly where the problem occurred.

Regarding painting

Never call repaint() from a paintComponent method. This creates an infinite loop, because repaint() will force the Swing painting system to call paintComponent again.

huangapple
  • 本文由 发表于 2020年9月15日 03:12:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/63890530.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定