Предпочтительный способ загрузки ресурсов в Java


107

Я хотел бы узнать, как лучше всего загрузить ресурс на Java:

  • this.getClass().getResource() (or getResourceAsStream()),
  • Thread.currentThread().getContextClassLoader().getResource(name),
  • System.class.getResource(name).

Ответы:


140

Разработайте решение в соответствии с тем, что вы хотите ...

Есть две вещи, которые getResource/ getResourceAsStream()получит от класса, для которого он вызван ...

  1. Загрузчик классов
  2. Стартовая локация

Итак, если вы это сделаете

this.getClass().getResource("foo.txt");

он попытается загрузить файл foo.txt из того же пакета, что и класс this, и с помощью загрузчика классов этого класса. Если вы поставите перед ним «/», то вы полностью ссылаетесь на ресурс.

this.getClass().getResource("/x/y/z/foo.txt")

загрузит ресурс из загрузчика классов «this» и из пакета xyz (он должен находиться в том же каталоге, что и классы в этом пакете).

Thread.currentThread().getContextClassLoader().getResource(name)

загрузится с помощью загрузчика класса контекста, но не разрешит имя в соответствии с каким-либо пакетом (на него должна быть абсолютная ссылка)

System.class.getResource(name)

Загрузит ресурс с помощью загрузчика системного класса (на него также должна быть абсолютная ссылка, так как вы не сможете ничего поместить в пакет java.lang (пакет System).

Просто взгляните на источник. Также указывает, что getResourceAsStream просто вызывает «openStream» для URL, возвращенного из getResource, и возвращает его.


Имена пакетов AFAIK не имеют значения, это имеет значение путь к классам загрузчика классов.
Барт ван Хекелом 05

@Bart, если вы посмотрите исходный код, вы заметите, что имя класса имеет значение, когда вы вызываете getResource для класса. Первое, что делает этот вызов, - это «resolveName», который добавляет префикс пакета, если это необходимо. Документ Javadoc для resolveName: «Добавить префикс имени пакета, если имя не является абсолютным. Удалить начальные строки» / «, если имя абсолютное»
Майкл Уайлс

10
Ах я вижу. Абсолютный здесь означает относительный к пути к классам, а не абсолютный файловой системы.
Барт ван Хекелом

1
Я просто хочу добавить, что вы всегда должны проверять, что поток, возвращаемый getResourceAsStream (), не равен нулю, потому что это будет, если ресурс не находится в пути к классам.
stenix

Также стоит отметить, что использование загрузчика классов контекста позволяет изменять загрузчик классов во время выполнения через Thread#setContextClassLoader. Это полезно, если вам нужно изменить путь к классам во время выполнения программы.
Макс

14

Что ж, отчасти это зависит от того, что вы хотите, если вы на самом деле находитесь в производном классе.

Например, предположим, что он SuperClassнаходится в A.jar и SubClassнаходится в B.jar, и вы выполняете код в методе экземпляра, объявленном в, SuperClassно где thisссылается на экземпляр SubClass. Если вы this.getClass().getResource()его используете, он будет выглядеть относительно SubClass, в B.jar. Я подозреваю, что обычно этого не требуется.

Лично я, вероятно, использовал бы Foo.class.getResourceAsStream(name)чаще всего - если вы уже знаете имя ресурса, который вам нужен, и вы уверены, где оно относительно Foo, это самый надежный способ сделать это, ИМО.

Конечно, бывают случаи, когда вы тоже этого не хотите: судите каждое дело по существу. Просто «Я знаю, что этот ресурс связан с этим классом» - самый распространенный вопрос, с которым я сталкивался.


skeet: сомнение в утверждении «вы выполняете код в методе экземпляра суперкласса, но где это относится к экземпляру подкласса», если мы выполняем операторы внутри метода экземпляра суперкласса, тогда «this» будет относиться к суперклассу, а не подкласс.
Dead Programmer

1
@Suresh: Нет, не будет. Попытайся! Создайте два класса, сделав один производным от другого, а затем распечатайте суперкласс this.getClass(). Создайте экземпляр подкласса и вызовите метод ... он распечатает имя подкласса, а не суперкласса.
Джон Скит

Благодаря методу экземпляра подкласса вызывается метод суперкласса.
Мертвый программист

1
Что мне интересно, так это то, сможет ли использование this.getResourceAsStream загружать ресурс только из той же банки, что и этот класс, а не из другой банки. По моим подсчетам, именно загрузчик классов загружает ресурс и наверняка не будет ограничен в загрузке только из одной банки?
Майкл Уайлс,

10

Я ищу в трех местах, как показано ниже. Комментарии приветствуются.

public URL getResource(String resource){

    URL url ;

    //Try with the Thread Context Loader. 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Let's now try with the classloader that loaded this class.
    classLoader = Loader.class.getClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Last ditch attempt. Get the resource from the classpath.
    return ClassLoader.getSystemResource(resource);
}

Спасибо, это отличная идея. Как раз то, что мне было нужно.
Dev

2
Я смотрел комментарии в вашем коде, и последний из них звучит интересно. Не все ли ресурсы загружены из пути к классам? И в каких случаях ClassLoader.getSystemResource () покрывает, что вышеперечисленное не удалось?
nyxz

Честно говоря, я не понимаю, зачем вам загружать файлы из трех разных мест. Разве вы не знаете, где хранятся ваши файлы?
bvdb 07

3

Я знаю, что уже поздно для другого ответа, но я просто хотел поделиться тем, что помогло мне в конце. Он также будет загружать ресурсы / файлы из абсолютного пути файловой системы (а не только из пути к классам).

public class ResourceLoader {

    public static URL getResource(String resource) {
        final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
        classLoaders.add(Thread.currentThread().getContextClassLoader());
        classLoaders.add(ResourceLoader.class.getClassLoader());

        for (ClassLoader classLoader : classLoaders) {
            final URL url = getResourceWith(classLoader, resource);
            if (url != null) {
                return url;
            }
        }

        final URL systemResource = ClassLoader.getSystemResource(resource);
        if (systemResource != null) {
            return systemResource;
        } else {
            try {
                return new File(resource).toURI().toURL();
            } catch (MalformedURLException e) {
                return null;
            }
        }
    }

    private static URL getResourceWith(ClassLoader classLoader, String resource) {
        if (classLoader != null) {
            return classLoader.getResource(resource);
        }
        return null;
    }

}

0

Я пробовал много способов и функций, которые предлагались выше, но они не работали в моем проекте. В любом случае я нашел решение, и вот оно:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}

Вам лучше использовать this.getClass().getResourceAsStream()в этом случае. Если вы посмотрите на источник getResourceAsStreamметода, вы заметите, что он делает то же самое, что и вы, но более разумным способом (откат, если его нет, ClassLoaderможно найти в классе). Это также указывает на то, что вы можете столкнуться с потенциалом nullна getClassLoaderв вашем коде ...
Doc Davluz

@PromCompot, как я уже сказал this.getClass().getResourceAsStream(), у меня не работает, поэтому я использую это работает. Я думаю, что есть люди, которые могут столкнуться с такой проблемой, как моя.
Владислав
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.