Why is getResourceAsStream returning null?

2019-09-06 08:56发布

问题:

I have an image inside my jar which I am trying to load, but getResourceAsStream() always returns null.

Directory structure:

com/thesimpzone/watch_pugs/watch_pugs/{all my class files}
META-INF/MANIFEST.MF
resources/generic/mouse.png

Content.java:

public abstract class Content {

    protected Map<String, BufferedImage> images = new HashMap<String, BufferedImage>();

    protected String prefix;

    public Content(String prefix){
        this.prefix = prefix;
    }

    protected void loadImage(String name){
        System.out.println(name);
        System.out.println(prefix);
        String path = (prefix + name);
        System.out.println(path);
        String identifier = name.substring(0, name.lastIndexOf("."));
        try{
            InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
            images.put(identifier, ImageIO.read(in));
        }catch(IOException | ClassCastException e){
            throw new RuntimeException("Image " + identifier + " at " + path + " could not be loaded.");
        }
    }
    [...]
}

GenericContent.java:

public class GenericContent extends Content {

    public GenericContent(){
        super("resources/generic/");
        this.loadContent();
    }

    @Override
    public void loadContent() {
        loadImage("mouse.png");
    }

}

StackTrace:

mouse.png
resources/generic/
resources/generic/mouse.png
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: input == null!
    at javax.imageio.ImageIO.read(ImageIO.java:1348)
    at com.thesimpzone.watch_pugs.watch_pugs.content.Content.loadImage(Content.java:29)
    at com.thesimpzone.watch_pugs.watch_pugs.content.GenericContent.loadContent(GenericContent.java:17)
    at com.thesimpzone.watch_pugs.watch_pugs.content.GenericContent.<init>(GenericContent.java:12)
    at com.thesimpzone.watch_pugs.watch_pugs.Canvas.<init>(Canvas.java:45)
    at com.thesimpzone.watch_pugs.watch_pugs.Framework.<init>(Framework.java:75)
    at com.thesimpzone.watch_pugs.watch_pugs.Window.<init>(Window.java:50)
    at com.thesimpzone.watch_pugs.watch_pugs.Window.<init>(Window.java:26)
    at com.thesimpzone.watch_pugs.watch_pugs.Window$1.run(Window.java:60)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:744)
    at java.awt.EventQueue.access$400(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:697)
    at java.awt.EventQueue$3.run(EventQueue.java:691)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:714)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

I have no idea why the classloader can't find the image. I have looked in the compiled jar and the file is there, and opens fine in paint so the file is OK. I have tried various ClassLoader variations, including getSystemClassLoader(), getClassLoader(), and Content.class.getClassLoader(); and also getResourceAsStream(path) instead of getResource(path).openStream(). I have tried with and without the leading '/' on the prefix, so I'm all out of ideas and Google isn't helping. Also, it seems like what I'm doing to define 'prefix' is very awkward and so if there is a better way I would be glad if someone showed me how.

Thanks.

回答1:

It is all about relative vs absolute packages when calling getResourceAsStream(), you looking for something relative to whatever package Content is at as the root.

there are no "directories" on the classpath, especially inside .jar files, only packages

The best thing to use is Thread.currentThread().getContextClassloader().getResourceAsStream() with a fully qualified package without the leading /.

The reason this is best is because inside of application containers, they usually have multiple classloaders and this way you don't have to care which one your resource is loaded from.

In your case:

Thread.currentThread().getContextClassloader().getResourceAsStream("resources/generic/mouse.png");

If you still get an error with this approach your .jar isn't built like you think it is or if you are getting this from inside an IDE you probably aren't copying the contents resource/generic/ into the classpath.

Personally I always use the form:

Thread.currentThread().getContextClassLoader().getResourceAsStream("path/to/resource/file.ext"); as it always works from where ever you call it and whatever classloader you are in, and it is explicit about where it is looking to find what it is looking for.