Бажаний спосіб завантаження ресурсів на 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.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 не мають значення, це саме класний шлях завантажувача класів.
Барт ван Хекелом

@Bart, якщо ви подивитеся на вихідний код, ви помітите, що ім'я класу має значення, коли ви викликаєте getResource в класі. Перше, що робить цей виклик - це виклик "resolutionName", який додає префікс пакета, якщо це доречно. Javadoc для resolutionName - "Додати префікс імені пакета, якщо ім'я не є абсолютним. Видалити ведуче" / ", якщо ім'я абсолютне"
Michael Wiles

10
А, бачу. Абсолют тут означає відносно класового шляху, а не абсолют файлової системи.
Барт ван Хекелом

1
Я просто хочу додати, що ви завжди повинні перевірити, що потік, що повернувся з getResourceAsStream (), не є нульовим, тому що це буде, якщо ресурс не знаходиться в межах класного шляху.
стенікс

Також варто відзначити, що використання завантажувача контекстного класу дозволяє змінювати завантажувач класів під час виконання через Thread#setContextClassLoader. Це корисно, якщо вам потрібно змінити шлях класу під час виконання програми.
Макс

14

Що ж, це частково залежить від того, що ви хочете статися, якщо ви насправді в похідному класі.

Наприклад, припустимо SuperClass в A.jar і SubClassу B.jar, і ви виконуєте код у методі екземпляра, оголошеному в, SuperClassале де thisпосилається на екземпляр SubClass. Якщо ви користуєтеся, this.getClass().getResource()він виглядатиме відносно SubClass, у B.jar. Я підозрюю, що зазвичай це не те, що потрібно.

Особисто я, мабуть, використовую Foo.class.getResourceAsStream(name)найчастіше - якщо ви вже знаєте назву ресурсу, за яким ви шукаєте, і ви впевнені, де це стосується Foo, це найбільш надійний спосіб зробити це ІМО.

Звичайно, бувають випадки, коли це не те, чого ти хочеш: оцінюй кожну справу по суті. Просто "я знаю, що цей ресурс є в комплекті з цим класом" - це найпоширеніший, з яким я стикався.


skeet: сумнів у твердженні "ви виконуєте код в екземплярі методу SuperClass, але де це стосується екземпляра SubClass", якщо ми виконуємо оператори всередині методу екземпляра суперкласу, тоді "це" буде посилатися на суперклас не підкласу.
Мертвий програміст

1
@ Суреш: Ні, це не буде. Спробуй це! Створіть два класи, зробивши один похідним від іншого, а потім у роздрукуванні надкласового класу 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);
}

Дякую, це чудова ідея. Тільки те, що мені було потрібно.
devo

2
Я дивився на коментарі у вашому коді, і останній звучить цікаво. Чи не всі ресурси завантажені з classpath? І які випадки ClassLoader.getSystemResource () охоплюють, щоб вищезгадане не вдалося?
nyxz

Чесно кажучи, я не розумію, чому ви хочете завантажити файли з трьох різних місць. Ви не знаєте, де зберігаються ваші файли?
bvdb

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.