Odczytywanie pliku zasobów z jar

242

Chciałbym przeczytać zasób z mojego słoika w następujący sposób:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));

//Read the file

i działa dobrze podczas uruchamiania go w Eclipse, ale jeśli wyeksportuję go do słoika, uruchom go, pojawi się wyjątek IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

i naprawdę nie wiem dlaczego, ale z pewnymi testami odkryłem, że się zmieniam

file = new File(getClass().getResource("/file.txt").toURI());

do

file = new File(getClass().getResource("/folder/file.txt").toURI());

wtedy działa odwrotnie (działa w słoiku, ale nie zaćmienie).

Korzystam z Eclipse, a folder z moim plikiem znajduje się w folderze zajęć.

PrinceCJC
źródło
Jeśli chcesz czytać pliki z katalogu w słoiku z dowolnymi numerami plików, zobacz Stackoverflow-Link
Michael Hegner
1
Nie jestem pewien, czy pierwotne pytanie dotyczyło wiosny. Link w poprzednim komentarzu odnosi się do odpowiedzi na wiosnę z innego pytania. Uważam, że getResourceAsStreamnadal jest to prostsze i bardziej przenośne rozwiązanie problemu.
Drew MacInnis,

Odpowiedzi:

398

Zamiast próbować adresować zasób jako plik, po prostu poproś ClassLoader, aby zwrócił InputStream dla zasobu zamiast getResourceAsStream :

InputStream in = getClass().getResourceAsStream("/file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Tak długo, jak file.txtzasób jest dostępny w ścieżce klasy, to podejście będzie działać w ten sam sposób, niezależnie od tego, czy file.txtzasób znajduje się w classes/katalogu, czy w jar.

URI is not hierarchicalWystępuje, ponieważ URI zasobu w pliku jar będzie wyglądać następująco: file:/example.jar!/file.txt. Nie można odczytać wpisów w jar( zippliku), jakby to był zwykły stary plik .

Wyjaśnia to dobrze odpowiedzi na:

Drew MacInnis
źródło
3
Dziękuję, to było bardzo pomocne i kod działa idealnie, ale mam jeden problem, muszę ustalić, czy InputStreamistnieje (jak File.exists()), aby moja gra mogła powiedzieć, czy użyć domyślnego pliku, czy nie. Dzięki.
PrinceCJC,
1
Aha i BTW powodem getClass().getResource("**/folder**/file.txt")tego było to, że miałem ten folder w tym samym katalogu, co mój słoik :).
PrinceCJC,
5
getResourceAsStreamzwraca null, jeśli zasób nie istnieje, więc może to być test „istnieje”.
Drew MacInnis,
1
BTW, masz literówkę: powinien to być BufferedReader, a nie BufferredReader (zwróć uwagę na dodatkowe „r” w dalszej części)
mailmindlin
2
I oczywiście ... nie zapomnij zamknąć inputStream i BufferedReader
Noremac
26

Aby uzyskać dostęp do pliku w słoiku, masz dwie opcje:

  • Umieść plik w strukturze katalogów pasującej do nazwy pakietu (po rozpakowaniu pliku .jar powinien on znajdować się w tym samym katalogu co plik .class), a następnie uzyskaj do niego dostęp za pomocą getClass().getResourceAsStream("file.txt")

  • Umieść plik w katalogu głównym (po rozpakowaniu pliku .jar powinien on znajdować się w katalogu głównym), a następnie uzyskaj do niego dostęp za pomocą Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

Pierwsza opcja może nie działać, gdy jar jest używany jako wtyczka.

Juozas Kontvainis
źródło
Bardzo dziękuję za tę świetną odpowiedź ... Kolejną korzyścią z drugiej opcji jest to, że działa ona również z głównej metody, ponieważ getClass () działa tylko z instancji, a nie z metod statycznych.
Dan Ortega
6

Miałem wcześniej ten problem i wróciłem do awaryjnego sposobu ładowania. Zasadniczo pierwszy sposób działa w pliku .jar, a drugi sposób działa w środowisku Eclipse lub innym środowisku IDE.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}
MForm
źródło
3

Do tej pory (grudzień 2017 r.) Jest to jedyne znalezione rozwiązanie, które działa zarówno wewnątrz, jak i na zewnątrz IDE.

Użyj PathMatchingResourcePatternResolver

Uwaga: działa również w wiosennym rozruchu

W tym przykładzie czytam niektóre pliki znajdujące się w folderze src / main / resources / my_folder :

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}
Naor Bar
źródło
3

Problem polega na tym, że niektóre biblioteki stron trzecich wymagają ścieżek plików, a nie strumieni wejściowych. Większość odpowiedzi nie rozwiązuje tego problemu.

W takim przypadku jednym z obejść jest skopiowanie zawartości zasobu do pliku tymczasowego. W poniższym przykładzie użyto jUnit TemporaryFolder.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }
Navneeth
źródło
To jest bardzo dużo. Dzięki za udokumentowanie tego. Jestem ciekawy, jakie inne opcje istnieją bez JUnit.
Alex Moore-Niemi
0

Jeśli chcesz czytać jako plik, uważam, że nadal istnieje podobne rozwiązanie:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());
pablo.vix
źródło
4
URL.getFile () nie konwertuje adresu URL na nazwę pliku. Zwraca część adresu URL po hoście, z nienaruszonym kodowaniem procentowym, więc jeśli ścieżka zawiera znaki spoza ASCII lub znaki ASCII niedozwolone w adresach URL (w tym spacje), wynikiem nie będzie istniejąca nazwa pliku , nawet jeśli adres URL jest file:adresem URL.
VGR
48
to nie działa wewnątrz, gdy program zostanie skompilowany do słoika
Akshay Kasar
1
Nie działa ze słoika, chyba że najpierw skonwertujesz na ciąg i zapiszesz go lokalnie.
smoosh911,
0

Upewnij się, że pracujesz z odpowiednim separatorem. Zamieniłem wszystko /na ścieżce względnej na File.separator. Działa to dobrze w IDE, jednak nie działało w JAR kompilacji.

Petterson
źródło
0

Myślę, że powinno to również działać w java. Poniższy kod, którego używam, to kotlin.

val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')
irvifa
źródło
0

Znalazłem poprawkę

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

Zamień „Main” na klasę Java, w której ją zakodowałeś. Zastąp „path” ścieżką w pliku jar.

na przykład, jeśli umieścisz plik State1.txt w pakiecie com.issac.state, a następnie wpisz ścieżkę jako „/ com / issac / state / State1”, jeśli korzystasz z systemu Linux lub Mac. Jeśli używasz systemu Windows, wpisz ścieżkę jako „\ com \ issac \ state \ State1”. Nie dodawaj rozszerzenia .txt do pliku, chyba że wystąpi wyjątek Nie znaleziono pliku.

zero
źródło
-1

Możesz użyć modułu ładującego, który będzie czytał ścieżkę klasy jako ścieżkę ROOT (bez „/” na początku)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
sendon1982
źródło
-1

Jeśli używasz wiosny, możesz użyć następującej metody, aby odczytać plik z src / main / resources:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }
Sujan M.
źródło
1
Witamy w SO. To nie daje odpowiedzi na pytanie. Gdy zdobędziesz wystarczającą reputację , będziesz mógł komentować dowolny post. Sprawdź również, co mogę zamiast tego zrobić .
thewaywewere
Spowoduje to usunięcie podziałów linii w pliku!
elad.chen
-1

Z jakiegoś powodu classLoader.getResource()zawsze zwracał wartość null, gdy wdrażałem aplikację internetową do WildFly 14. Pobieranie classLoader z getClass().getClassLoader()lub Thread.currentThread().getContextClassLoader()zwraca wartość null.

getClass().getClassLoader() Dokument API mówi:

„Zwraca moduł ładujący klasę. Niektóre implementacje mogą używać wartości null do reprezentowania modułu ładującego klasy bootstrap. Ta metoda zwróci wartość null w takich implementacjach, jeśli ta klasa została załadowana przez moduł ładujący klasy bootstrap.”

może być, jeśli używasz WildFly, a Twoja aplikacja internetowa wypróbuje to

request.getServletContext().getResource()zwrócił adres URL zasobu. Tutaj żądanie jest przedmiotem ServletRequest.

Ram C
źródło
-2

Poniższy kod działa z Spring boot (kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
Bikash Das
źródło