ความแตกต่างระหว่างตัวโหลดคลาสบริบทของเธรดและตัวโหลดคลาสปกติคืออะไร
นั่นคือถ้าThread.currentThread().getContextClassLoader()
และgetClass().getClassLoader()
ส่งคืนคลาสโหลดเดอร์อ็อบเจ็กต์ที่แตกต่างกันอันใดจะถูกใช้?
ความแตกต่างระหว่างตัวโหลดคลาสบริบทของเธรดและตัวโหลดคลาสปกติคืออะไร
นั่นคือถ้าThread.currentThread().getContextClassLoader()
และgetClass().getClassLoader()
ส่งคืนคลาสโหลดเดอร์อ็อบเจ็กต์ที่แตกต่างกันอันใดจะถูกใช้?
คำตอบ:
แต่ละคลาสจะใช้ classloader ของตัวเองเพื่อโหลดคลาสอื่น ๆ ดังนั้นหากClassA.class
การอ้างอิงClassB.class
นั้นClassB
จำเป็นต้องอยู่ใน classpath ของ classloader ของClassA
หรือผู้ปกครอง
classloader บริบทของเธรดเป็น classloader ปัจจุบันสำหรับเธรดปัจจุบัน วัตถุที่สามารถสร้างขึ้นจากชั้นในแล้วส่งผ่านไปยังหัวข้อที่เป็นเจ้าของโดยClassLoaderC
ClassLoaderD
ในกรณีนี้วัตถุต้องใช้Thread.currentThread().getContextClassLoader()
โดยตรงหากต้องการโหลดทรัพยากรที่ไม่พร้อมใช้งานบน classloader ของตนเอง
ClassA.class
การอ้างอิงนั้นหมายถึงClassB.class
อะไร
นี่ไม่ได้ตอบคำถามเดิม แต่เป็นคำถามที่มีการจัดอันดับสูงและเชื่อมโยงกับContextClassLoader
แบบสอบถามใด ๆฉันคิดว่ามันเป็นสิ่งสำคัญที่จะตอบคำถามที่เกี่ยวข้องเมื่อควรใช้คลาสตัวโหลดบริบท คำตอบสั้น ๆ : อย่าใช้คลาสโหลดเดอร์บริบท ! แต่ตั้งเป็นgetClass().getClassLoader()
เมื่อคุณต้องเรียกวิธีการที่ขาดClassLoader
พารามิเตอร์
เมื่อรหัสจากคลาสหนึ่งขอให้โหลดคลาสอื่นตัวโหลดคลาสที่ถูกต้องที่จะใช้เป็นตัวโหลดคลาสเดียวกันกับคลาสตัวเรียก (เช่น, getClass().getClassLoader()
) นี่คือวิธีที่สิ่งต่าง ๆ ทำงาน 99.9% ของเวลาเพราะนี่คือสิ่งที่ JVM ทำเองในครั้งแรกที่คุณสร้างอินสแตนซ์ของคลาสใหม่เรียกใช้เมธอดสแตติกหรือเข้าถึงฟิลด์สแตติก
เมื่อคุณต้องการสร้างคลาสโดยใช้การสะท้อน (เช่นเมื่อดีซีเรียลไลซ์หรือโหลดคลาสที่กำหนดชื่อที่กำหนดค่าได้) ไลบรารีที่ทำการสะท้อนควรถามแอปพลิเคชันที่ตัวโหลดคลาสใช้เสมอโดยรับClassLoader
พารามิเตอร์จากแอปพลิเคชัน แอปพลิเคชัน (ซึ่งทราบคลาสทั้งหมดที่จำเป็นต้องมีการสร้าง) ควรผ่านมันgetClass().getClassLoader()
ไป
วิธีอื่นในการรับคลาสโหลดเดอร์นั้นไม่ถูกต้อง หากมีการใช้ห้องสมุด hacks เช่นThread.getContextClassLoader()
, sun.misc.VM.latestUserDefinedLoader()
หรือsun.reflect.Reflection.getCallerClass()
มันเป็นข้อผิดพลาดที่เกิดจากความบกพร่องใน API ที่ โดยทั่วไปThread.getContextClassLoader()
มีอยู่เพียงเพราะใครก็ตามที่ออกแบบObjectInputStream
API ลืมที่จะยอมรับClassLoader
ว่าเป็นพารามิเตอร์และความผิดพลาดนี้ได้หลอกหลอนชุมชน Java มาจนถึงทุกวันนี้
ดังกล่าวกล่าวว่าคลาส JDK จำนวนมากใช้หนึ่งในไม่กี่แฮ็กในการเดาคลาสโหลดเดอร์ที่จะใช้ บางคนใช้ContextClassLoader
(ซึ่งล้มเหลวเมื่อคุณเรียกใช้แอพที่แตกต่างกันในกลุ่มเธรดที่แชร์หรือเมื่อคุณออกจากContextClassLoader null
) บางคนก็เดินไปตามสแต็ก (ซึ่งล้มเหลวเมื่อผู้เรียกโดยตรงของคลาสนั้นเป็นห้องสมุด) (ซึ่งก็ดีตราบใดที่มีการบันทึกไว้ให้ใช้คลาสในเท่านั้นCLASSPATH
) หรือ bootstrap class loader และบางตัวใช้การผสมผสานที่ไม่สามารถคาดเดาได้ของเทคนิคด้านบน (ซึ่งจะทำให้สับสนมากขึ้นเท่านั้น) ส่งผลให้เกิดการร้องไห้และขบเขี้ยวเคี้ยวฟันมาก
เมื่อใช้เช่น API, ครั้งแรกพยายามที่จะหาเกินของวิธีการที่ยอมรับสำหรับรถตักดินชั้นเป็นพารามิเตอร์ หากไม่มีวิธีการที่สมเหตุสมผลให้ลองตั้งค่าContextClassLoader
ก่อนการเรียก API (และรีเซ็ตในภายหลัง):
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
มีบทความใน javaworld.com ที่อธิบายถึงความแตกต่าง => คุณควรใช้ ClassLoader ใด
(1)
ตัวโหลดบริบทบริบทของเธรดจัดเตรียมประตูหลังรอบโครงร่างการมอบหมายการแบ่งกลุ่ม
ใช้ JNDI เช่น: ความกล้าของมันถูกนำไปใช้โดย bootstrap class ใน rt.jar (เริ่มต้นด้วย J2SE 1.3) แต่คลาส JNDI หลักเหล่านี้อาจโหลดตัวให้บริการ JNDI ที่นำไปใช้โดยผู้จำหน่ายอิสระและอาจปรับใช้ใน -classpath ของแอปพลิเคชัน สถานการณ์นี้เรียกร้องให้ผู้โหลดคลาสหลัก (ตัวแรกในกรณีนี้) โหลดคลาสที่มองเห็นได้จากหนึ่งในตัวโหลดคลาสของเด็ก (ตัวอย่างเช่นระบบหนึ่ง) การมอบหมาย J2SE ปกติไม่ทำงานและวิธีแก้ไขปัญหาคือการทำให้คลาส JNDI หลักใช้เธรดบริบทตัวโหลดเธรดดังนั้นจึงมีประสิทธิภาพ "ทันเนล" ผ่านลำดับชั้นของตัวโหลดคลาสในทิศทางตรงกันข้ามกับการมอบหมายที่เหมาะสม
(2) จากแหล่งเดียวกัน:
ความสับสนนี้อาจจะอยู่กับ Java ไปสักระยะหนึ่ง ใช้ J2SE API ด้วยการโหลดทรัพยากรแบบไดนามิกทุกชนิดและลองเดาว่าจะใช้กลยุทธ์การโหลดแบบใด นี่คือตัวอย่าง:
- JNDI ใช้ context classers
- Class.getResource () และ Class.forName () ใช้ classloader ปัจจุบัน
- JAXP ใช้ context classers (ตั้งแต่ J2SE 1.4)
- java.util.ResourceBundle ใช้ classloader ปัจจุบันของผู้โทร
- ตัวจัดการโปรโตคอล URL ที่ระบุผ่านคุณสมบัติระบบ java.protocol.handler.pkgs ถูกค้นหาใน bootstrap และ classloaders ระบบเท่านั้น
- Java Serialization API ใช้ classloader ปัจจุบันของผู้โทรโดยค่าเริ่มต้น
bootstrap
class loader ที่ถูกตั้งค่าเป็น context class loader แต่ child system
classpath class loader ที่Thread
ถูกตั้งค่าด้วย JNDI
เรียนแล้วทำให้แน่ใจว่าจะใช้Thread.currentThread().getContextClassLoader()
ในการโหลดเรียนการดำเนิน JNDI ที่มีอยู่ในคลาสพา ธ
การเพิ่มคำตอบ @David Roussel อาจมีการโหลดคลาสต่างๆ
ให้เข้าใจวิธีการทำงานของตัวโหลดคลาส
จาก javin paul blog ใน javarevisited:
ClassLoader
ปฏิบัติตามสามหลักการ
คลาสถูกโหลดใน Java เมื่อต้องการ สมมติว่าคุณมีคลาสเฉพาะของแอปพลิเคชันที่เรียกว่า Abc.class คำขอแรกของการโหลดคลาสนี้จะมาถึง Application ClassLoader ซึ่งจะมอบสิทธิ์ให้กับผู้ปกครองส่วนขยาย ClassLoader ซึ่งจะมอบหมายตัวแทนเพิ่มเติมให้กับโหลดเดอร์คลาส Primordial หรือ Bootstrap
Bootstrap ClassLoaderรับผิดชอบการโหลดไฟล์คลาส JDK มาตรฐานจาก rt.jar และเป็นพาเรนต์ของตัวโหลดคลาสทั้งหมดใน Java Bootstrap class loader ไม่มีพ่อแม่
Extension ClassLoaderมอบหมายการโหลดคลาสให้กับพาเรนต์ Bootstrap และหากไม่สำเร็จให้โหลดไดเร็กทอรีคลาส jre / lib / ext ในรูปแบบคลาสหรือไดเร็กทอรีอื่นที่ชี้โดยคุณสมบัติระบบ java.ext.dirs
โหลดคลาสระบบหรือแอ็พพลิเคชันและรับผิดชอบการโหลดคลาสเฉพาะแอปพลิเคชันจากตัวแปรสภาพแวดล้อม CLASSPATH, ตัวเลือกบรรทัดคำสั่ง -classpath หรือ -cp, แอตทริบิวต์ Class-Path ของไฟล์ Manifest ภายใน JAR
Application class loaderเป็นลูกของExtension ClassLoaderและถูกใช้โดยsun.misc.Launcher$AppClassLoader
class
หมายเหตุ:ยกเว้นรถตักดินระดับเงินทุนซึ่งจะดำเนินการในภาษาพื้นเมืองส่วนใหญ่อยู่ใน C, รถตักชั้น Java java.lang.ClassLoader
จะดำเนินการโดยใช้
ตามหลักการการมองเห็นChild ClassLoaderสามารถดูคลาสที่โหลดโดยParent ClassLoaderแต่ในทางกลับกันไม่เป็นความจริง
ตามหลักการนี้คลาสที่โหลดโดยพาเรนต์ไม่ควรโหลดโดย Child ClassLoader อีกครั้ง
ClassB
ต้องอยู่ใน classpath ของClassA
โหลดเดอร์ (หรือClassA
พ่อแม่ของโหลดเดอร์)? เป็นไปไม่ได้ClassA
หรือที่โหลดเดอร์จะแทนที่loadClass()
เช่นนั้นโหลดได้สำเร็จClassB
แม้ว่าClassB
จะไม่ได้อยู่ใน classpath