Cách tải tài nguyên ưa thích trong Java


107

Tôi muốn biết cách tốt nhất để tải tài nguyên trong Java:

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

Câu trả lời:


140

Tìm ra giải pháp theo những gì bạn muốn ...

Có hai thứ mà getResource/ getResourceAsStream()sẽ nhận được từ lớp mà nó được gọi là ...

  1. Trình tải lớp
  2. Vị trí bắt đầu

Vì vậy, nếu bạn làm

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

nó sẽ cố gắng tải foo.txt từ cùng một gói với lớp "this" và với trình tải lớp của lớp "this". Nếu bạn đặt dấu "/" ở phía trước thì bạn hoàn toàn đang tham chiếu đến tài nguyên.

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

sẽ tải tài nguyên từ trình tải lớp của "this" và từ gói xyz (nó sẽ cần ở cùng thư mục với các lớp trong gói đó).

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

sẽ tải bằng trình tải lớp ngữ cảnh nhưng sẽ không phân giải tên theo bất kỳ gói nào (nó phải được tham chiếu tuyệt đối)

System.class.getResource(name)

Sẽ tải tài nguyên bằng trình tải lớp hệ thống (nó cũng phải được tham chiếu tuyệt đối, vì bạn sẽ không thể đưa bất cứ thứ gì vào gói java.lang (gói của Hệ thống).

Chỉ cần nhìn vào nguồn. Cũng chỉ ra rằng getResourceAsStream chỉ gọi "openStream" trên URL được trả về từ getResource và trả về điều đó.


Tên các gói AFAIK không quan trọng, đó là classpath của trình tải lớp.
Bart van Heukelom

@Bart nếu bạn nhìn vào mã nguồn, bạn sẽ nhận thấy rằng tên lớp rất quan trọng khi bạn gọi getResource trên một lớp. Điều đầu tiên mà cuộc gọi này thực hiện là gọi "ResolutionName" sẽ thêm tiền tố gói nếu thích hợp. Javadoc cho resolveName là "Thêm một tiền tố tên gói nếu tên không phải là tuyệt đối Remove hàng đầu '/' nếu tên là tuyệt đối"
Michael Wiles

10
Ah tôi thấy. Tuyệt đối ở đây có nghĩa là liên quan đến classpath, chứ không phải là tuyệt đối của hệ thống tệp.
Bart van Heukelom

1
Tôi chỉ muốn nói thêm rằng bạn nên luôn kiểm tra xem luồng được trả về từ getResourceAsStream () không phải là null, bởi vì nó sẽ như vậy nếu tài nguyên không nằm trong classpath.
stenix

Cũng cần lưu ý rằng việc sử dụng trình nạp lớp ngữ cảnh cho phép thay đổi trình nạp lớp trong thời gian chạy Thread#setContextClassLoader. Điều này hữu ích nếu bạn cần sửa đổi classpath trong khi chương trình đang thực thi.
Tối đa

14

Chà, nó một phần phụ thuộc vào những gì bạn muốn xảy ra nếu bạn thực sự đang ở trong một lớp dẫn xuất.

Ví dụ: giả sử SuperClasslà trong A.jar và SubClasstrong B.jar, và bạn đang thực thi mã trong một phương thức thể hiện được khai báo trong SuperClassnhưng nơi thistham chiếu đến một thể hiện của SubClass. Nếu bạn sử dụng this.getClass().getResource()nó sẽ trông tương đối với SubClass, trong B.jar. Tôi nghi ngờ đó thường không phải là những gì cần thiết.

Cá nhân tôi có lẽ sẽ sử dụng Foo.class.getResourceAsStream(name)thường xuyên nhất - nếu bạn đã biết tên của tài nguyên bạn đang theo dõi và bạn chắc chắn về vị trí liên quan của nó, thì đó là Foocách hiệu quả nhất IMO.

Tất nhiên cũng có lúc đó không phải là điều bạn muốn: hãy đánh giá từng trường hợp dựa trên giá trị của nó. Chỉ là "Tôi biết tài nguyên này được đóng gói với lớp này" là câu phổ biến nhất mà tôi gặp phải.


xiên: nghi ngờ trong câu lệnh "bạn đang thực thi mã trong một phương thức thể hiện của SuperClass nhưng trong đó điều này đề cập đến một thể hiện của SubClass" nếu chúng ta đang thực thi các câu lệnh bên trong phương thức thể hiện của siêu lớp thì "this" sẽ tham chiếu đến lớp cha không lớp con.
Dead Programmer

1
@Suresh: Không, sẽ không. Thử nó! Tạo hai lớp, làm cho một lớp dẫn xuất từ ​​lớp kia, rồi in ra lớp cha trong lớp cha this.getClass(). Tạo một thể hiện của lớp con và gọi phương thức ... nó sẽ in ra tên của lớp con, không phải lớp cha.
Jon Skeet

thanks phương thức thể hiện lớp con gọi phương thức của lớp cha.
Dead Programmer

1
Điều tôi băn khoăn là liệu việc sử dụng this.getResourceAsStream sẽ chỉ có thể tải một tài nguyên từ cùng một jar như clas này đến từ chứ không phải từ một jar khác. Theo tính toán của tôi, đó là trình nạp lớp tải tài nguyên và chắc chắn sẽ không bị hạn chế tải chỉ từ một jar?
Michael Wiles

10

Tôi tìm kiếm ba nơi như hình dưới đây. Bình luận được chào đón.

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);
}

Cảm ơn, đây là một ý tưởng tuyệt vời. Đúng thứ tôi cần.
sùng

2
Tôi đã xem các bình luận trong mã của bạn và bình luận cuối cùng nghe có vẻ thú vị. Không phải tất cả tài nguyên đều được tải từ classpath? Và những trường hợp nào mà ClassLoader.getSystemResource () sẽ đề cập đến mà ở trên không thành công?
nyxz

Thành thật mà nói, tôi không hiểu tại sao bạn lại muốn tải tệp từ 3 nơi khác nhau. Bạn không biết nơi lưu trữ các tệp của mình?
bvdb

3

Tôi biết nó thực sự muộn cho một câu trả lời khác nhưng tôi chỉ muốn chia sẻ những gì đã giúp tôi cuối cùng. Nó cũng sẽ tải tài nguyên / tệp từ đường dẫn tuyệt đối của hệ thống tệp (không chỉ đường dẫn của classpath).

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

Tôi đã thử rất nhiều cách và chức năng được đề xuất ở trên, nhưng chúng không hoạt động trong dự án của tôi. Dù sao thì tôi cũng đã tìm ra giải pháp và đây là:

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

Tốt hơn bạn nên sử dụng this.getClass().getResourceAsStream()trong trường hợp này. Nếu bạn nhìn vào nguồn của getResourceAsStreamphương thức, bạn sẽ nhận thấy rằng nó làm điều tương tự như bạn nhưng theo cách thông minh hơn (dự phòng nếu không ClassLoadercó trên lớp). Nó cũng chỉ ra rằng bạn có thể gặp phải một tiềm năng nulltrên getClassLoadermã của mình…
Doc Davluz

@PromCompot, như tôi đã nói this.getClass().getResourceAsStream()không hoạt động với tôi, vì vậy tôi sử dụng nó hoạt động. Tôi nghĩ rằng có một số người có thể đối mặt với vấn đề như tôi.
Vladislav
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.