getResourceAsStream trả về null


183

Tôi đang tải một tệp văn bản từ trong một gói trong JAR đã biên dịch của dự án Java của tôi. Cấu trúc thư mục có liên quan như sau:

/src/initialization/Lifepaths.txt

Mã của tôi tải một tập tin bằng cách gọi Class::getResourceAsStreamđể trả về a InputStream.

public class Lifepaths {
    public static void execute() {
        System.out.println(Lifepaths.class.getClass().
            getResourceAsStream("/initialization/Lifepaths.txt"));
    }

    private Lifepaths() {}

    //This is temporary; will eventually be called from outside
    public static void main(String[] args) {execute();}
}

Bản in ra sẽ luôn luôn in null, bất kể tôi sử dụng cái gì. Tôi không chắc tại sao những thứ trên không hoạt động, vì vậy tôi cũng đã thử:

  • "/src/initialization/Lifepaths.txt"
  • "initialization/Lifepaths.txt"
  • "Lifepaths.txt"

Cả hai công việc này. Tôi đã đọc rất nhiều câu hỏi cho đến nay về chủ đề này, nhưng không có câu hỏi nào hữu ích - thông thường, họ chỉ nói để tải các tệp bằng đường dẫn gốc, điều mà tôi đã làm. Điều đó, hoặc chỉ tải tệp từ thư mục hiện tại (chỉ tải filename), mà tôi cũng đã thử. Tệp đang được biên dịch vào JAR ở vị trí thích hợp với tên thích hợp.

Tôi giải quyết điều này như thế nào?


3
Bạn đã kiểm tra rằng nó thực sự trong tập tin jar? Bạn đã kiểm tra vỏ tập tin?
Jon Skeet

@JonSkeet Nó thực sự được biên dịch vào tệp JAR ở vị trí thích hợp và trường hợp này là chính xác.

1
@greedybuddha Trong khi tôi không thể gọi nó từ ngữ cảnh tĩnh, tôi có thể gọi nó bằng cách sử dụng Lifepaths.class. Điều đó đang được nói, tại sao nó getClassLoader()cho phép nó hoạt động? (Ngoài ra, vui lòng gửi câu trả lời!)

Bạn có thể cho thấy Lifepaths.getClass()? Không có phương thức tĩnh nào được định nghĩa trong Object ...
Puce

1
Có một cái nhìn vào câu trả lời này và xem nếu bạn có thể làm cho nó hoạt động bằng cách sử dụng getResource(String). BTW - Tôi luôn gặp vấn đề khi một trong hai người đó làm việc trong một staticbối cảnh. Vấn đề cơ bản là trình nạp lớp thu được là cái dành cho các lớp J2SE. Bạn cần có quyền truy cập vào trình nạp lớp ngữ cảnh dành cho chính ứng dụng.
Andrew Thompson

Câu trả lời:


159

Lifepaths.class.getClass().getResourceAsStream(...) tải tài nguyên bằng trình tải lớp hệ thống, rõ ràng là không thành công vì không thấy JAR của bạn

Lifepaths.class.getResourceAsStream(...) tải tài nguyên bằng cách sử dụng cùng một trình nạp lớp đã tải lớp Lifepaths và nó sẽ có quyền truy cập vào các tài nguyên trong JAR của bạn


129
Chỉ cần thêm vào, Khi gọi getResourceAsStream (tên), tên phải bắt đầu bằng "/". Tôi không chắc chắn liệu điều này là cần thiết, nhưng tôi có vấn đề mà không có nó.
David

3
Tôi đã vặn vẹo chuyện này từ 8h sáng nay / sáng hôm qua. Cứu tôi. Tôi cũng cần một dấu gạch chéo hàng đầu để làm cho nó hoạt động.
kyle

4
Ngoài ra, hãy nhớ rằng nguồn mong muốn có thể nằm ngoài hệ thống phân cấp gói. Trong trường hợp này, bạn sẽ phải sử dụng "../" trong đường dẫn của mình để lên một cấp và sau đó xuống một nhánh đường dẫn khác để tiếp cận tài nguyên của bạn.
Zon

3
@David - Tôi nghĩ rằng nó (hàng đầu '/') là cần thiết nếu không nó tìm kiếm liên quan đến gói Lifepaths. Class
Mz A

5
Chỉ cần thêm một số thông tin, bạn cần thêm một /trước đường dẫn nếu tệp của bạn nằm trong một thư mục khác; ví dụ initialization/Lifepaths.txt. Nếu đường dẫn của tệp giống với lớp yout (nhưng dưới tài nguyên là thư mục chính), bạn chỉ có thể đặt tên của tệp mà không cần bất kỳ /. Ví dụ: nếu lớp của bạn có đường dẫn sau src/main/java/paths/Lifepaths.java, tệp của bạn phải có đường dẫn này src/main/resources/paths/Lifepaths.txt.
Dwhitz

59

Luật như sau:

  1. kiểm tra vị trí của tệp bạn muốn tải bên trong JAR (và do đó cũng đảm bảo rằng nó thực sự được thêm vào JAR)
  2. sử dụng một đường dẫn tuyệt đối: đường dẫn bắt đầu từ gốc của JAR
  3. sử dụng một đường dẫn tương đối: đường dẫn bắt đầu tại thư mục gói của lớp bạn đang gọi getResource / getResoucreAsStream

Và cố gắng:

Lifepaths.class.getResourceAsStream("/initialization/Lifepaths.txt")

thay vì

Lifepaths.class.getClass().getResourceAsStream("/initialization/Lifepaths.txt")

(không chắc nó có tạo ra sự khác biệt không, nhưng cái trước sẽ sử dụng ClassLoader / JAR chính xác, trong khi tôi không chắc chắn với cái sau)


2
Tôi đã thực hiện cả ba điều này. Vui lòng đọc lại câu hỏi của tôi.

Từ câu hỏi của bạn, không rõ "Cấu trúc thư mục có liên quan" là gì và nếu bạn thực sự đã kiểm tra xem và vị trí của tệp trong JAR (bước 1)
Puce

1
Nhận xét của bạn về con đường tương đối cuối cùng đã giải quyết vấn đề ở cuối của tôi; cảm ơn!
Paul Bormans

Hấp dẫn. Hóa ra tôi đã phải làm "/config.properies" (với một dấu gạch chéo) để đến đó ...
Erk

45

Vì vậy, có một số cách để lấy tài nguyên từ một jar và mỗi cách có cú pháp hơi khác nhau trong đó đường dẫn cần được chỉ định khác nhau.

Giải thích tốt nhất tôi đã thấy là bài viết này từ JavaWorld . Tôi sẽ tóm tắt ở đây, nhưng nếu bạn muốn biết thêm, bạn nên xem bài viết.

Phương pháp

1) ClassLoader.getResourceAsStream().

Định dạng: "/" - tên riêng biệt; không có "/" hàng đầu (tất cả các tên là tuyệt đối).

Thí dụ: this.getClass().getClassLoader().getResourceAsStream("some/pkg/resource.properties");

2) Class.getResourceAsStream()

Định dạng: "/" - tên riêng biệt; hàng đầu "/" chỉ tên tuyệt đối; tất cả các tên khác đều liên quan đến gói của lớp

Thí dụ: this.getClass().getResourceAsStream("/some/pkg/resource.properties");


ví dụ thứ hai của bạn bị hỏng
Janus Troelsen

1
Ví dụ thứ hai không bị hỏng, như tôi đã nói trong câu trả lời, nó phụ thuộc vào vị trí của tài nguyên.
greedybuddha

Ngoài ra: đảm bảo rằng IDE của bạn nhìn thấy tệp ('some / pkg / resource.properIES') bằng cách làm mới thư mục nguồn.
Eric Duminil

15

Đừng sử dụng các đường dẫn tuyệt đối, làm cho chúng liên quan đến thư mục 'tài nguyên' trong dự án của bạn. Mã nhanh và bẩn hiển thị nội dung của MyTest.txt từ thư mục 'tài nguyên'.

@Test
public void testDefaultResource() {
    // can we see default resources
    BufferedInputStream result = (BufferedInputStream) 
         Config.class.getClassLoader().getResourceAsStream("MyTest.txt");
    byte [] b = new byte[256];
    int val = 0;
    String txt = null;
    do {
        try {
            val = result.read(b);
            if (val > 0) {
                txt += new String(b, 0, val);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
    } while (val > -1);
    System.out.println(txt);
}


8

Dường như có vấn đề với ClassLoader mà bạn đang sử dụng. Sử dụng contextClassLoader để tải lớp. Điều này không liên quan đến việc nó có trong phương thức tĩnh / không tĩnh không

Thread.currentThread().getContextClassLoader().getResourceAsStream......


6

Tôi thấy mình trong một vấn đề tương tự. Vì tôi đang sử dụng maven, tôi cần cập nhật tệp pom.xml của mình để bao gồm một cái gì đó như thế này:

   ...
</dependencies>
<build>
    <resources>
        <resource>
            <directory>/src/main/resources</directory>
        </resource>
        <resource>
            <directory>../src/main/resources</directory>
        </resource>
    </resources>
    <pluginManagement>
        ...

Lưu ý thẻ tài nguyên trong đó để xác định thư mục đó ở đâu. Nếu bạn có các dự án lồng nhau (như tôi làm) thì bạn có thể muốn lấy tài nguyên từ các khu vực khác thay vì chỉ trong mô-đun bạn đang làm việc. Điều này giúp giảm việc giữ cùng một tệp trong mỗi repo nếu bạn đang sử dụng dữ liệu cấu hình tương tự


3

Điều làm việc cho tôi là thêm tập tin bên dưới My Project/Java Resources/srcvà sau đó sử dụng

this.getClass().getClassLoader().getResourceAsStream("myfile.txt");

Tôi không cần phải thêm rõ ràng tệp này vào đường dẫn (thêm nó vào để /srclàm điều đó rõ ràng)


Cú pháp java sai
Ilya Kharlamov

2

Không biết có giúp được gì không, nhưng trong trường hợp của tôi, tôi đã có tài nguyên của mình trong thư mục / src / và đã gặp lỗi này. Sau đó tôi đã chuyển hình ảnh vào thư mục bin và nó đã khắc phục vấn đề.


2

Đảm bảo thư mục tài nguyên của bạn (ví dụ "src") nằm trong đường dẫn lớp của bạn (đảm bảo rằng đó là thư mục nguồn trong đường dẫn xây dựng của bạn trong nhật thực).

Hãy chắc chắn rằng clazz được tải từ trình nạp lớp chính.

Sau đó, để tải src / khởi tạo / Lifepaths.txt, hãy sử dụng

clazz.getResourceAsStream("/initialization/Lifepaths.txt");

Tại sao: clazz.getResourcesAsStream(foo)tìm kiếm foo từ trong đường dẫn của clazz , liên quan đến thư mục clazz sống trong . "/" Hàng đầu làm cho nó tải từ thư mục gốc của bất kỳ thư mục nào trong đường dẫn lớp của clazz.

Trừ khi bạn đang ở trong một loại nào đó, như Tomcat, hoặc đang trực tiếp làm gì đó với ClassLoaders, bạn chỉ có thể coi đường dẫn lớp nhật thực / dòng lệnh của bạn như là đường dẫn lớp của trình nạp lớp duy nhất.


2

Trình nạp lớp JVM mặc định sẽ sử dụng trình nạp lớp cha để tải tài nguyên trước: deletegate-Parent-classloader .

Lifepaths.class.getClass()Trình tải lớp là bootstrap classloader, vì vậy getResourceAsStreamsẽ chỉ tìm kiếm $ JAVA_HOME, bất kể người dùng cung cấpclasspath . Rõ ràng, Lifepaths.txt không có ở đó.

Lifepaths.classTrình nạp lớp là system classpath classloader, vì vậy getResourceAsStreamsẽ tìm kiếm do người dùng định nghĩa classpathvà Lifepaths.txt ở đó.

Khi sử dụng java.lang.Class#getResourceAsStream(String name), tên không bắt đầu bằng '/' sẽ được thêm bằng package nametiền tố. Nếu bạn muốn tránh điều này, xin vui lòng sử dụng java.lang.ClassLoader#getResourceAsStream. Ví dụ:

ClassLoader loader = Thread.currentThread().getContextClassLoader();
String resourceName = "Lifepaths.txt";
InputStream resourceStream = loader.getResourceAsStream(resourceName); 

2

Nói đại khái:

getClass().getResource("/") ~ = Thread.currentThread().getContextClassLoader().getResource(".")

Giả sử cấu trúc dự án của bạn giống như sau:

├── src
   ├── main
   └── test
   └── test
       ├── java
          └── com
              └── github
                  └── xyz
                      └── proj
                          ├── MainTest.java
                          └── TestBase.java
       └── resources
           └── abcd.txt
└── target
    └── test-classes
        ├── com
        └── abcd.txt
// in MainClass.java
this.getClass.getResource("/") -> "~/proj_dir/target/test-classes/"
this.getClass.getResource(".") -> "~/proj_dir/target/test-classes/com/github/xyz/proj/"
Thread.currentThread().getContextClassLoader().getResources(".") -> "~/proj_dir/target/test-classes/"
Thread.currentThread().getContextClassLoader().getResources("/") ->  null

2

nếu bạn đang sử dụng Maven, hãy đảm bảo rằng bao bì của bạn là 'jar' chứ không phải 'pom'.

<packaging>jar</packaging>

0

Những gì bạn thực sự cần là một classPath tuyệt đối đầy đủ cho tệp. Vì vậy, thay vì đoán nó, hãy thử tìm hiểu ROOT và sau đó di chuyển tệp đến vị trí cơ sở tốt hơn một cấu trúc tệp <.war> ...

URL test1 = getClass().getResource("/");
URL test2 = getClass().getClassLoader().getResource("/");            
URL test3 = getClass().getClassLoader().getResource("../");

logger.info(test1.getPath()); 
logger.info(test2.getPath());
logger.info(test3.getPath());

0

Những gì làm việc cho tôi là tôi đặt các tập tin dưới

src/main/java/myfile.log

InputStream is = getClass().getClassLoader().getResourceAsStream("myfile.log");
        
        if (is == null) {
            throw new FileNotFoundException("Log file not provided");
        }

Thư mục nguồn được gọi src/main/JAVA, rõ ràng các tệp không phải mã của bạn không nên được đặt ở đây.
leyren

-12

@Emracool ... Tôi muốn đề xuất cho bạn một giải pháp thay thế. Vì dường như bạn đang cố tải một tệp * .txt. Tốt hơn để sử dụng FileInputStream()thay vì điều này gây phiền nhiễu getClass().getClassLoader().getResourceAsStream()hoặc getClass().getResourceAsStream(). Ít nhất mã của bạn sẽ thực thi đúng.


gì ??? -1 cho câu trả lời làm việc như vậy. Không có vấn đề gì. Nhưng giải pháp đề xuất ở trên sẽ làm việc chắc chắn.
Jain
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.