วิธีที่ต้องการในการโหลดทรัพยากรใน 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" หากคุณใส่ "/" ไว้ข้างหน้าแสดงว่าคุณกำลังอ้างถึงทรัพยากรอย่างแน่นอน

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 ไม่สำคัญ แต่เป็น classpath ของ class loader ที่ทำ
Bart van Heukelom

@Bart ถ้าคุณดูซอร์สโค้ดคุณจะสังเกตเห็นว่าชื่อคลาสมีความสำคัญเมื่อคุณเรียก getResource ในคลาส สิ่งแรกที่การเรียกนี้เรียกว่า "editName" ซึ่งจะเพิ่มคำนำหน้าแพ็คเกจตามความเหมาะสม Javadoc สำหรับ editName คือ "เพิ่มคำนำหน้าชื่อแพ็กเกจหากชื่อไม่ใช่ค่าสัมบูรณ์ Remove นำหน้า" / "if name is absolute"
Michael Wiles

10
ฉันเห็น ค่าสัมบูรณ์ในที่นี้หมายถึงเมื่อเทียบกับ classpath แทนที่จะเป็นระบบไฟล์แบบสัมบูรณ์
Bart van Heukelom

1
ฉันแค่อยากจะเพิ่มว่าคุณควรตรวจสอบเสมอว่าสตรีมที่ส่งคืนจาก getResourceAsStream () ไม่เป็นโมฆะเพราะจะเกิดขึ้นหากทรัพยากรไม่อยู่ใน classpath
stenix

นอกจากนี้ยังเป็นที่น่าสังเกตคือการใช้รถตักดินชั้นบริบทช่วยให้ ClassLoader Thread#setContextClassLoaderที่จะมีการเปลี่ยนแปลงที่รันไทม์ผ่าน สิ่งนี้มีประโยชน์หากคุณต้องการแก้ไข classpath ในขณะที่โปรแกรมกำลังทำงาน
สูงสุด

14

ดีก็ส่วนหนึ่งขึ้นอยู่กับสิ่งที่คุณต้องการให้เกิดขึ้นถ้าคุณเป็นจริงในชั้นเรียนมา

ตัวอย่างเช่นสมมติว่าSuperClassอยู่ใน A.jar และSubClassอยู่ใน B.jar และคุณกำลังเรียกใช้โค้ดในวิธีการอินสแตนซ์ที่ประกาศไว้SuperClassแต่thisอ้างถึงอินสแตนซ์ของSubClass. ถ้าคุณใช้this.getClass().getResource()มันจะดูสัมพันธ์กับSubClassใน B. jar ฉันสงสัยว่ามักจะไม่ใช่สิ่งที่จำเป็น

โดยส่วนตัวแล้วฉันอาจใช้Foo.class.getResourceAsStream(name)บ่อยที่สุด - ถ้าคุณรู้จักชื่อของทรัพยากรที่คุณตามมาแล้วและคุณแน่ใจว่าเกี่ยวข้องกับที่Fooใดนั่นเป็นวิธีที่มีประสิทธิภาพมากที่สุดในการทำ IMO

แน่นอนว่ามีหลายครั้งที่ไม่ใช่สิ่งที่คุณต้องการเช่นกัน: ตัดสินแต่ละกรณีด้วยข้อดีของมัน เป็นเพียง "ฉันรู้ว่าทรัพยากรนี้รวมอยู่ในชั้นเรียนนี้" เป็นทรัพยากรที่พบบ่อยที่สุดที่ฉันพบ


skeet: มีข้อสงสัยในคำสั่ง "คุณกำลังเรียกใช้โค้ดในวิธีการอินสแตนซ์ของ SuperClass แต่ในกรณีนี้หมายถึงอินสแตนซ์ของคลาสย่อย" ถ้าเรากำลังเรียกใช้คำสั่งภายในวิธีการอินสแตนซ์ของซูเปอร์คลาส "สิ่งนี้" จะหมายถึงซูเปอร์คลาสไม่ใช่ คลาสย่อย
Dead Programmer

1
@Suresh: ไม่มันจะไม่ ลองมัน! สร้างสองชั้นทำให้หนึ่งเป็นผลมาจากที่อื่น ๆ และจากนั้นใน superclass this.getClass()พิมพ์ออกมา สร้างอินสแตนซ์ของคลาสย่อยและเรียกใช้เมธอด ... มันจะพิมพ์ชื่อของคลาสย่อยไม่ใช่ซูเปอร์คลาส
จอน Skeet

ขอบคุณวิธีการอินสแตนซ์คลาสย่อยเรียกวิธีการของซูเปอร์คลาส
Dead Programmer

1
สิ่งที่ฉันสงสัยคือการใช้ this.getResourceAsStream จะสามารถโหลดทรัพยากรจาก jar เดียวกับที่ clas นี้มาจาก jar อื่นได้หรือไม่ จากการคำนวณของฉันมันเป็นตัวโหลดคลาสที่โหลดทรัพยากรและจะไม่ถูก จำกัด จากการโหลดจากโถเดียวอย่างแน่นอน?
Michael Wiles

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

พูดตามตรงฉันไม่เข้าใจว่าทำไมคุณถึงต้องการโหลดไฟล์จาก 3 ที่ที่แตกต่างกัน คุณไม่รู้ว่าไฟล์ของคุณถูกเก็บไว้ที่ไหน?
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ในรหัสของคุณ ...
หมอ Davluz

@PromCompot อย่างที่บอกว่าใช้this.getClass().getResourceAsStream()ไม่ได้สำหรับฉันดังนั้นฉันจึงใช้มันได้ผล ฉันคิดว่ามีบางคนที่สามารถเผชิญกับปัญหาเช่นเดียวกับฉัน
Vladislav
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.