Class.getResource () และ ClassLoader.getResource () แตกต่างกันอย่างไร?


194

ฉันสงสัยว่าความแตกต่างระหว่างClass.getResource()และเป็นClassLoader.getResource()อย่างไร

แก้ไข: ฉันต้องการทราบเป็นพิเศษหากแคชใด ๆ ที่เกี่ยวข้องในระดับไฟล์ / ไดเรกทอรี เช่นเดียวกับใน "รายการไดเรกทอรีถูกแคชในรุ่น Class?"

AFAIK ต่อไปนี้ควรทำเช่นเดียวกัน แต่ไม่:

getClass().getResource() 
getClass().getClassLoader().getResource()

ฉันค้นพบสิ่งนี้เมื่อเล่นซอกับรหัสการสร้างรายงานที่สร้างไฟล์ใหม่WEB-INF/classes/จากไฟล์ที่มีอยู่ในไดเรกทอรีนั้น เมื่อใช้วิธีการจากชั้นเรียนฉันสามารถค้นหาไฟล์ที่มีเมื่อใช้งานโดยใช้getClass().getResource()แต่เมื่อพยายามดึงไฟล์ที่สร้างขึ้นใหม่ฉันได้รับวัตถุที่ไม่มีค่า การเรียกดูไดเรกทอรีอย่างชัดเจนแสดงว่ามีไฟล์ใหม่ ชื่อไฟล์ถูกต่อท้ายด้วยเครื่องหมายสแลชเช่นเดียวกับใน "/myFile.txt"

ในทางกลับกันClassLoaderเวอร์ชั่นของเวอร์ชั่นgetResource()ก็พบไฟล์ที่สร้างขึ้น จากประสบการณ์นี้ดูเหมือนว่าจะมีการแคชรายการไดเรกทอรีที่เกิดขึ้น ฉันถูกและถ้าเป็นเช่นนั้นเอกสารนี้อยู่ที่ไหน?

จากAPI เอกสารบนClass.getResource()

ค้นหาทรัพยากรที่มีชื่อที่กำหนด กฎสำหรับการค้นหาทรัพยากรที่เกี่ยวข้องกับคลาสที่กำหนดถูกนำไปใช้โดยการกำหนดคลาสโหลดเดอร์ของคลาส วิธีนี้มอบให้กับ class loader ของวัตถุนี้ หากวัตถุนี้ถูกโหลดโดยตัวโหลดคลาส bootstrap วิธีจะมอบหมายให้ ClassLoader.getSystemResource (java.lang.String)

สำหรับฉันการอ่านนี้ "Class.getResource เรียกชื่อ getResource () ของ classloader ของมันเอง" getClass().getClassLoader().getResource()ซึ่งก็จะเป็นเช่นเดียวกับการทำ แต่เห็นได้ชัดว่าไม่ใช่ มีใครช่วยเล่าเรื่องนี้ให้ฉันฟังหน่อยได้ไหม?

คำตอบ:


6

เพื่อตอบคำถามว่ามีการแคชใด ๆ เกิดขึ้นหรือไม่

ฉันตรวจสอบจุดนี้เพิ่มเติมโดยการรันแอปพลิเคชัน Java แบบสแตนด์อะโลนที่โหลดไฟล์จากดิสก์อย่างต่อเนื่องโดยใช้เมธอด getResourceAsStream ClassLoader ฉันสามารถแก้ไขไฟล์และการเปลี่ยนแปลงดังกล่าวได้ทันทีเช่นไฟล์ถูกโหลดใหม่จากดิสก์โดยไม่แคช

อย่างไรก็ตาม: ฉันกำลังทำงานในโครงการที่มีโมดูล maven หลายรายการและโครงการบนเว็บที่มีการพึ่งพาซึ่งกันและกัน ฉันใช้ IntelliJ เป็น IDE ของฉันในการรวบรวมและเรียกใช้โครงการเว็บ

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


ฉันก็ใช้ Maven และ IntelliJ ด้วยดังนั้นนี่คือคำตอบที่มีสภาพแวดล้อมที่ตรงกับของฉันมากที่สุดและมีคำอธิบายที่สมเหตุสมผลสำหรับคำถาม # 2
oligofren

251

Class.getResourceสามารถใช้ชื่อทรัพยากร "ญาติ" ซึ่งถือว่าเป็นญาติกับแพคเกจของชั้นเรียน หรือคุณสามารถระบุชื่อทรัพยากร "สัมบูรณ์" โดยใช้เครื่องหมายทับหน้า เส้นทางทรัพยากรของ Classloader นั้นจะถือว่าสมบูรณ์เสมอ

ดังนั้นสิ่งต่อไปนี้จึงเทียบเท่ากันโดยทั่วไป:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

และนี่คือ (แต่พวกเขาแตกต่างจากด้านบน):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");

คำตอบที่ดีกับตัวอย่างที่ชัดเจน แม้ว่าจริงๆแล้วโพสต์จะได้คำตอบสำหรับคำถามสองข้อ แต่ฉันเห็นแล้วว่าคำถามที่สองนั้นเป็นคำถามที่ซ่อนอยู่ ค่อนข้างไม่แน่ใจว่าฉันควรจะปรับปรุงโพสต์เพื่อสะท้อนถึงสิ่งนี้หรือไม่ แต่สิ่งที่ฉันอยากรู้ที่สองคือสิ่งนี้ (ความคิดเห็นถัดไป):
oligofren

2
มีการแคชบางอย่างเกิดขึ้นในรุ่น Class.getResource () หรือไม่? สิ่งที่ทำให้ฉันเชื่อว่านี่คือการสร้างรายงานแจสเปอร์บางส่วน: เราใช้ getClass (). getResource ("/ aDocument.jrxml") เพื่อดึงไฟล์ jasper xml ไฟล์ไบนารีแจสเปอร์จะถูกสร้างขึ้นในไดเรกทอรีเดียวกัน getClass (). getResource ("/ aDocument.jasper") ไม่สามารถค้นหาได้แม้ว่าจะสามารถค้นหาเอกสารในระดับเดียวกันได้อย่างชัดเจน (ไฟล์อินพุต) นี่คือที่ ClassLoader.getResource () พิสูจน์แล้วว่ามีประโยชน์เนื่องจากดูเหมือนว่าไม่ได้ใช้แคชของรายการไดเรกทอรี แต่ฉันไม่สามารถหาเอกสารเกี่ยวกับเรื่องนี้
oligofren

2
@oligofren: อืม ... ฉันจะไม่คาดหวัง Class.getResource () เพื่อทำแคชใด ๆ ที่มี ...
จอนสกีต

1
@JonSkeet ทำไมถึงthis.getClass().getClassLoader().getResource("/");ส่งคืน null ไม่ควรเหมือนกับthis.getClass().getClassLoader().getResource(".");
Asif Mushtaq

@ UnKnown: ฉันคิดว่าคุณน่าจะถามคำถามใหม่เกี่ยวกับเรื่องนี้
Jon Skeet

22

การเรียกครั้งแรกจะค้นหาสัมพันธ์กับ.classไฟล์ในขณะที่การค้นหาหลังสัมพันธ์กับรูทคลาสพา ธ

ในการแก้ปัญหาเช่นนั้นฉันพิมพ์ URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );

3
ฉันคิดว่า "classloader root" จะแม่นยำกว่า "classpath root" - เพื่อจะพิถีพิถัน
Jon Skeet

4
ทั้งสองสามารถค้นหา "พา ธ สัมบูรณ์" หากชื่อไฟล์นั้นถูกเติมโดย "/"
oligofren

2
ที่น่าสนใจ ... ฉันกำลังตีกรณีที่ getClass (). getResource ("/ someAbsPath") ส่งคืน URL ในประเภท /path/to/mylib.jar!/someAbsPath และ getClass (). getClassLoafer (" / someAbsPath ") กลับมา null ... ดังนั้น 'รากของ ClassLoader' ดูเหมือนจะไม่เป็นความคิดที่กำหนดไว้เป็นอย่างดี ...
ปิแอร์เฮนรี่


8
@PierreHenry: getClassLoader().getResource("/...")คืนค่าเสมอnull- classloader ไม่ลบส่วนนำ/จากพา ธ ดังนั้นการค้นหาล้มเหลวเสมอ เพียงgetClass().getResource()จัดการเริ่มต้น/เป็นทางญาติที่แน่นอนที่จะคลาสพา ธ
Aaron Digulla

17

ต้องค้นหามันในรายละเอียด:

getResource ของคลาส () - เอกสารระบุความแตกต่าง:

เมธอดนี้มอบหมายการเรียกไปยังคลาสโหลดเดอร์หลังจากทำการเปลี่ยนแปลงเหล่านี้กับชื่อรีซอร์ส: หากชื่อรีซอร์สเริ่มต้นด้วย "/" จะไม่มีการเปลี่ยนแปลง มิฉะนั้นชื่อแพ็กเกจจะต่อท้ายชื่อทรัพยากรหลังจากแปลง "." ถึง "/". หากวัตถุนี้ถูกโหลดโดยตัวโหลด bootstrap การโทรจะมอบหมายให้ ClassLoader.getSystemResource


2
คุณมีข้อมูลใด ๆ เกี่ยวกับว่าแคชเก็บรายการไดเรกทอรีหรือไม่? นี่เป็นข้อแตกต่างที่สำคัญระหว่างสองวิธีเมื่อค้นหาไฟล์อินพุตครั้งแรกจากนั้นสร้างไฟล์โดยใช้ไฟล์นั้นในไดเรกทอรีเดียวกัน รุ่น Class ไม่พบรุ่น ClassLoader ทำ (ทั้งคู่ใช้ "/file.txt")
oligofren

11

คำตอบทั้งหมดเหล่านี้รอบที่นี่เช่นเดียวกับคำตอบในคำถามนี้แสดงให้เห็นว่าการโหลด URL ที่สมบูรณ์เช่น "/foo/bar.properties" ได้รับการรักษาเดียวกันและclass.getResourceAsStream(String) class.getClassLoader().getResourceAsStream(String)นี่ไม่ใช่กรณีอย่างน้อยไม่ได้อยู่ในการกำหนดค่า / รุ่น Tomcat ของฉัน (ปัจจุบัน 7.0.40)

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

ขออภัยฉันไม่มีคำอธิบายที่น่าพอใจอย่างแน่นอน แต่ฉันคิดว่าโพงตัวนี้ใช้กลอุบายสกปรกและเวทมนตร์สีดำของเขากับนักจัดคลาส ฉันเคยใช้class.getResourceAsStream(String)ในอดีตและไม่เคยมีปัญหาใด ๆ

PS: ฉันโพสต์สิ่งนี้ที่นี่


พฤติกรรมนี้ดูเหมือนจะเป็นข้อบกพร่องใน Tomcat ที่ได้รับการแก้ไขในเวอร์ชัน 8 ฉันได้เพิ่มย่อหน้าเกี่ยวกับสิ่งนั้นในคำตอบ
LordOfThePigs

2

Class.getResourcesจะดึงทรัพยากรโดย classloader ซึ่งโหลดวัตถุ ในขณะที่ClassLoader.getResourceจะดึงทรัพยากรโดยใช้ classloader ที่ระบุ


0

ฉันลองอ่านจาก input1.txt ซึ่งอยู่ในแพ็คเกจของฉัน พร้อมกับคลาสที่พยายามอ่าน

ผลงานดังต่อไปนี้:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

ส่วนที่สำคัญที่สุดคือการโทรgetPath()ถ้าคุณต้องการชื่อเส้นทางที่ถูกต้องในรูปแบบ String ไม่ได้ใช้toString()เพราะมันจะเพิ่มข้อความการจัดรูปแบบพิเศษซึ่งจะทำให้ตัวอักษรทั้งหมดขึ้นชื่อไฟล์ (คุณสามารถลองและดูพิมพ์)

ใช้เวลา 2 ชั่วโมงในการดีบั๊กนี้ ... :(


สิ่งที่เกี่ยวกับClass.getResourceAsStream () ?
Lu55

ทรัพยากรไม่ใช่ไฟล์ อาจไม่ได้แตกไฟล์จากไฟล์ JAR หรือ WAR และหากไม่ใช่FileReaderหรือFileInputStreamไม่สามารถใช้เพื่อเข้าถึง คำตอบไม่ถูกต้อง
มาร์ควิสแห่ง Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.