ส่วนที่หนึ่ง ฉันต้องอ่านบทความนี้หรือไม่?
ตัวโหลดคลาส Java มีความสำคัญต่อการทำงานของระบบ Java แต่เรามักจะละเลยไป ตัวโหลดคลาส Java โหลดคลาสขณะรันไทม์โดยการค้นหาและโหลดคลาสเหล่านั้น ตัวโหลดคลาสแบบกำหนดเองสามารถเปลี่ยนวิธีการโหลดคลาสได้อย่างสมบูรณ์ โดยปรับแต่ง Java virtual machine ของคุณในแบบที่คุณต้องการ บทความนี้จะแนะนำตัวโหลดคลาส Java โดยย่อ จากนั้นอธิบายผ่านตัวอย่างการสร้างตัวโหลดคลาสแบบกำหนดเอง ตัวโหลดคลาสนี้จะคอมไพล์โค้ดโดยอัตโนมัติก่อนที่จะโหลดคลาส คุณจะได้เรียนรู้ว่าจริงๆ แล้ว Class Loader ทำอะไรได้บ้าง และจะสร้างคลาสของคุณเองได้อย่างไร ตราบใดที่คุณมีความรู้พื้นฐานเกี่ยวกับ Java รู้วิธีสร้าง คอมไพล์ และรันโปรแกรม Java แบบบรรทัดคำสั่ง และแนวคิดพื้นฐานบางประการของไฟล์คลาส Java คุณก็จะสามารถเข้าใจเนื้อหาของบทความนี้ได้ หลังจากอ่านบทความนี้แล้ว คุณควรจะสามารถ:
* ขยายฟังก์ชั่นของเครื่องเสมือน Java
* สร้างตัวโหลดคลาสแบบกำหนดเอง
* วิธีรวมคลาสโหลดเดอร์แบบกำหนดเองเข้ากับแอปพลิเคชันของคุณ
* แก้ไขคลาสโหลดเดอร์ของคุณให้เข้ากันได้กับ Java 2
ส่วนที่ 2 บทนำ class loader คืออะไร?
ความแตกต่างระหว่าง Java และภาษาอื่นๆ คือ Java ทำงานบน Java Virtual Machine (JVM) ซึ่งหมายความว่าโค้ดที่คอมไพล์แล้วจะถูกบันทึกในรูปแบบที่ไม่ขึ้นกับแพลตฟอร์ม แทนที่จะเป็นรูปแบบที่ทำงานบนเครื่องเฉพาะ รูปแบบนี้มีความแตกต่างที่สำคัญมากมายจากรูปแบบโค้ดปฏิบัติการแบบเดิม โดยเฉพาะอย่างยิ่ง ไม่เหมือนกับโปรแกรม C หรือ C++ ตรงที่โปรแกรม Java ไม่ใช่ไฟล์ปฏิบัติการอิสระ แต่ประกอบด้วยไฟล์คลาสแยกจำนวนมาก แต่ละไฟล์คลาสสอดคล้องกับคลาส Java นอกจากนี้ไฟล์คลาสเหล่านี้จะไม่โหลดลงในหน่วยความจำทันที แต่จะโหลดเมื่อโปรแกรมต้องการ ตัวโหลดคลาสเป็นเครื่องมือที่ใช้ในเครื่องเสมือน Java เพื่อโหลดคลาสลงในหน่วยความจำ ยิ่งไปกว่านั้น ตัวโหลดคลาส Java ยังถูกนำไปใช้ใน Java อีกด้วย วิธีนี้ทำให้คุณสามารถสร้างคลาสโหลดเดอร์ของคุณเองได้อย่างง่ายดาย โดยไม่ต้องมีความเข้าใจเชิงลึกเกี่ยวกับ Java virtual machine
ทำไมต้องสร้างคลาสโหลดเดอร์?
ตอนนี้ Java Virtual Gold มีคลาสโหลดเดอร์อยู่แล้ว เราจำเป็นต้องสร้างคลาสอื่นขึ้นมาเองหรือไม่ เป็นคำถามที่ดี ตัวโหลดคลาสเริ่มต้นรู้วิธีโหลดคลาสจากระบบโลคัลเท่านั้น เมื่อโปรแกรมของคุณถูกคอมไพล์อย่างสมบูรณ์ โดยทั่วไปแล้วคลาสโหลดเดอร์เริ่มต้นจะทำงานได้ดี แต่สิ่งที่น่าตื่นเต้นที่สุดอย่างหนึ่งเกี่ยวกับ Java ก็คือการโหลดคลาสจากเครือข่ายได้ง่ายเพียงใด แทนที่จะโหลดเฉพาะในเครื่อง
ตัวอย่างเช่น เบราว์เซอร์สามารถโหลดคลาสผ่านตัวโหลดคลาสแบบกำหนดเองได้ มีหลายวิธีในการโหลดคลาส หนึ่งในสิ่งที่น่าตื่นเต้นที่สุดเกี่ยวกับ Java ก็คือ คุณสามารถปรับแต่งมันได้นอกเหนือจากจากท้องถิ่นหรือเครือข่าย:
* ตรวจสอบลายเซ็นดิจิทัลโดยอัตโนมัติก่อนที่จะรันโค้ดที่ไม่น่าเชื่อถือ
* ถอดรหัสรหัสตามรหัสผ่านที่ผู้ใช้ให้ไว้
* สร้างคลาสแบบไดนามิกตามความต้องการของผู้ใช้ ทุกสิ่งที่คุณสนใจสามารถรวมเข้ากับแอปพลิเคชันของคุณได้อย่างง่ายดายในรูปแบบของ bytecode ตัวอย่างของตัวโหลดคลาสแบบกำหนดเองหากคุณใช้ JDK (Java Software Development Kit) appletviewer (เบราว์เซอร์แอปพลิเคชันขนาดเล็ก) หรืออื่น ๆ
สำหรับเบราว์เซอร์ที่ฝังตัว Java คุณใช้คลาสโหลดเดอร์แบบกำหนดเองอยู่แล้ว เมื่อ Sun เปิดตัวภาษา Java ครั้งแรก หนึ่งในสิ่งที่น่าตื่นเต้นที่สุดคือการได้เห็นว่า Java รันโค้ดที่ดาวน์โหลดจากเว็บไซต์ระยะไกลอย่างไร ดำเนินการจากไซต์ระยะไกลผ่าน HTTP
รหัสไบต์ที่ส่งโดยการเชื่อมต่อ P ดูแปลกไปเล็กน้อย ใช้งานได้เนื่องจาก Java มีความสามารถในการติดตั้งคลาสโหลดเดอร์แบบกำหนดเอง เบราว์เซอร์แอปเพล็ตมีคลาสโหลดเดอร์ คลาสโหลดเดอร์นี้ไม่พบคลาส Java ในเครื่อง แต่จะเข้าถึงเซิร์ฟเวอร์ระยะไกล โหลดไฟล์ bytecode ดั้งเดิมผ่าน HTTP จากนั้นแปลงเป็นคลาส Java ในเครื่องเสมือน Java แน่นอนว่าคลาสโหลดเดอร์ทำสิ่งอื่นๆ มากมาย: พวกมันบล็อกคลาส Java ที่ไม่ปลอดภัย และป้องกันไม่ให้แอปเพล็ตที่แตกต่างกันบนเพจต่างๆ เข้ามารบกวนซึ่งกันและกัน Echidna ซึ่งเป็นแพ็คเกจที่เขียนโดย Luke Gorrie เป็นแพ็คเกจซอฟต์แวร์ Java แบบเปิดที่อนุญาตให้แอปพลิเคชัน Java หลายตัวทำงานอย่างปลอดภัยในเครื่องเสมือน Java ป้องกันการรบกวนระหว่างแอปพลิเคชันโดยใช้ตัวโหลดคลาสแบบกำหนดเองเพื่อให้แต่ละแอปพลิเคชันมีสำเนาของไฟล์คลาส
ตัวอย่างคลาสโหลดเดอร์ของเรา ตอนนี้เรารู้แล้วว่าคลาสโหลดเดอร์ทำงานอย่างไร และวิธีการกำหนดคลาสโหลดเดอร์ของเราเอง เราจึงสร้างคลาสโหลดเดอร์แบบกำหนดเองที่ชื่อว่า CompilingClassLoader (CCL) CCL ทำการคอมไพล์ให้เรา ดังนั้นเราจึงไม่จำเป็นต้องคอมไพล์ด้วยตนเอง โดยพื้นฐานแล้วเทียบเท่ากับการมีโปรแกรม "make" ที่สร้างในสภาพแวดล้อมรันไทม์ของเรา
หมายเหตุ: ก่อนที่เราจะดำเนินการขั้นตอนต่อไป จำเป็นต้องเข้าใจแนวคิดที่เกี่ยวข้องบางประการก่อน
ระบบได้รับการปรับปรุงอย่างมากใน JDK เวอร์ชัน 1.2 (ซึ่งเราเรียกว่าแพลตฟอร์ม Java 2) บทความนี้เขียนภายใต้ JDK 1.0 และ 1.1 แต่ทุกอย่างจะใช้งานได้ในเวอร์ชันที่ใหม่กว่า ClassLoader ได้รับการปรับปรุงใน Java2 ด้วย
บทนำโดยละเอียดมีอยู่ในส่วนที่ห้า
ส่วนที่ 3 ภาพรวมของโครงสร้างของ ClassLoader วัตถุประสงค์พื้นฐานของคลาสโหลดเดอร์คือเพื่อรองรับคำขอสำหรับคลาส Java เมื่อเครื่องเสมือน Java ต้องการคลาส มันจะตั้งชื่อคลาสให้กับตัวโหลดคลาส จากนั้นตัวโหลดคลาสจะพยายามส่งคืนอินสแตนซ์ของคลาสที่เกี่ยวข้อง ตัวโหลดคลาสแบบกำหนดเองสามารถสร้างขึ้นได้โดยการแทนที่วิธีการที่เกี่ยวข้องในขั้นตอนต่างๆ ต่อไปเราจะเรียนรู้เกี่ยวกับวิธีการหลักบางประการของตัวโหลดคลาส คุณจะเข้าใจว่าเมธอดเหล่านี้ทำงานอย่างไร และทำงานอย่างไรเมื่อโหลดไฟล์คลาส คุณจะทราบด้วยว่าคุณต้องเขียนโค้ดใดเมื่อสร้างตัวโหลดคลาสแบบกำหนดเอง ในส่วนถัดไป คุณจะใช้ประโยชน์จากความรู้นี้และ CompilingCl แบบกำหนดเองของเรา
assLoader ทำงานร่วมกัน
วิธีการ loadClass
ClassLoader.loadClass() เป็นจุดเริ่มต้นของ ClassLoader ลายเซ็นวิธีการมีดังนี้:
คลาส loadClass (ชื่อสตริง, แก้ไขบูลีน);
ชื่อพารามิเตอร์ระบุชื่อเต็มของคลาส (รวมถึงชื่อแพ็กเกจ) ที่ต้องการโดยเครื่องเสมือน Java เช่น Foo หรือ java.lang.Object
พารามิเตอร์การแก้ไขระบุว่าจำเป็นต้องแก้ไขคลาสหรือไม่ คุณสามารถเข้าใจความละเอียดของคลาสว่าพร้อมสำหรับการรันอย่างสมบูรณ์หรือไม่ โดยทั่วไปไม่จำเป็นต้องแยกวิเคราะห์ หากเครื่องเสมือน Java เพียงต้องการทราบว่าคลาสนี้มีอยู่หรือต้องการทราบคลาสพาเรนต์ การแยกวิเคราะห์ก็ไม่จำเป็นเลย ใน Java 1.1 และเวอร์ชันก่อนหน้า หากคุณต้องการปรับแต่งคลาสโหลดเดอร์ เมธอด loadClass เป็นเมธอดเดียวที่จำเป็นต้องถูกแทนที่ในคลาสย่อย
(ClassLoader เปลี่ยนไปใน Java1.2 และจัดเตรียมเมธอด findClass())
วิธีการกำหนดClass
DefinClass เป็นวิธีการที่ลึกลับมากใน ClassLoader วิธีการนี้สร้างอินสแตนซ์ของคลาสจากอาร์เรย์ไบต์ อาร์เรย์ไบต์ดิบที่มีข้อมูลนี้อาจมาจากระบบไฟล์หรือจากเครือข่าย DefinClass แสดงให้เห็นถึงความซับซ้อน ความลึกลับ และการพึ่งพาแพลตฟอร์มของ Java Virtual Machine โดยจะตีความโค้ดไบต์เพื่อแปลงเป็นโครงสร้างข้อมูลรันไทม์ ตรวจสอบความถูกต้อง และอื่นๆ แต่ไม่ต้องกังวล คุณไม่จำเป็นต้องดำเนินการใดๆ นี้ จริงๆ แล้ว คุณไม่สามารถแทนที่มันได้เลย
เพราะวิธีการแก้ไขโดยคำสำคัญสุดท้าย
วิธีการค้นหาSystemClass
เมธอด findSystemClass โหลดไฟล์จากระบบโลคัล ค้นหาไฟล์คลาสบนระบบโลคัล และหากพบ จะเรียกใช้
DefinClass แปลงอาร์เรย์ไบต์ดั้งเดิมให้เป็นอ็อบเจ็กต์คลาส นี่เป็นกลไกเริ่มต้นสำหรับเครื่องเสมือน Java เพื่อโหลดคลาสเมื่อรันแอปพลิเคชัน Java สำหรับตัวโหลดคลาสแบบกำหนดเอง เราจำเป็นต้องใช้ findSystemClass หลังจากที่เราไม่สามารถโหลดได้ เหตุผลนั้นง่ายมาก: ตัวโหลดคลาสของเรามีหน้าที่รับผิดชอบในการดำเนินการตามขั้นตอนบางอย่างในการโหลดคลาส แต่ไม่ใช่ทุกคลาส ตัวอย่างเช่น,
แม้ว่าตัวโหลดคลาสของเราจะโหลดบางคลาสจากไซต์ระยะไกล แต่ก็ยังมีคลาสพื้นฐานอีกมากมายที่ต้องโหลดจากระบบภายในเครื่อง
คลาสเหล่านี้ไม่เป็นปัญหาสำหรับเรา ดังนั้นเราจึงปล่อยให้เครื่องเสมือน Java โหลดคลาสเหล่านี้ด้วยวิธีเริ่มต้น: จากระบบภายในเครื่อง นี่คือสิ่งที่ findSystemClass ทำ กระบวนการทั้งหมดมีคร่าวๆดังนี้:
* เครื่องเสมือน Java ร้องขอตัวโหลดคลาสแบบกำหนดเองของเราเพื่อโหลดคลาส
* เราตรวจสอบว่าไซต์ระยะไกลมีคลาสที่ต้องโหลดหรือไม่
*ถ้ามีเราก็ได้คลาสนี้
* ถ้าไม่เช่นนั้น เราคิดว่าคลาสนี้อยู่ในไลบรารีคลาสพื้นฐานและเรียก findSystemClass เพื่อโหลดจากระบบไฟล์
ในคลาสโหลดเดอร์แบบกำหนดเองส่วนใหญ่ คุณควรเรียก findSystemClass ก่อนเพื่อประหยัดเวลาในการค้นหาจากระยะไกล
ที่จริงแล้ว ดังที่เราจะเห็นในหัวข้อถัดไป Java virtual machine จะได้รับอนุญาตให้โหลดคลาสจากระบบไฟล์ในเครื่องได้ก็ต่อเมื่อเราแน่ใจว่าเราได้คอมไพล์โค้ดของเราโดยอัตโนมัติแล้ว
วิธีการแก้ไขClass
ตามที่กล่าวไว้ข้างต้น บันทึกคลาสสามารถแบ่งออกเป็นการโหลดบางส่วน (โดยไม่ต้องแยกวิเคราะห์) และการโหลดที่สมบูรณ์ (รวมถึงการแยกวิเคราะห์) เมื่อเราสร้างคลาสโหลดเดอร์แบบกำหนดเอง เราอาจจำเป็นต้องเรียก solveClass
MethodfindLoadedClass
findLoadedClass ใช้แคช: เมื่อจำเป็นต้องโหลดคลาสเพื่อโหลดคลาส คุณสามารถเรียกใช้เมธอดนี้ก่อนเพื่อดูว่ามีการโหลดคลาสเพื่อป้องกันการโหลดคลาสที่โหลดแล้วซ้ำหรือไม่ ต้องเรียกวิธีนี้ก่อน มาดูกันว่าวิธีการเหล่านี้จัดรวมกันอย่างไร
ตัวอย่างการใช้งาน loadClass ของเราดำเนินการตามขั้นตอนต่อไปนี้ (เราไม่ได้ระบุเทคโนโลยีเฉพาะเพื่อรับไฟล์คลาส - อาจมาจากเครือข่าย จากแพ็คเกจที่บีบอัด หรือคอมไพล์แบบไดนามิก ไม่ว่าในกรณีใด สิ่งที่เราได้รับคือไฟล์ bytecode ต้นฉบับ)
* เรียก findLoadedClass เพื่อตรวจสอบว่าคลาสนี้โหลดแล้วหรือไม่
* ถ้าไม่โหลด เราจะได้อาร์เรย์ไบต์ดั้งเดิมมา
* หากได้รับอาร์เรย์แล้ว ให้เรียก DefinClass เพื่อแปลงเป็นคลาสอ็อบเจ็กต์
* หากไม่สามารถรับอาร์เรย์ไบต์ดั้งเดิมได้ ให้เรียก findSystemClass เพื่อตรวจสอบว่าสามารถบันทึกจากระบบไฟล์ในเครื่องได้หรือไม่
* หากการแก้ไขพารามิเตอร์เป็นจริง ให้เรียก solveClass เพื่อแก้ไขอ็อบเจ็กต์คลาส
* หากไม่พบคลาส ให้ส่ง ClassNotFoundException
* มิฉะนั้น ให้ส่งคืนคลาสนี้
ตอนนี้เรามีความเข้าใจที่ครอบคลุมมากขึ้นเกี่ยวกับความรู้ในการทำงานของคลาสโหลดเดอร์แล้ว เราสามารถสร้างคลาสโหลดเดอร์แบบกำหนดเองได้ ในส่วนถัดไป เราจะพูดถึง CCL
ส่วนที่ 4. CompilingClassLoader
CCL แสดงให้เราเห็นฟังก์ชั่นของคลาสโหลดเดอร์ จุดประสงค์ของ CCL คือการเปิดใช้งานโค้ดของเราให้คอมไพล์และอัปเดตโดยอัตโนมัติ นี่คือวิธีการทำงาน:
* เมื่อมีการร้องขอคลาส ให้ตรวจสอบก่อนว่ามีไฟล์คลาสอยู่ในไดเร็กทอรีปัจจุบันและไดเร็กทอรีย่อยของดิสก์หรือไม่
* หากไม่มีไฟล์คลาส แต่มีไฟล์ซอร์สโค้ด ให้เรียกคอมไพเลอร์ Java เพื่อคอมไพล์และสร้างไฟล์คลาส
* หากมีไฟล์คลาสอยู่แล้ว ให้ตรวจสอบว่าไฟล์คลาสนั้นเก่ากว่าไฟล์ซอร์สโค้ดหรือไม่ หากไฟล์คลาสเก่ากว่าไฟล์ซอร์สโค้ด ให้เรียกคอมไพลเลอร์ Java เพื่อสร้างไฟล์คลาสใหม่
* หากการคอมไพล์ล้มเหลว หรือไม่สามารถสร้างไฟล์คลาสจากไฟล์ต้นฉบับได้เนื่องจากสาเหตุอื่น ให้ส่งข้อยกเว้น ClassNotFou
ndข้อยกเว้น
* หากคุณยังไม่ได้รับคลาสนี้ คลาสนั้นอาจมีอยู่ในไลบรารีคลาสอื่น เรียก findSystemClass เพื่อดูว่าสามารถพบได้หรือไม่
* หากไม่พบ ให้โยน ClassNotFoundException
* มิฉะนั้น ให้ส่งคืนคลาสนี้
การคอมไพล์ Java มีการใช้งานอย่างไร?
ก่อนจะไปไกลกว่านี้ เราต้องเข้าใจกระบวนการคอมไพล์ Java ก่อน โดยปกติ คอมไพลเลอร์ Java จะคอมไพล์เฉพาะคลาสที่ระบุเท่านั้น นอกจากนี้ยังจะรวบรวมคลาสอื่น ๆ ที่เกี่ยวข้องหากจำเป็นโดยคลาสที่ระบุ CCL จะรวบรวมคลาสที่เราต้องรวบรวมในแอปพลิเคชันทีละรายการ อย่างไรก็ตาม โดยทั่วไปแล้ว หลังจากที่คอมไพเลอร์คอมไพล์คลาสแรกแล้ว
CCL จะพบว่ามีการรวบรวมคลาสที่เกี่ยวข้องที่จำเป็นอื่นๆ แล้ว ทำไม คอมไพลเลอร์ Java ใช้กฎที่คล้ายกันเหมือนที่เราทำ: หากไม่มีคลาสหรือไฟล์ต้นฉบับได้รับการอัปเดต คลาสนั้นจะถูกคอมไพล์ โดยพื้นฐานแล้ว Java คอมไพเลอร์นั้นนำหน้า CCL หนึ่งก้าว และงานส่วนใหญ่ทำโดยคอมไพเลอร์ Java เราดูเหมือนว่า CCL กำลังรวบรวมคลาสเหล่านี้
ในกรณีส่วนใหญ่ คุณจะพบว่ามีการเรียกคอมไพเลอร์ในคลาสฟังก์ชันหลัก เพียงเท่านั้น แค่เรียกง่ายๆ ก็เพียงพอแล้ว อย่างไรก็ตาม มีกรณีพิเศษที่คลาสเหล่านี้ไม่ได้รับการคอมไพล์ในครั้งแรกที่ปรากฏ หากคุณโหลดคลาสตามชื่อโดยใช้เมธอด Class.forName คอมไพเลอร์ Java จะไม่ทราบว่าคลาสนั้นจำเป็นหรือไม่ ในกรณีนี้
คุณพบว่า CCL เรียกคอมไพเลอร์อีกครั้งเพื่อคอมไพล์คลาส รหัสในส่วนที่ 6 แสดงให้เห็นถึงกระบวนการนี้
การใช้ CompilationClassLoader
ในการใช้ CCL เราไม่สามารถรันโปรแกรมของเราได้โดยตรง แต่จะต้องรันในลักษณะพิเศษดังนี้:
% จาวาฟู arg1 arg2
เราดำเนินการเช่นนี้:
% ชวา CCLRun ฟู arg1 arg2
CCLRun เป็นโปรแกรม stub พิเศษที่สร้าง CompilingClassLoader และใช้เพื่อโหลดคลาสฟังก์ชันหลักของเรา เพื่อให้แน่ใจว่าโปรแกรมทั้งหมดจะถูกโหลดโดย CompilingClassLoader CCLRun ใช้ Ja
API การสะท้อน va เรียกใช้ฟังก์ชันหลักของคลาสฟังก์ชันหลักและส่งพารามิเตอร์ไปยังฟังก์ชันนี้ หากต้องการเรียนรู้เพิ่มเติม โปรดดูซอร์สโค้ดในส่วนที่ 6
ลองใช้ตัวอย่างเพื่อสาธิตวิธีการทำงานของกระบวนการทั้งหมด
โปรแกรมหลักคือคลาสที่เรียกว่า Foo ซึ่งสร้างอินสแตนซ์ของคลาส Bar อินสแตนซ์ Bar นี้จะสร้างอินสแตนซ์ของคลาส Baz ซึ่งมีอยู่ในแพ็คเกจ baz นี่เป็นการสาธิตวิธีที่ CCL โหลดคลาสจากแพ็คเกจย่อย Bar ยังโหลดคลาส Boo ตามชื่อคลาสด้วย
ซึ่ง CCL ก็ทำได้เช่นกัน โหลดคลาสทั้งหมดแล้วและพร้อมที่จะรัน ใช้ซอร์สโค้ดจากบทที่ 6 เพื่อรันโปรแกรมนี้ คอมไพล์ CCLRun และ CompilingClassLoader ตรวจสอบให้แน่ใจว่าคุณไม่ได้คอมไพล์คลาสอื่น (Foo, Bar, Baz, a
nd Boo) มิฉะนั้น CCL จะไม่ทำงาน
% ชวา CCLRun ฟู arg1 arg2
CCL: กำลังรวบรวม Foo.java...
ฟู! arg1 arg2
บาร์! arg1 arg2
บาส! arg1 arg2
CCL: กำลังรวบรวม Boo.java...
บู!
โปรดสังเกตว่าคอมไพเลอร์ถูกเรียกเป็นครั้งแรกสำหรับ Foo.java และ Bar และ baz.Baz ก็ถูกคอมไพล์ร่วมกันเช่นกัน แล้วก็เหมือนบู
เมื่อจำเป็นต้องโหลดช่องสัญญาณ CCL จะเรียกคอมไพเลอร์อีกครั้งเพื่อคอมไพล์
ส่วนที่ 5 ภาพรวมของการปรับปรุง Class Loader ใน Java 2 ใน Java 1.2 และเวอร์ชันที่ใหม่กว่า class loader ได้รับการปรับปรุงอย่างมาก รหัสเก่ายังคงใช้งานได้ แต่ระบบใหม่ทำให้การใช้งานของเราง่ายขึ้น โมเดลใหม่นี้คือโมเดลการมอบสิทธิ์พร็อกซี ซึ่งหมายความว่าหากตัวโหลดคลาสไม่พบคลาส มันจะขอให้ตัวโหลดคลาสพาเรนต์ค้นหาคลาสนั้น ตัวโหลดคลาสของระบบเป็นบรรพบุรุษของตัวโหลดคลาสทั้งหมด ตัวโหลดคลาสของระบบจะโหลดคลาสตามค่าเริ่มต้น นั่นคือจากระบบไฟล์ในเครื่อง โดยทั่วไปการเอาชนะเมธอด loadClass จะพยายามโหลดคลาสหลายวิธี หากคุณเขียนคลาสโหลดเดอร์จำนวนมาก คุณจะพบว่าคุณเพียงแค่ทำการแก้ไขบางอย่างในวิธีการที่ซับซ้อนนี้ซ้ำแล้วซ้ำอีก การใช้งานเริ่มต้นของ loadClass ใน Java 1.2 รวมถึงวิธีการทั่วไปในการค้นหาคลาส ซึ่งช่วยให้คุณสามารถแทนที่เมธอด findClass และ loadClass เพื่อเรียกใช้เมธอด findClass ได้อย่างเหมาะสม ข้อดีของสิ่งนี้คือคุณไม่จำเป็นต้องแทนที่ loadClass คุณเพียงแค่ต้องแทนที่ findClass เท่านั้น ซึ่งจะช่วยลดภาระงาน
วิธีการใหม่: findClass
วิธีการนี้ถูกเรียกโดยการใช้งานเริ่มต้นของ loadClass เป้าหมายของ findClass คือการรวมโค้ดเฉพาะตัวโหลดคลาสทั้งหมด
ไม่จำเป็นต้องทำซ้ำโค้ด (เช่น การเรียกคลาสโหลดเดอร์ของระบบเมื่อวิธีการที่ระบุล้มเหลว)
วิธีการใหม่: getSystemClassLoader
ไม่ว่าคุณจะแทนที่เมธอด findClass และ loadClass เมธอด getSystemClassLoader สามารถเข้าถึงตัวโหลดคลาสระบบได้โดยตรง (แทนที่จะเข้าถึงโดยอ้อมผ่าน findSystemClass)
วิธีการใหม่: getParent
เพื่อมอบหมายคำขอให้กับตัวโหลดคลาสหลัก คุณสามารถรับตัวโหลดคลาสหลักของตัวโหลดคลาสนี้ได้ด้วยวิธีนี้ คุณสามารถมอบหมายคำขอให้กับตัวโหลดคลาสหลักได้เมื่อวิธีการเฉพาะในตัวโหลดคลาสแบบกำหนดเองไม่พบคลาส ตัวโหลดคลาสหลักของตัวโหลดคลาสประกอบด้วยรหัสที่สร้างตัวโหลดคลาส
ส่วนที่ 6 ซอร์สโค้ด
การคอมไพล์ClassLoader.java
ต่อไปนี้เป็นเนื้อหาของไฟล์ CompilingClassLoader.java
นำเข้า java.io.*;
-
CompilingClassLoader รวบรวมไฟล์ต้นฉบับ Java แบบไดนามิก จะตรวจสอบว่ามีไฟล์ .class อยู่หรือไม่ และไฟล์ .class เก่ากว่าไฟล์ต้นฉบับหรือไม่
-
CompilingClassLoader คลาสสาธารณะขยาย ClassLoader
-
//ระบุชื่อไฟล์ อ่านเนื้อหาไฟล์ทั้งหมดจากดิสก์ และส่งคืนอาร์เรย์ไบต์
ไบต์ส่วนตัว [] getBytes (ชื่อไฟล์สตริง) พ่น IOException {
// รับขนาดไฟล์
ไฟล์ ไฟล์ = ไฟล์ใหม่ (ชื่อไฟล์);
ยาว len = file.length();
//สร้างอาร์เรย์ให้เพียงพอสำหรับจัดเก็บเนื้อหาของไฟล์
ไบต์ raw[] = ไบต์ใหม่ [(int)len];
//เปิดไฟล์
FileInputStream fin = FileInputStream ใหม่ (ไฟล์);
// อ่านเนื้อหาทั้งหมด หากไม่สามารถอ่านได้ แสดงว่าเกิดข้อผิดพลาด
int r = fin.read (ดิบ);
ถ้า (r != len)
โยน IOException ใหม่ ( "ไม่สามารถอ่านทั้งหมดได้, "+r+" != "+len );
//อย่าลืมปิดไฟล์
ครีบ.ปิด();
// ส่งคืนอาร์เรย์นี้
กลับดิบ;
-
// สร้างกระบวนการเพื่อคอมไพล์ซอร์สไฟล์ Java ที่ระบุและระบุพารามิเตอร์ไฟล์ หากการคอมไพล์สำเร็จ ให้คืนค่าเป็นจริง มิฉะนั้น
//คืนค่าเท็จ
คอมไพล์บูลีนส่วนตัว (String javaFile) พ่น IOException {
//แสดงความคืบหน้าในปัจจุบัน
System.out.println( "CCL: กำลังคอมไพล์ "+javaFile+"..." );
//เริ่มคอมไพเลอร์
กระบวนการ p = Runtime.getRuntime().exec( "javac "+javaFile );
//รอให้การคอมไพล์จบลง
พยายาม {
p.waitFor();
} catch( InterruptedException เช่น ) { System.out.println( เช่น )
// ตรวจสอบโค้ดส่งคืนเพื่อดูว่ามีข้อผิดพลาดในการคอมไพล์หรือไม่
int ret = p.exitValue();
//ส่งคืนว่าการคอมไพล์สำเร็จหรือไม่
กลับ ret==0;
-
// รหัสหลักของตัวโหลดคลาส - คลาสการโหลดจะรวบรวมไฟล์ต้นฉบับโดยอัตโนมัติเมื่อจำเป็น
loadClass คลาสสาธารณะ (ชื่อสตริง, แก้ไขบูลีน)
พ่น ClassNotFoundException {
//จุดประสงค์ของเราคือการได้รับคลาสอ็อบเจ็กต์
คลาสคลาส = null;
// ก่อนอื่นให้ตรวจสอบว่าคลาสนี้ได้รับการประมวลผลแล้วหรือไม่
clas = findLoadedClass( ชื่อ );
//System.out.println( "findLoadedClass: "+clas );
// รับชื่อพาธผ่านชื่อคลาส เช่น java.lang.Object => java/lang/Object
สตริง fileStub = name.replace( ''''.'''', ''''/'''' );
// สร้างวัตถุที่ชี้ไปยังไฟล์ต้นฉบับและไฟล์คลาส
สตริง javaFilename = fileStub+".java";
สตริง classFilename = fileStub+".class";
ไฟล์ javaFile = ไฟล์ใหม่ ( javaFilename );
ไฟล์ classFile = ไฟล์ใหม่ ( classFilename );
//System.out.println( "j "+javaFile.lastModified()+" c "
//+classFile.lastModified() );
// ก่อนอื่น พิจารณาว่าจำเป็นต้องคอมไพล์หรือไม่ หากไฟล์ต้นฉบับมีอยู่ แต่ไม่มีไฟล์คลาส หรือมีทั้งสองไฟล์ยกเว้นไฟล์ต้นฉบับ
// Newer ระบุว่าจำเป็นต้องคอมไพล์
ถ้า (javaFile.exists() &&(!classFile.exists() ||
javaFile.lastModified() > classFile.lastModified())) {
พยายาม {
// คอมไพล์ หากการคอมไพล์ล้มเหลว เราต้องประกาศสาเหตุของความล้มเหลว (เพียงใช้คลาสเก่าไม่เพียงพอ)
ถ้า (!compile( javaFilename ) || !classFile.exists()) {
โยน ClassNotFoundException ใหม่ ( "การคอมไพล์ล้มเหลว: "+javaFilename );
-
} จับ (IOException เช่น) {
// ข้อผิดพลาด IO อาจเกิดขึ้นระหว่างการคอมไพล์
โยน ClassNotFoundException ใหม่ (ie.toString());
-
-
// ตรวจสอบให้แน่ใจว่าได้รับการคอมไพล์อย่างถูกต้องหรือไม่จำเป็นต้องคอมไพล์ เราจะเริ่มโหลดไบต์ดิบ
พยายาม {
// อ่านไบต์
ไบต์ raw[] = getBytes( classFilename );
//แปลงเป็นวัตถุคลาส
clas = DefinClass (ชื่อ, ดิบ, 0, raw.length);
} จับ (IOException เช่น) {
// นี่ไม่ได้หมายความว่าล้มเหลว บางทีคลาสที่เรากำลังเผชิญอยู่อาจอยู่ในไลบรารีคลาสในเครื่อง เช่น java.lang.Object
-
//System.out.println( "defineClass: "+clas );
//บางทีอาจอยู่ในไลบรารีของชั้นเรียน ซึ่งโหลดด้วยวิธีเริ่มต้น
ถ้า (clas==null) {
clas = findSystemClass( ชื่อ );
-
//System.out.println( "findSystemClass: "+clas );
// หากการแก้ไขพารามิเตอร์เป็นจริง ให้ตีความคลาสตามต้องการ
ถ้า (แก้ไข && clas != null)
แก้ไขคลาส (clas);
// หากไม่ได้รับคลาส แสดงว่าเกิดข้อผิดพลาด
ถ้า (คลาส == โมฆะ)
โยน ClassNotFoundException ใหม่ (ชื่อ);
// มิฉะนั้น ให้ส่งคืนอ็อบเจ็กต์คลาสนี้
กลับชั้น;
-
-
CCRun.java
นี่คือไฟล์ CCRun.java
นำเข้า java.lang.reflect.*;
-
CCLRun โหลดคลาสผ่าน CompilingClassLoader เพื่อรันโปรแกรม
-
CCLRun คลาสสาธารณะ
-
โมฆะสาธารณะแบบคงที่ main ( String args [] ) พ่นข้อยกเว้น {
//พารามิเตอร์ตัวแรกระบุคลาสฟังก์ชันหลักที่ผู้ใช้ต้องการเรียกใช้
สตริง progClass = args[0];
//พารามิเตอร์ถัดไปคือพารามิเตอร์ที่ส่งไปยังคลาสฟังก์ชันหลักนี้
สตริง progArgs[] = สตริงใหม่ [args.length-1];
System.arraycopy(args, 1, progArgs, 0, progArgs.length);
// สร้าง CompilingClassLoader
CompilingClassLoader ccl = ใหม่ CompilingClassLoader();
//โหลดคลาสฟังก์ชันหลักผ่าน CCL
คลาส clas = ccl.loadClass( progClass );
// ใช้การสะท้อนเพื่อเรียกใช้ฟังก์ชันหลักและส่งพารามิเตอร์
// สร้างคลาสอ็อบเจ็กต์ที่แสดงถึงประเภทพารามิเตอร์ของฟังก์ชันหลัก
คลาส mainArgType[] = { (สตริงใหม่[0]).getClass() };
// ค้นหาฟังก์ชันหลักมาตรฐานในชั้นเรียน
วิธีการ main = clas.getMethod( "main", mainArgType );
// สร้างรายการพารามิเตอร์ - ในกรณีนี้คืออาร์เรย์ของสตริง
วัตถุ argsArray[] = { progArgs };
// เรียกใช้ฟังก์ชันหลัก
main. วิงวอน ( null, argsArray );
-
-
Foo.java
ต่อไปนี้เป็นเนื้อหาของไฟล์ Foo.java
ชั้นเรียนสาธารณะฟู
-
โมฆะสาธารณะแบบคงที่ main ( String args [] ) พ่นข้อยกเว้น {
System.out.println( "foo! "+args[0]+" "+args[1] );
แถบใหม่ (args[0], args[1] );
-
-
บาร์.ชวา
ต่อไปนี้เป็นเนื้อหาของไฟล์ Bar.java
นำเข้า baz.*;
บาร์คลาสสาธารณะ
-
บาร์สาธารณะ (สตริง a, สตริง b) {
System.out.println( "bar! "+a+" "+b );
ใหม่บาซ(a, b );
พยายาม {
คลาส booClass = Class.forName( "บู" );
วัตถุ boo = booClass.newInstance();
} จับ (ข้อยกเว้นจ) {
e.printStackTrace();
-
-
-
baz/Baz.java
ต่อไปนี้เป็นเนื้อหาของไฟล์ baz/Baz.java
แพ็คเกจบาซ;
คลาสสาธารณะ Baz
-
Baz สาธารณะ (สตริง a, สตริง b) {
System.out.println( "baz! "+a+" "+b );
-
-
Boo.java
ต่อไปนี้เป็นเนื้อหาของไฟล์ Boo.java
ชั้นเรียนสาธารณะ Boo
-
publicBoo() {
System.out.println( "บู่!" );
-
-
ส่วนที่ 7 สรุปโดยสรุป หลังจากอ่านบทความนี้แล้ว คุณทราบหรือไม่ว่าการสร้างคลาสโหลดเดอร์แบบกำหนดเองช่วยให้คุณสามารถเจาะลึกเข้าไปในระบบภายในของเครื่องเสมือน Java ได้ คุณสามารถโหลดไฟล์คลาสจากทรัพยากรใดๆ หรือสร้างมันขึ้นมาแบบไดนามิก เพื่อให้คุณสามารถทำสิ่งต่างๆ มากมายที่คุณสนใจโดยการขยายฟังก์ชันเหล่านี้ และคุณยังสามารถทำให้ฟังก์ชันอันทรงพลังบางอย่างสมบูรณ์ได้อีกด้วย
หัวข้ออื่นๆ เกี่ยวกับ ClassLoader ดังที่กล่าวไว้ในตอนต้นของบทความนี้ ตัวโหลดคลาสแบบกำหนดเองมีบทบาทสำคัญในเบราว์เซอร์ Java ที่ฝังตัวและเบราว์เซอร์แอปเพล็ต