สับสน
ในอดีตเมื่อดูที่ซอร์สโค้ดฉันมักจะพบรหัสในเฟรมเวิร์กโดยใช้เธรด currentthread.getContextClassloader () เพื่อรับคลาสบริบทของเธรดปัจจุบันและใช้ตัวโหลดคลาสบริบทนี้เพื่อโหลดคลาส
เมื่อเรามักจะเขียนโค้ดในโปรแกรมเมื่อเราต้องการโหลดคลาสแบบไดนามิกเรามักจะใช้ class.forName () เพื่อโหลดคลาสที่เราต้องการ ตัวอย่างเช่นสิ่งที่พบบ่อยที่สุดคือเมื่อเรากำลังเขียนโปรแกรม JDBC เราใช้ class.forName () เพื่อโหลดไดรเวอร์ JDBC
ลอง {return class.forname ("Oracle.jdbc.driver.oracledriver");} catch (classnotfoundexception e) {// skip}เหตุใดเมื่อเราใช้ class.forName () เพื่อโหลดคลาสหากไม่พบคลาสเรายังคงพยายามโหลดคลาสโดยใช้คลาสโหลดเดอร์ที่ได้รับโดย thread.currentthread.getContextloader () ตัวอย่างเช่นเราอาจพบรหัสต่อไปนี้:
ลอง {return class.forName (className);} catch (classnotFoundException e) {// skip} classloader ctxclassloader = thread.currentThread (). getContextClassLoader (); if (ctxClassLoader! = null) {ลอง {clazz = ctxclassloader.loadclass (className); } catch (classnotFoundException e) {// skip}}ส่วนที่เป็นตัวหนาที่นี่คือการใช้ตัวโหลดที่ได้รับโดย thread.currentthread.getContextloader () เพื่อโหลดคลาส เห็นได้ชัดว่าตัวโหลดคลาสที่ใช้เมื่อ class.forName () โหลดคลาสอาจแตกต่างจากตัวโหลดคลาสที่ได้จาก tread.currentthread.getContextloader () เหตุใดความแตกต่างจึงเกิดขึ้น?
ตัวโหลดคลาส Java
ก่อนที่จะทำความเข้าใจว่าทำไมตัวโหลดคลาสนี้ที่ได้รับโดย tread.currentthread.getContextloader () มาก่อนให้เข้าใจคลาสโหลดเดอร์ (classloader) ก่อนที่จะใช้ใน JVM
classloaders มีสามประเภทใน JVM โดยค่าเริ่มต้น:
ตัวโหลดคลาส bootstrap
ตัวโหลดคลาส Bootstrap คลาสโหลดเดอร์เป็นตัวโหลดคลาสที่สร้างขึ้นใน JDK ซึ่งใช้ในการโหลดคลาสภายใน JDK ตัวโหลดคลาส bootstrap ใช้สำหรับโหลดคลาสต่ำกว่า $ JAVA_HOME/JRE/LIB ใน JDK เช่นคลาสในแพ็คเกจ RT.JAR ตัวโหลดคลาส bootstrap เป็นส่วนหนึ่งของ JVM และโดยทั่วไปจะเขียนในรหัสดั้งเดิม
ตัวโหลดคลาสส่วนขยาย
ตัวโหลดคลาสส่วนขยายคลาสส่วนใหญ่ใช้เพื่อโหลดคลาสในแพ็คเกจส่วนขยาย JDK โดยทั่วไปแพ็คเกจภายใต้ $ java_home/lib/ext จะถูกโหลดผ่านตัวโหลดคลาสนี้และคลาสภายใต้แพ็คเกจนี้โดยทั่วไปจะเริ่มต้นด้วย Javax
ตัวโหลดคลาสระบบ
ตัวโหลดคลาส System Class Loader เรียกอีกอย่างว่าแอปพลิเคชันคลาสโหลดเดอร์ (AppClassLoader) ตามชื่อแนะนำตัวโหลดคลาสนี้ใช้ในการโหลดรหัสแอปพลิเคชันที่นักพัฒนามักจะเขียน ตัวโหลดคลาสระบบใช้ในการโหลดคลาสแอปพลิเคชันระดับที่เก็บไว้ในเส้นทาง ClassPath
รหัสต่อไปนี้แสดงรายการตัวโหลดคลาสทั้งสามนี้:
คลาสสาธารณะ MainClass {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {system.out.println (integer.class.getClassLoader ()); System.out.println (logging.class.getClassLoader ()); System.out.println (mainclass.class.getclassloader ()); -สถานที่รับ bootstrap class loader ส่งคืนค่า null ตลอดไป
null # bootstrap คลาส loader sun.misc.launcher$extclasslassloader@5e2de80c # ส่วนขยายคลาสรถตัก
รูปแบบการมอบหมายผู้ปกครอง
รถตักสามประเภทที่แนะนำข้างต้นไม่ได้แยกพวกเขามีความสัมพันธ์แบบลำดับชั้น:
รถตักสามชั้นทำงานร่วมกันผ่านความสัมพันธ์แบบลำดับชั้นนี้และรับผิดชอบการโหลดคลาสด้วยกัน โมเดลลำดับชั้นข้างต้นเรียกว่าโมเดล "การมอบหมายหลัก" ของตัวโหลดคลาส รูปแบบการมอบหมายหลักกำหนดให้รถตักคลาสทั้งหมดต้องมีตัวโหลดหลักยกเว้นตัวโหลดคลาส Bootstrap ระดับบนสุด เมื่อคลาสโหลดเดอร์โหลดคลาสก่อนอื่นตรวจสอบว่ามีคลาสในแคชที่โหลดหรือไม่ ถ้าไม่เช่นนั้นตัวโหลดหลักจะถูกมอบหมายให้โหลดคลาสก่อน ตัวโหลดหลักทำงานเช่นเดียวกับตัวโหลดลูกก่อนหน้านี้จนกว่าคำขอจะถึงตัวโหลดคลาส bootstrap ระดับบนสุด หากตัวโหลดหลักไม่สามารถโหลดคลาสที่ต้องการได้แล้วตัวโหลดเด็กจะพยายามโหลดคลาสด้วยตัวเอง มันทำงานคล้ายกับต่อไปนี้:
เราสามารถใช้รหัสใน classloader ใน JDK เพื่อดูการใช้งานกลไกการมอบหมายผู้ปกครอง รหัสถูกนำไปใช้ใน classloader.loadclass ()
คลาสหมุน <?> loadclass (ชื่อสตริง, การแก้ไขบูลีน) พ่น classnotfoundexception ซิงโครไนซ์ (getClassLoadingLock (ชื่อ)) {// ก่อนอื่นตรวจสอบว่าคลาสได้รับการโหลดคลาสแล้ว < if (c == null) {long t0 = system.nanotime (); ลอง {ถ้า (parent! = null) {c = parent.loadclass (ชื่อ, เท็จ); } else {c = findbootstrapclassornull (ชื่อ); }} catch (classnotfoundexception e) {// classnotfoundexception โยนถ้าไม่พบคลาส // จากตัวโหลดคลาสแม่ที่ไม่ใช่ NULL} ถ้า (c == null) {// ถ้ายังไม่พบแล้วเรียก FindClass ตามลำดับ // เพื่อค้นหาคลาส t1 ยาว = system.nanotime (); c = findclass (ชื่อ); // นี่คือตัวโหลดคลาสที่กำหนด; บันทึกสถิติ sun.misc.perfcounter.getParentDelegationTime (). AddTime (T1 - T0); sun.misc.perfcounter.getfindclasstime (). addelapsedtime จาก (T1); sun.misc.perfcounter.getFindClasses (). เพิ่มขึ้น (); }} ถ้า (แก้ไข) {ResolveClass (c); } return c; -ข้อดีอย่างหนึ่งของการใช้การมอบหมายหลักในการจัดระเบียบรถตักคลาสคือความปลอดภัย หากเรากำหนดคลาสสตริงด้วยตนเองเราหวังว่าจะแทนที่คลาสสตริงนี้ด้วยการใช้งานของ java.lang.string ใน Java เริ่มต้น
เราใส่ไฟล์คลาสของคลาสสตริงที่เรานำไปใช้ในเส้นทาง classpath เมื่อเราใช้คลาสโหลดเดอร์เพื่อโหลดคลาสสตริงที่เรานำไปใช้ขั้นแรกตัวโหลดคลาสจะมอบหมายคำขอไปยังตัวโหลดพาเรนต์และผ่านเลเยอร์โดยเลเยอร์ตัวโหลดคลาส bootstrap จะโหลดประเภทสตริงในแพ็คเกจ RT.JAR แล้วส่งคืนให้เราตลอดทาง ในกระบวนการนี้ตัวโหลดคลาสของเราจะละเว้นคลาสสตริงที่เราวางไว้ใน classpath
หากกลไกการมอบหมายหลักไม่ได้ถูกนำมาใช้ตัวโหลดคลาสระบบสามารถค้นหาไฟล์คลาสสตริงในเส้นทาง classpath และโหลดลงในโปรแกรมทำให้การใช้งานสตริงใน JDK ถูกเขียนทับ ดังนั้นวิธีการทำงานของรถตักคลาสนี้ทำให้มั่นใจได้ว่าโปรแกรม Java สามารถทำงานได้อย่างปลอดภัยและเสถียรในระดับหนึ่ง
ตัวโหลดคลาสบริบทของเธรด
การพูดคุยข้างต้นเกี่ยวกับเนื้อหาที่เกี่ยวข้องกับรถตักคลาสจำนวนมาก แต่ยังไม่ได้พูดคุยเกี่ยวกับหัวข้อของวันนี้ตัวโหลดคลาสบริบทของเธรด
ณ จุดนี้เรารู้อยู่แล้วว่า Java ให้บริการรถตักสามประเภทและทำงานในคอนเสิร์ตตามกลไกการมอบหมายผู้ปกครองที่เข้มงวด บนพื้นผิวดูเหมือนว่าสมบูรณ์แบบ แต่มันเป็นกลไกการมอบหมายผู้ปกครองที่เข้มงวดซึ่งทำให้เกิดข้อ จำกัด บางอย่างเมื่อโหลดคลาส
เมื่อเฟรมเวิร์กพื้นฐานเพิ่มเติมของเราจำเป็นต้องใช้คลาสแอปพลิเคชันระดับเราสามารถใช้คลาสเหล่านี้ได้หากคลาสนี้โหลดเมื่อคลาสโหลดเดอร์ที่ใช้โดยเฟรมเวิร์กปัจจุบันของเราสามารถโหลดได้ กล่าวอีกนัยหนึ่งเราไม่สามารถใช้ตัวโหลดคลาสของตัวโหลดคลาสปัจจุบันได้ ข้อ จำกัด นี้เกิดจากกลไกการมอบหมายผู้ปกครองเนื่องจากการมอบหมายการร้องขอการโหลดในชั้นเรียนเป็นแบบทางเดียว
แม้ว่าจะมีหลายกรณี แต่ก็ยังมีความต้องการเช่นนี้ บริการ JNDI ทั่วไป JNDI จัดเตรียมอินเทอร์เฟซกับทรัพยากรการสืบค้น แต่การใช้งานเฉพาะนั้นดำเนินการโดยผู้ผลิตที่แตกต่างกัน ในเวลานี้รหัสของ JNDI ถูกโหลดโดยตัวโหลดคลาส Bootstrap ของ JVM แต่การใช้งานเฉพาะคือรหัสอื่นนอกเหนือจาก JDK ที่ผู้ใช้ให้ไว้ดังนั้นจึงสามารถโหลดได้โดยตัวโหลดคลาสระบบหรือตัวโหลดคลาสที่ผู้ใช้กำหนด ภายใต้กลไกการมอบหมายผู้ปกครอง JNDI ไม่สามารถรับการดำเนินการตาม SPI ของ JNDI ได้
เพื่อแก้ปัญหานี้จะมีการแนะนำตัวโหลดคลาสบริบทของเธรด SetContextClassLoader () ของ Java.lang.Thread คลาส SetContextClassLoader () (หากไม่ได้ตั้งค่าจะได้รับการสืบทอดจากเธรดพาเรนต์โดยค่าเริ่มต้นหากโปรแกรมไม่ได้ตั้งค่ามันจะเริ่มต้นกับตัวโหลดคลาสของระบบ) ด้วยตัวโหลดคลาสบริบทของเธรดแอปพลิเคชันสามารถผ่านคลาสโหลดเดอร์ที่ใช้โดยแอปพลิเคชันไปยังรหัสที่ใช้ตัวโหลดคลาสระดับบนสุดผ่าน java.lang.thread.setContextClassLoader () ตัวอย่างเช่นบริการ JNDI ด้านบนสามารถใช้วิธีนี้เพื่อรับโหลดคลาสที่สามารถโหลดการใช้งาน SPI และรับคลาสการใช้งาน SPI ที่จำเป็น
จะเห็นได้ว่าการแนะนำตัวตักคลาสเกลียวนั้นเป็นการทำลายกลไกการมอบหมายผู้ปกครอง แต่มันให้ความยืดหยุ่นในการโหลดคลาส
แก้ไขข้อสงสัย
กลับไปที่จุดเริ่มต้นเพื่อโหลดคลาสที่ใช้โดยผู้ใช้นอกกรอบคลาสเหล่านี้อาจไม่ถูกโหลดผ่านคลาสโหลดเดอร์ที่ใช้โดยเฟรมเวิร์ก เพื่อข้ามรูปแบบการมอบหมายหลักของตัวโหลดคลาส thread.getContextClassLoader () ใช้เพื่อโหลดคลาสเหล่านี้
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น