ความแตกต่างระหว่างโหลดเดอร์คลาสบริบทของเธรดและ classloader ปกติ


242

ความแตกต่างระหว่างตัวโหลดคลาสบริบทของเธรดและตัวโหลดคลาสปกติคืออะไร

นั่นคือถ้าThread.currentThread().getContextClassLoader()และgetClass().getClassLoader()ส่งคืนคลาสโหลดเดอร์อ็อบเจ็กต์ที่แตกต่างกันอันใดจะถูกใช้?

คำตอบ:


151

แต่ละคลาสจะใช้ classloader ของตัวเองเพื่อโหลดคลาสอื่น ๆ ดังนั้นหากClassA.classการอ้างอิงClassB.classนั้นClassBจำเป็นต้องอยู่ใน classpath ของ classloader ของClassAหรือผู้ปกครอง

classloader บริบทของเธรดเป็น classloader ปัจจุบันสำหรับเธรดปัจจุบัน วัตถุที่สามารถสร้างขึ้นจากชั้นในแล้วส่งผ่านไปยังหัวข้อที่เป็นเจ้าของโดยClassLoaderC ClassLoaderDในกรณีนี้วัตถุต้องใช้Thread.currentThread().getContextClassLoader()โดยตรงหากต้องการโหลดทรัพยากรที่ไม่พร้อมใช้งานบน classloader ของตนเอง


1
ทำไมคุณพูดว่าClassBต้องอยู่ใน classpath ของClassAโหลดเดอร์ (หรือClassAพ่อแม่ของโหลดเดอร์)? เป็นไปไม่ได้ClassAหรือที่โหลดเดอร์จะแทนที่loadClass()เช่นนั้นโหลดได้สำเร็จClassBแม้ว่าClassBจะไม่ได้อยู่ใน classpath
Pacerier

8
แน่นอนว่าตัวแบ่งข้อมูลบางตัวไม่มีคลาสพา ธ เมื่อเขียนว่า "ClassB ต้องอยู่ใน classpath ของ classloader ของ ClassA" ฉันหมายถึง "ClassB ต้องโหลดได้โดย classloader ของ ClassA" 90% ของเวลาที่พวกเขาหมายถึงเหมือนกัน แต่ถ้าคุณไม่ได้ใช้ตัวโหลดคลาสที่อ้างอิง URL ดังนั้นเฉพาะกรณีที่สองเท่านั้นที่เป็นจริง
David Roussel

ClassA.classการอ้างอิงนั้นหมายถึงClassB.classอะไร
jameshfisher

1
เมื่อ ClassA มีคำสั่งนำเข้าสำหรับ ClassB หรือหากมีวิธีการใน ClassA ที่มีตัวแปรท้องถิ่นประเภท ClassB นั่นจะทริกเกอร์ ClassB ให้โหลดหากยังไม่ได้โหลด
David Roussel

ฉันคิดว่าปัญหาของฉันเชื่อมโยงกับหัวข้อนี้ คุณคิดอย่างไรเกี่ยวกับการแก้ปัญหาของฉัน? ฉันรู้ว่ามันไม่ใช่รูปแบบที่ดี แต่ฉันไม่มีความคิดอื่น ๆ เกี่ยวกับวิธีการแก้ไข: stackoverflow.com/questions/29238493/…
Marcin Erbel

109

นี่ไม่ได้ตอบคำถามเดิม แต่เป็นคำถามที่มีการจัดอันดับสูงและเชื่อมโยงกับ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()มีอยู่เพียงเพราะใครก็ตามที่ออกแบบObjectInputStreamAPI ลืมที่จะยอมรับ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);
}

5
ใช่นี่คือคำตอบที่ฉันจะชี้ให้ทุกคนถามคำถาม
Marko Topolnik

6
คำตอบนี้มุ่งเน้นไปที่การใช้คลาสโหลดเดอร์สำหรับการโหลดคลาส (เพื่อสร้างอินสแตนซ์ผ่านการสะท้อนกลับหรือคล้ายกัน) ในขณะที่จุดประสงค์อื่นที่ใช้สำหรับ (และอันที่จริงแล้วจุดประสงค์เดียวที่ฉันเคยใช้ ใช้หลักการเดียวกันนี้หรือมีสถานการณ์ที่คุณต้องการดึงทรัพยากรผ่านบริบทคลาสโหลดเดอร์แทนที่จะเรียกคลาสโหลดเดอร์?
Egor Hans

90

มีบทความใน 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 ปัจจุบันของผู้โทรโดยค่าเริ่มต้น

เนื่องจากมีข้อเสนอแนะว่าวิธีแก้ปัญหาคือการทำให้คลาส JNDI หลักใช้ตัวโหลดบริบทเธรดฉันไม่เข้าใจว่าสิ่งนี้จะช่วยได้อย่างไรในกรณีนี้เราต้องการโหลดคลาสผู้จัดจำหน่ายการใช้งานโดยใช้ parent classloader แต่พวกเขาไม่สามารถมองเห็น . ดังนั้นเราจะโหลดโดยใช้พาเรนต์ได้อย่างไรแม้ว่าเราจะตั้งค่า classloader พาเรนต์นี้ใน context classloader ของ thread
ซันนี่ Gupta

6
@SAM วิธีแก้ปัญหาที่แนะนำจริง ๆ แล้วค่อนข้างตรงกันข้ามกับสิ่งที่คุณพูดในตอนท้าย ไม่ใช่ parent bootstrapclass loader ที่ถูกตั้งค่าเป็น context class loader แต่ child systemclasspath class loader ที่Threadถูกตั้งค่าด้วย JNDIเรียนแล้วทำให้แน่ใจว่าจะใช้Thread.currentThread().getContextClassLoader()ในการโหลดเรียนการดำเนิน JNDI ที่มีอยู่ในคลาสพา ธ
Ravi Thapliyal

"การมอบหมาย J2SE ปกติไม่ทำงาน" ฉันขอทราบได้ไหมว่าทำไมมันถึงใช้งานไม่ได้ เนื่องจาก Bootstrap ClassLoader สามารถโหลดคลาสจาก rt.jar เท่านั้นและไม่สามารถโหลดคลาสจาก -classpath ของแอปพลิเคชันได้ ขวา?
YuFeng Shen

37

การเพิ่มคำตอบ @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$AppClassLoaderclass

หมายเหตุ:ยกเว้นรถตักดินระดับเงินทุนซึ่งจะดำเนินการในภาษาพื้นเมืองส่วนใหญ่อยู่ใน C, รถตักชั้น Java java.lang.ClassLoaderจะดำเนินการโดยใช้

หลักการทัศนวิสัย

ตามหลักการการมองเห็นChild ClassLoaderสามารถดูคลาสที่โหลดโดยParent ClassLoaderแต่ในทางกลับกันไม่เป็นความจริง

หลักการความเป็นเอกลักษณ์

ตามหลักการนี้คลาสที่โหลดโดยพาเรนต์ไม่ควรโหลดโดย Child ClassLoader อีกครั้ง

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