รูปแบบการมอบหมายผู้ปกครอง
แนวคิดของการโหลดในชั้นเรียนควรได้รับการพิจารณาว่าเป็นนวัตกรรมในภาษา Java วัตถุประสงค์คือเพื่อแยกกระบวนการโหลดคลาสจากเครื่องเสมือนและบรรลุวัตถุประสงค์ของ "การได้รับไบนารีสตรีมที่อธิบายคลาสนี้ผ่านชื่อที่ผ่านการรับรองของคลาส" โมดูลรหัสที่ใช้ฟังก์ชั่นนี้เป็นตัวโหลดคลาส โมเดลพื้นฐานของตัวโหลดคลาสคือรูปแบบการมอบหมายผู้ปกครองที่มีชื่อเสียง มันฟังดูยอดเยี่ยม แต่ตรรกะนั้นง่ายมาก เมื่อเราต้องการโหลดคลาสก่อนอื่นเราจะพิจารณาว่าคลาสได้รับการโหลดหรือไม่ ถ้าไม่เราจะพิจารณาว่ามีการโหลดโดยตัวโหลดหลักหรือไม่ หากเราไม่ได้เรียกวิธีการค้นหาของเราเองเพื่อลองโหลด นี่คือโมเดลพื้นฐาน (ลบการละเมิดภาพการขโมย):
นอกจากนี้ยังใช้งานง่ายมาก คีย์คือวิธี loadclass ของคลาส classloader ซอร์สโค้ดมีดังนี้:
คลาสที่ได้รับการป้องกัน <?> loadclass (ชื่อสตริง, การแก้ไขบูลีน) พ่น classnotFoundException {ซิงโครไนซ์ (getClassLoadingLock (ชื่อ)) {// ก่อนตรวจสอบว่าคลาสได้รับการโหลดคลาสแล้ว <?> c = findloadedClass (ชื่อ) 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 (). เพิ่มขึ้น (); }} ถ้า (แก้ไข) {คลาส (c); } return c; -ทันใดนั้นฉันก็รู้สึกว่าล้อเล่นทำไมฉันถึงแค่โยนข้อยกเว้นโดยตรง? ในความเป็นจริงมันเป็นเพราะคลาส classloader เป็นคลาสนามธรรม ในความเป็นจริงมันจะเขียนคลาสย่อยเมื่อใช้ วิธีนี้จะถูกเขียนใหม่ตามที่จำเป็นเพื่อดำเนินการตามกระบวนการโหลดที่ต้องการโดยธุรกิจ
custom classloader
เมื่อปรับแต่งคลาสย่อยของ classloader มีสองวิธีทั่วไป: หนึ่งคือการเขียนวิธี loadclass ใหม่และอีกวิธีหนึ่งคือการเขียนวิธี FindClass ใหม่ ในความเป็นจริงสองวิธีนี้เป็นหลักเหมือนกัน ท้ายที่สุดแล้ว LoadClass จะเรียก FindClass ด้วย แต่การพูดอย่างมีเหตุผลก็เป็นการดีที่สุดที่จะไม่แก้ไขตรรกะภายในของ LoadClass โดยตรง
โดยส่วนตัวแล้วฉันคิดว่าวิธีที่ดีกว่าคือการเขียนวิธีการโหลดของคลาสที่กำหนดเองใหม่ใน FindClass
ทำไมถึงดีกว่า? เพราะฉันยังได้กล่าวก่อนหน้านี้ว่าวิธีการโหลดคลาสเป็นสถานที่ในการใช้ตรรกะของโมเดลที่มีผู้ปกครอง การแก้ไขวิธีนี้โดยไม่ได้รับอนุญาตจะนำไปสู่แบบจำลองที่ถูกทำลายและทำให้เกิดปัญหาได้ง่าย ดังนั้นจึงเป็นการดีที่สุดที่จะทำการเปลี่ยนแปลงขนาดเล็กภายในกรอบของแบบจำลองการมอบหมายผู้ปกครองเพื่อไม่ทำลายโครงสร้างที่มั่นคงดั้งเดิม ในขณะเดียวกันก็หลีกเลี่ยงความจำเป็นในการเขียนรหัสซ้ำที่ได้รับมอบหมายจากผู้ปกครองในกระบวนการเขียนวิธี loadclass ใหม่ จากมุมมองของการใช้ซ้ำของรหัสการไม่แก้ไขวิธีนี้โดยตรงเป็นตัวเลือกที่ดีกว่าเสมอ
แน่นอนว่ามันจะแตกต่างกันถ้าคุณจงใจทำลายโมเดลคณะกรรมการหลัก
ทำลายรูปแบบการมอบหมายหลัก
ทำไมต้องทำลายรูปแบบการมอบหมายหลัก?
ในความเป็นจริงในบางกรณีเราอาจต้องโหลดสองคลาสที่แตกต่างกัน แต่น่าเสียดายที่ชื่อของสองคลาสนี้เหมือนกันทุกประการ ในเวลานี้รูปแบบการมอบหมายผู้ปกครองไม่สามารถตอบสนองความต้องการของเราได้ เราจำเป็นต้องเขียนวิธี loadclass ใหม่เพื่อทำลายโมเดลการมอบหมายหลักและปล่อยให้โหลดชื่อคลาสเดียวกันหลายครั้ง แน่นอนการทำลายล้างที่กล่าวถึงที่นี่เป็นเพียงการทำลายล้างในความหมายของท้องถิ่น
แต่ชื่อคลาสเหมือนกัน JVM จะแยกแยะความแตกต่างสองคลาสนี้ได้อย่างไร เห็นได้ชัดว่าสิ่งนี้จะไม่ทำให้เกิดการล่มสลายของโลกทัศน์ ในความเป็นจริงคลาสไม่เพียง แต่ถูก จำกัด ด้วยชื่อคลาสใน JVM แต่ยังเป็นของคลาสโหลดที่โหลด คลาสที่โหลดโดยคลาสโหลดที่แตกต่างกันจริง ๆ แล้วไม่มีผลต่อกันและกัน
ทำการทดลอง
มาเขียนสองคลาสก่อน:
แพ็คเกจ com.mythsman.test; คลาสสาธารณะสวัสดี {โมฆะสาธารณะพูด () {system.out.println ("นี่มาจาก Hello v1"); - แพ็คเกจ com.mythsman.test; คลาสสาธารณะสวัสดี {โมฆะสาธารณะพูด () {system.out.println ("นี่มาจาก Hello v2"); - ชื่อคลาสสองชื่อเหมือนกันความแตกต่างเพียงอย่างเดียวคือการใช้วิธีการนั้นแตกต่างกัน ก่อนอื่นเรารวบรวมแยกต่างหากจากนั้นเปลี่ยนชื่อไฟล์คลาสที่สร้างเป็น hello.class.1 และ hello.class.2
เป้าหมายของเราคือการสร้างอินสแตนซ์ของสองคลาสนี้ในคลาสทดสอบ
จากนั้นเราจะสร้างคลาสทดสอบใหม่ com.mythsman.test.main และสร้างคลาสโหลดแบบกำหนดเองสองรายการในฟังก์ชั่นหลัก:
classloader classloader1 = new classloader () {@override คลาสสาธารณะ <?> loadclass (String s) พ่น classnotfoundexception {ลอง {ถ้า (s.equals ("com.mythsman.test.hello") {byte [] classBytes = files.readAlbytes ส่งคืน defineclass (s, classbytes, 0, classbytes.length); } else {return super.loadclass (s); }} catch (ioexception e) {โยน classnotFoundException ใหม่; }}}; classloader classloader2 = new classloader () {@override คลาสสาธารณะ <?> loadclass (สตริง s) พ่น classnotfoundexception {ลอง {ถ้า (s.equals ("com.mythsman.test.hello" files.ReadAllBytes (paths.get ("/home/myths/desktop/test/hello.class.2")); ส่งคืน defineclass (s, classbytes, 0, classbytes.length); } else {return super.loadclass (s); }} catch (ioexception e) {โยน classnotFoundException ใหม่; -
วัตถุประสงค์ของสองคลาสโหลดนี้คือการเชื่อมโยงสองไบต์ที่แตกต่างกันของคลาส Hello แยกต่างหาก เราจำเป็นต้องอ่านไฟล์ bytecode และโหลดลงในคลาสผ่านวิธีการกำหนด โปรดทราบว่าเราโอเวอร์โหลดวิธีการโหลด หากเราโอเวอร์โหลดวิธี FindClass จากนั้นเนื่องจากกลไกการประมวลผลผู้ได้รับมอบหมายผู้ปกครองของวิธี loadclass วิธีการค้นหาของคลาสที่สองจะไม่ถูกเรียก
แล้วเราจะสร้างอินสแตนซ์ได้อย่างไร? เห็นได้ชัดว่าเราไม่สามารถอ้างถึงชื่อคลาสได้โดยตรง (ชื่อขัดแย้ง) ดังนั้นเราจึงสามารถใช้การสะท้อนได้เท่านั้น:
Object hellov1 = classloader1.loadclass ("com.mythsman.test.hello"). newinstance (); วัตถุ hellov2 = classloader2.loadclass ("com.mythsman.test.hello"). newinstance (); hellov1.getClass (). getMethod ("พูด"). เรียก (Hellov1); hellov2.getClass () getMethod ("พูด") เอาท์พุท:
นี่มาจาก Hello V1 นี่มาจาก Hello V2
ตกลงแม้ว่าคุณจะเสร็จสิ้นสองโหลด แต่ก็ยังมีบางจุดที่ให้ความสนใจ
ความสัมพันธ์ระหว่างสองคลาสคืออะไร
เห็นได้ชัดว่าคลาสทั้งสองนี้ไม่ใช่คลาสเดียวกัน แต่ชื่อของพวกเขาเหมือนกัน ดังนั้นผลลัพธ์ของผู้ประกอบการเช่น Isinstance ของ:
System.out.println ("คลาส:"+hellov1.getClass ()); system.out.println ("คลาส:"+hellov2.getClass ()); system.out.println ("มี hcode: "+hellov2.getclass (). hashcode ()); system.out.println (" classloader: "+hellov1.getClass (). getClassloader ()); System.out.println (" classloader: "+hellov2.getClass () เอาท์พุท:
คลาส: คลาส com.mythsman.test.helloclass: คลาส com.mythsman.test.hellohashcode: 1581781576hashcode: 1725154839classloader: com.mythsman.test.main โทร
ชื่อชั้นเรียนของพวกเขาเหมือนกัน แต่ hashcodes ของคลาสนั้นแตกต่างกันซึ่งหมายความว่าทั้งสองนี้ไม่ใช่คลาสเดียวกันและรถตักคลาสของพวกเขาก็แตกต่างกัน (ในความเป็นจริงพวกเขาเป็นสองชั้นภายในของหลัก)
ความสัมพันธ์ระหว่างรถตักสองคลาสนี้กับตัวโหลดคลาสสามชั้นของระบบคืออะไร?
ใช้ตัวโหลดคลาสที่กำหนดเองครั้งแรกเป็นตัวอย่าง:
System.out.println (classloader1.getParent (). getParent (). getParent ()); System.out.println (classloader1.getParent (). getParent ()); system.out.println (classlo ader1.getParent ()); System.out.println (classloader1.getParent ()); System.out.println (classloader1); system.out.println (classloader.getSystemClassloader ());
เอาท์พุท:
nullsun.misc.launcher $ [email protected] $ [email protected] $ [email protected] $ appclassloader@18b4aac2
แน่นอนความสัมพันธ์ระหว่างพ่อแม่-ลูกที่กล่าวถึงที่นี่ไม่ใช่ความสัมพันธ์ที่สืบทอดมา แต่ความสัมพันธ์แบบผสมผสาน Classloader เด็กบันทึกการอ้างอิง (พาเรนต์) ของคลาส Parent Loadloader