การสอนคลาส Java ขั้นสูง: คู่มือการรีโหลดคลาส
เผยแพร่แล้ว: 2022-03-11ในโครงการพัฒนา Java เวิร์กโฟลว์ทั่วไปเกี่ยวข้องกับการรีสตาร์ทเซิร์ฟเวอร์ทุกครั้งที่มีการเปลี่ยนแปลงคลาส และไม่มีใครบ่นเกี่ยวกับเรื่องนี้ นั่นคือข้อเท็จจริงเกี่ยวกับการพัฒนา Java เราทำงานแบบนั้นตั้งแต่วันแรกกับ Java แต่การรีโหลดคลาส Java นั้นยากไหม และปัญหานั้นอาจเป็นทั้งความท้าทายและน่าตื่นเต้นในการแก้ปัญหาสำหรับนักพัฒนา Java ที่มีทักษะหรือไม่ ในบทช่วยสอนคลาส Java นี้ ฉันจะพยายามแก้ไขปัญหา ช่วยให้คุณได้รับประโยชน์ทั้งหมดจากการรีโหลดคลาสแบบทันทีทันใด และเพิ่มประสิทธิภาพการทำงานของคุณอย่างมาก
ไม่ค่อยมีการกล่าวถึงการรีโหลดคลาส Java และมีเอกสารประกอบเพียงเล็กน้อยที่สำรวจกระบวนการนี้ ฉันมาที่นี่เพื่อเปลี่ยนสิ่งนั้น บทช่วยสอนคลาส Java นี้จะให้คำอธิบายทีละขั้นตอนของกระบวนการนี้ และช่วยให้คุณเชี่ยวชาญเทคนิคที่เหลือเชื่อนี้ โปรดทราบว่าการนำการโหลดคลาส Java มาใช้ใหม่นั้นต้องใช้ความระมัดระวังอย่างมาก แต่การเรียนรู้วิธีทำจะทำให้คุณอยู่ในลีกใหญ่ ทั้งในฐานะนักพัฒนา Java และในฐานะสถาปนิกซอฟต์แวร์ นอกจากนี้ยังจะไม่เจ็บที่จะเข้าใจวิธีหลีกเลี่ยงข้อผิดพลาด 10 ข้อที่พบบ่อยที่สุดของ Java
การตั้งค่าพื้นที่ทำงาน
ซอร์สโค้ดทั้งหมดสำหรับบทช่วยสอนนี้ถูกอัปโหลดบน GitHub ที่นี่
ในการรันโค้ดในขณะที่คุณทำตามบทช่วยสอนนี้ คุณจะต้องใช้ Maven, Git และ Eclipse หรือ IntelliJ IDEA
หากคุณกำลังใช้ Eclipse:
- รันคำสั่ง
mvn eclipse:eclipse
เพื่อสร้างไฟล์โปรเจ็กต์ของ Eclipse - โหลดโปรเจ็กต์ที่สร้างขึ้น
- ตั้งค่าพาธเอาต์พุตไปยัง
target/classes
หากคุณกำลังใช้ IntelliJ:
- นำเข้าไฟล์
pom
ของโปรเจ็กต์ - IntelliJ จะไม่คอมไพล์อัตโนมัติเมื่อคุณเรียกใช้ตัวอย่าง ดังนั้นคุณต้อง:
- เรียกใช้ตัวอย่างภายใน IntelliJ จากนั้นทุกครั้งที่คุณต้องการคอมไพล์ คุณจะต้องกด
Alt+BE
- เรียกใช้ตัวอย่างภายนอก IntelliJ ด้วย
run_example*.bat
ตั้งค่าคอมไพเลอร์อัตโนมัติของคอมไพเลอร์ IntelliJ เป็นจริง จากนั้น ทุกครั้งที่คุณเปลี่ยนไฟล์จาวา IntelliJ จะคอมไพล์ไฟล์นั้นโดยอัตโนมัติ
ตัวอย่างที่ 1: การรีโหลดคลาสด้วย Java Class Loader
ตัวอย่างแรกจะให้ความเข้าใจทั่วไปเกี่ยวกับตัวโหลดคลาส Java นี่คือซอร์สโค้ด
รับคำจำกัดความคลาส User
ต่อไปนี้:
public static class User { public static int age = 10; }
เราสามารถทำสิ่งต่อไปนี้:
public static void main(String[] args) { Class<?> userClass1 = User.class; Class<?> userClass2 = new DynamicClassLoader("target/classes") .load("qj.blog.classreloading.example1.StaticInt$User"); ...
ในตัวอย่างบทช่วยสอนนี้ จะมีคลาส User
สองคลาสที่โหลดลงในหน่วยความจำ userClass1
จะถูกโหลดโดยตัวโหลดคลาสเริ่มต้นของ JVM และ userClass2
โดยใช้ DynamicClassLoader
ตัวโหลดคลาสที่กำหนดเองซึ่งมีซอร์สโค้ดให้ไว้ในโปรเจ็กต์ GitHub และฉันจะอธิบายในรายละเอียดด้านล่าง
นี่คือส่วนที่เหลือของวิธีการ main
:
out.println("Seems to be the same class:"); out.println(userClass1.getName()); out.println(userClass2.getName()); out.println(); out.println("But why there are 2 different class loaders:"); out.println(userClass1.getClassLoader()); out.println(userClass2.getClassLoader()); out.println(); User.age = 11; out.println("And different age values:"); out.println((int) ReflectUtil.getStaticFieldValue("age", userClass1)); out.println((int) ReflectUtil.getStaticFieldValue("age", userClass2)); }
และผลลัพธ์:
Seems to be the same class: qj.blog.classreloading.example1.StaticInt$User qj.blog.classreloading.example1.StaticInt$User But why there are 2 different class loaders: qj.util.lang.DynamicClassLoader@3941a79c sun.misc.Launcher$AppClassLoader@1f32e575 And different age values: 11 10
อย่างที่คุณเห็นที่นี่ แม้ว่าคลาส User
จะมีชื่อเหมือนกัน แต่จริงๆ แล้วเป็นสองคลาสที่แตกต่างกัน และสามารถจัดการและจัดการแยกกันได้ ค่าอายุแม้ว่าจะประกาศเป็นสแตติก แต่ก็มีอยู่ในสองเวอร์ชัน โดยแนบแยกกับแต่ละคลาส และสามารถเปลี่ยนแปลงได้อย่างอิสระเช่นกัน
ในโปรแกรม Java ปกติ ClassLoader
เป็นพอร์ทัลที่นำคลาสเข้าสู่ JVM เมื่อคลาสหนึ่งต้องการโหลดคลาสอื่น มันเป็นงานของ ClassLoader
ในการโหลด
อย่างไรก็ตาม ในตัวอย่างคลาส Java นี้ ClassLoader
แบบกำหนดเองที่ชื่อ DynamicClassLoader
ใช้เพื่อโหลดเวอร์ชันที่สองของคลาส User
หากแทนที่จะเป็น DynamicClassLoader
เราต้องใช้ตัวโหลดคลาสดีฟอลต์อีกครั้ง (ด้วยคำสั่ง StaticInt.class.getClassLoader()
) คลาส User
เดียวกันจะถูกใช้ เนื่องจากคลาสที่โหลดทั้งหมดจะถูกแคช
DynamicClassLoader
สามารถมี classloaders ได้หลายตัวในโปรแกรม Java ปกติ คลาสที่โหลดคลาสหลักของคุณคือ ClassLoader
เป็นคลาสเริ่มต้น และจากโค้ดของคุณ คุณสามารถสร้างและใช้งานคลาสโหลดเดอร์ได้มากเท่าที่คุณต้องการ นี่จึงเป็นกุญแจสำคัญในการรีโหลดคลาสใน Java DynamicClassLoader
อาจเป็นส่วนที่สำคัญที่สุดของบทช่วยสอนทั้งหมดนี้ ดังนั้นเราต้องเข้าใจว่าการโหลดคลาสแบบไดนามิกทำงานอย่างไรก่อนที่เราจะบรรลุเป้าหมาย
ไม่เหมือนกับพฤติกรรมเริ่มต้นของ ClassLoader
DynamicClassLoader
ของเราสืบทอดกลยุทธ์เชิงรุกมากขึ้น ตัวโหลดคลาสปกติจะให้ลำดับความสำคัญของ ClassLoader
นต์และโหลดเฉพาะคลาสที่พาเรนต์ไม่สามารถโหลดได้ ที่เหมาะกับสถานการณ์ปกติ แต่ไม่ใช่ในกรณีของเรา แต่ DynamicClassLoader
จะพยายามค้นหาเส้นทางคลาสทั้งหมดและแก้ไขคลาสเป้าหมายก่อนที่จะมอบสิทธิ์ให้กับพาเรนต์
ในตัวอย่างข้างต้น DynamicClassLoader
ถูกสร้างขึ้นด้วยพาธคลาสเดียวเท่านั้น: "target/classes"
(ในไดเร็กทอรีปัจจุบันของเรา) จึงสามารถโหลดคลาสทั้งหมดที่อยู่ในตำแหน่งนั้นได้ สำหรับคลาสทั้งหมดที่ไม่ได้อยู่ในนั้น จะต้องอ้างถึง parent classloader ตัวอย่างเช่น เราจำเป็นต้องโหลดคลาส String
ในคลาส StaticInt
และตัวโหลดคลาสของเราไม่มีการเข้าถึง rt.jar
ในโฟลเดอร์ JRE ของเรา ดังนั้นคลาส String
ของตัวโหลดคลาสพาเรนต์จะถูกใช้
รหัสต่อไปนี้มาจาก AggressiveClassLoader
ซึ่งเป็นคลาสพาเรนต์ของ DynamicClassLoader
และแสดงว่าลักษณะการทำงานนี้ถูกกำหนดไว้ที่ใด
byte[] newClassData = loadNewClass(name); if (newClassData != null) { loadedClasses.add(name); return loadClass(newClassData, name); } else { unavaiClasses.add(name); return parent.loadClass(name); }
จดคุณสมบัติต่อไปนี้ของ DynamicClassLoader
:
- คลาสที่โหลดมีประสิทธิภาพเหมือนกันและคุณลักษณะอื่น ๆ เหมือนกับคลาสอื่นที่โหลดโดยตัวโหลดคลาสดีฟอลต์
-
DynamicClassLoader
สามารถรวบรวมขยะพร้อมกับคลาสและอ็อบเจ็กต์ที่โหลดทั้งหมด
ด้วยความสามารถในการโหลดและใช้คลาสเดียวกันได้สองเวอร์ชัน ตอนนี้เรากำลังคิดที่จะทิ้งเวอร์ชันเก่าและโหลดเวอร์ชันใหม่เพื่อแทนที่ ในตัวอย่างต่อไป เราจะทำอย่างนั้น…อย่างต่อเนื่อง
ตัวอย่างที่ 2: การโหลดคลาสซ้ำอย่างต่อเนื่อง
ตัวอย่าง Java ถัดไปนี้จะแสดงให้คุณเห็นว่า JRE สามารถโหลดและรีโหลดคลาสได้ตลอดไป โดยคลาสเก่าถูกทิ้งและรวบรวมขยะ และคลาสใหม่จะโหลดจากฮาร์ดไดรฟ์และนำไปใช้ นี่คือซอร์สโค้ด
นี่คือลูปหลัก:
public static void main(String[] args) { for (;;) { Class<?> userClass = new DynamicClassLoader("target/classes") .load("qj.blog.classreloading.example2.ReloadingContinuously$User"); ReflectUtil.invokeStatic("hobby", userClass); ThreadUtil.sleep(2000); } }
ทุก ๆ สองวินาที คลาส User
เก่าจะถูกทิ้ง คลาสใหม่จะถูกโหลดและเรียกใช้วิธีการ hobby
นี่คือคำจำกัดความคลาส User
:
@SuppressWarnings("UnusedDeclaration") public static class User { public static void hobby() { playFootball(); // will comment during runtime // playBasketball(); // will uncomment during runtime } // will comment during runtime public static void playFootball() { System.out.println("Play Football"); } // will uncomment during runtime // public static void playBasketball() { // System.out.println("Play Basketball"); // } }
เมื่อเรียกใช้แอปพลิเคชันนี้ คุณควรพยายามแสดงความคิดเห็นและยกเลิกการใส่เครื่องหมายรหัสที่ระบุรหัสในคลาส User
คุณจะเห็นว่าจะใช้คำจำกัดความใหม่ล่าสุดเสมอ
นี่คือตัวอย่างผลลัพธ์บางส่วน:
... Play Football Play Football Play Football Play Basketball Play Basketball Play Basketball
ทุกครั้งที่สร้างอินสแตนซ์ใหม่ของ DynamicClassLoader
จะโหลดคลาส User
จากโฟลเดอร์ target/classes
ซึ่งเราได้ตั้งค่า Eclipse หรือ IntelliJ ให้ส่งออกไฟล์คลาสล่าสุด DynamicClassLoader
เก่าทั้งหมดและคลาส User
เก่าจะถูกยกเลิกการเชื่อมโยงและอยู่ภายใต้ตัวรวบรวมขยะ
หากคุณคุ้นเคยกับ JVM HotSpot ก็ควรสังเกตที่โครงสร้างคลาสยังสามารถเปลี่ยนแปลงและโหลดใหม่ได้: วิธี playFootball
จะถูกลบออกและเพิ่มวิธีการ playBasketball
ซึ่งแตกต่างจาก HotSpot ซึ่งอนุญาตให้เปลี่ยนเนื้อหาเมธอดเท่านั้น หรือไม่สามารถโหลดคลาสซ้ำได้
ตอนนี้เราสามารถรีโหลดคลาสได้แล้ว ถึงเวลาลองรีโหลดคลาสหลายๆ คลาสพร้อมกัน มาลองดูกันในตัวอย่างต่อไป
ตัวอย่างที่ 3: การโหลดซ้ำหลายคลาส
ผลลัพธ์ของตัวอย่างนี้จะเหมือนกันกับตัวอย่างที่ 2 แต่จะแสดงวิธีการใช้ลักษณะการทำงานนี้ในโครงสร้างที่เหมือนแอปพลิเคชันมากขึ้นด้วยวัตถุบริบท บริการ และโมเดล ซอร์สโค้ดของตัวอย่างนี้ค่อนข้างใหญ่ ดังนั้นฉันจึงแสดงเฉพาะบางส่วนที่นี่ รหัสที่มาแบบเต็มอยู่ที่นี่
นี่คือวิธีการ main
:
public static void main(String[] args) { for (;;) { Object context = createContext(); invokeHobbyService(context); ThreadUtil.sleep(2000); } }
และวิธีการ createContext
:
private static Object createContext() { Class<?> contextClass = new DynamicClassLoader("target/classes") .load("qj.blog.classreloading.example3.ContextReloading$Context"); Object context = newInstance(contextClass); invoke("init", context); return context; }
วิธีการเรียกใช้ invokeHobbyService
:

private static void invokeHobbyService(Object context) { Object hobbyService = getFieldValue("hobbyService", context); invoke("hobby", hobbyService); }
และนี่คือคลาส Context
:
public static class Context { public HobbyService hobbyService = new HobbyService(); public void init() { // Init your services here hobbyService.user = new User(); } }
และคลาส HobbyService
:
public static class HobbyService { public User user; public void hobby() { user.hobby(); } }
คลาส Context
ในตัวอย่างนี้ซับซ้อนกว่าคลาส User
ในตัวอย่างก่อนหน้ามาก: มีลิงก์ไปยังคลาสอื่น และมีเมธอด init
ที่จะเรียกทุก ๆ ที่สร้างอินสแตนซ์ โดยพื้นฐานแล้วมันคล้ายกับคลาสบริบทของแอปพลิเคชันในโลกแห่งความเป็นจริงมาก (ซึ่งติดตามโมดูลของแอปพลิเคชันและทำการแทรกการพึ่งพา) ดังนั้นความสามารถในการโหลดคลาส Context
นี้ใหม่พร้อมกับคลาสที่เชื่อมโยงทั้งหมดนั้นเป็นขั้นตอนที่ดีในการนำเทคนิคนี้ไปใช้กับชีวิตจริง
เมื่อจำนวนคลาสและอ็อบเจ็กต์เพิ่มขึ้น ขั้นตอน "การดร็อปเวอร์ชันเก่า" ก็จะซับซ้อนมากขึ้นเช่นกัน นี่เป็นเหตุผลที่ใหญ่ที่สุดว่าทำไมการโหลดคลาสจึงยาก หากต้องการดรอปเวอร์ชันเก่า เราจะต้องตรวจสอบให้แน่ใจว่าเมื่อสร้างบริบทใหม่แล้ว การอ้างอิง ทั้งหมด ไปยังคลาสและอ็อบเจ็กต์เก่าจะหายไป เราจะจัดการกับสิ่งนี้อย่างสง่างามได้อย่างไร?
เมธอด main
ที่นี่จะยึดวัตถุบริบทไว้ และ นั่นเป็นลิงก์เดียวที่เชื่อมโยง ไปยังทุกสิ่งที่จำเป็นต้องทิ้ง ถ้าเราทำลายลิงก์นั้น ออบเจ็กต์บริบทและคลาสบริบท และอ็อบเจ็กต์บริการ ... ทั้งหมดจะอยู่ภายใต้ตัวรวบรวมขยะ
คำอธิบายเล็กๆ น้อยๆ เกี่ยวกับสาเหตุที่โดยปกติคลาสนั้นยังคงอยู่ และไม่ได้รับขยะที่เก็บรวบรวม:
- โดยปกติ เราโหลดคลาสทั้งหมดของเราลงใน Java classloader เริ่มต้น
- ความสัมพันธ์ class-classloader เป็นความสัมพันธ์แบบสองทาง โดยที่ class loader จะแคชคลาสทั้งหมดที่โหลดไว้ด้วย
- ตราบใดที่ classloader ยังคงเชื่อมต่อกับเธรดที่ใช้งานอยู่ ทุกอย่าง (คลาสที่โหลดทั้งหมด) จะไม่มีภูมิคุ้มกันต่อตัวรวบรวมขยะ
- ที่กล่าวว่า เว้นแต่ว่าเราสามารถแยกโค้ดที่เราต้องการรีโหลดจากโค้ดที่โหลดไว้แล้วโดย class loader เริ่มต้น การเปลี่ยนแปลงโค้ดใหม่ของเราจะไม่ถูกนำไปใช้ระหว่างรันไทม์
จากตัวอย่างนี้ เราพบว่าการรีโหลดคลาสของแอปพลิเคชันทั้งหมดนั้นค่อนข้างง่าย เป้าหมายคือเพียงเพื่อรักษาการเชื่อมต่อแบบบางและดรอปได้จากเธรดสดไปยังไดนามิกคลาสโหลดเดอร์ที่ใช้งานอยู่ แต่ถ้าเราต้องการไม่ให้บางอ็อบเจกต์ (และคลาสของพวกมัน) ถูก รีโหลด และนำกลับมาใช้ใหม่ระหว่างรอบการรีโหลดล่ะ มาดูตัวอย่างต่อไป
ตัวอย่างที่ 4: การแยกคลาส Spaces แบบต่อเนื่องและโหลดซ้ำ
นี่คือรหัสที่มา..
วิธีการ main
:
public static void main(String[] args) { ConnectionPool pool = new ConnectionPool(); for (;;) { Object context = createContext(pool); invokeService(context); ThreadUtil.sleep(2000); } }
ดังนั้นคุณจะเห็นได้ว่าเคล็ดลับในที่นี้คือการโหลดคลาส ConnectionPool
และสร้างอินสแตนซ์นอกวงจรการโหลดซ้ำ เก็บไว้ในพื้นที่คงอยู่ และส่งผ่านการอ้างอิงไปยังออบเจ็กต์ Context
เมธอด createContext
ก็แตกต่างกันเล็กน้อยเช่นกัน:
private static Object createContext(ConnectionPool pool) { ExceptingClassLoader classLoader = new ExceptingClassLoader( (className) -> className.contains(".crossing."), "target/classes"); Class<?> contextClass = classLoader.load("qj.blog.classreloading.example4.reloadable.Context"); Object context = newInstance(contextClass); setFieldValue(pool, "pool", context); invoke("init", context); return context; }
ต่อจากนี้ไป เราจะเรียกอ็อบเจกต์และคลาสที่รีโหลดทุกรอบว่า "พื้นที่รีโหลดได้" และอื่นๆ - ออบเจ็กต์และคลาสที่ไม่ได้รีไซเคิลและไม่ถูกสร้างใหม่ระหว่างรอบการรีโหลด - "พื้นที่ที่คงอยู่" เราจะต้องมีความชัดเจนมากว่าวัตถุหรือชั้นเรียนใดอยู่ในพื้นที่ใด จึงวาดเส้นแบ่งระหว่างช่องว่างทั้งสองนี้
ตามที่เห็นจากรูปภาพ ไม่เพียงแต่ Context
object และ UserService
object ที่อ้างถึงวัตถุ ConnectionPool
แต่ Context
และ UserService
คลาสยังอ้างถึงคลาส ConnectionPool
ด้วย นี่เป็นสถานการณ์ที่อันตรายมากซึ่งมักจะนำไปสู่ความสับสนและความล้มเหลว ต้องไม่โหลดคลาส ConnectionPool
โดย DynamicClassLoader
ของเรา ต้องมีคลาส ConnectionPool
เดียวเท่านั้นในหน่วยความจำ ซึ่งเป็นคลาสที่โหลดโดย ClassLoader
เริ่มต้น นี่เป็นตัวอย่างหนึ่งที่แสดงให้เห็นว่าเหตุใดจึงต้องระมัดระวังเมื่อออกแบบสถาปัตยกรรมการรีโหลดคลาสใน Java
จะเกิดอะไรขึ้นถ้า DynamicClassLoader
ของเราโหลดคลาส ConnectionPool
โดยไม่ตั้งใจ จากนั้น อ็อบเจ็กต์ ConnectionPool
จากพื้นที่คงอยู่จะไม่สามารถส่งผ่านไปยังอ็อบเจ็กต์ Context
ได้ เนื่องจากอ็อบเจ็กต์ Context
คาดหวังอ็อบเจ็กต์ของคลาสอื่น ซึ่งมีชื่อว่า ConnectionPool
ด้วย แต่จริงๆ แล้วเป็นคลาสอื่น!
แล้วเราจะป้องกัน DynamicClassLoader
ของเราจากการโหลดคลาส ConnectionPool
ได้อย่างไร แทนที่จะใช้ DynamicClassLoader
ตัวอย่างนี้ใช้คลาสย่อยที่ชื่อว่า: ExceptingClassLoader
ซึ่งจะส่งต่อการโหลดไปยัง super classloader ตามฟังก์ชันเงื่อนไข:
(className) -> className.contains("$Connection")
หากเราไม่ใช้ ExceptingClassLoader
ที่นี่ DynamicClassLoader
จะโหลดคลาส ConnectionPool
เนื่องจากคลาสนั้นอยู่ในโฟลเดอร์ " target/classes
” อีกวิธีหนึ่งในการป้องกันไม่ให้คลาส ConnectionPool
ถูกเลือกโดย DynamicClassLoader
ของเราคือคอมไพล์คลาส ConnectionPool
ไปยังโฟลเดอร์อื่น อาจอยู่ในโมดูลอื่น และจะถูกคอมไพล์แยกกัน
กฎการเลือกพื้นที่
ตอนนี้งานการโหลดคลาส Java เริ่มสับสนจริงๆ เราจะกำหนดได้อย่างไรว่าคลาสใดควรอยู่ในพื้นที่คงอยู่ และคลาสใดในพื้นที่ที่สามารถโหลดซ้ำได้ นี่คือกฎ:
- คลาสในพื้นที่รีโหลดได้อาจอ้างอิงคลาสในพื้นที่คงอยู่ แต่คลาสในพื้นที่คง ไม่สามารถ อ้างอิงคลาสในพื้นที่รีโหลดได้ ในตัวอย่างก่อนหน้านี้ คลาส
Context
ที่โหลดซ้ำได้อ้างอิงคลาสConnectionPool
ที่คงอยู่ แต่ConnectionPool
ไม่มีการอ้างอิงถึงContext
- คลาสสามารถมีอยู่ในช่องว่างใดก็ได้ หากไม่อ้างอิงคลาสใดๆ ในพื้นที่อื่น ตัวอย่างเช่น คลาสยูทิลิตี้ที่มีเมธอดสแตติกทั้งหมด เช่น
StringUtils
สามารถโหลดได้ครั้งเดียวในพื้นที่คงอยู่ และโหลดแยกต่างหากในพื้นที่ที่โหลดซ้ำได้
ดังนั้นคุณจะเห็นได้ว่ากฎเกณฑ์ไม่ได้จำกัดอยู่มาก ยกเว้นคลาสการข้ามที่มีอ็อบเจ็กต์ที่อ้างอิงข้ามสองช่องว่าง คลาสอื่นทั้งหมดสามารถใช้ได้อย่างอิสระในพื้นที่คงอยู่หรือพื้นที่โหลดซ้ำได้ หรือทั้งสองอย่าง แน่นอน เฉพาะคลาสในพื้นที่ที่สามารถโหลดซ้ำได้เท่านั้นที่จะสนุกกับการรีโหลดด้วยรอบการโหลดซ้ำ
ดังนั้นปัญหาที่ท้าทายที่สุดในการโหลดคลาสจึงถูกจัดการ ในตัวอย่างต่อไป เราจะพยายามนำเทคนิคนี้ไปใช้กับเว็บแอปพลิเคชันทั่วไป และสนุกกับการรีโหลดคลาส Java เช่นเดียวกับภาษาสคริปต์อื่นๆ
ตัวอย่างที่ 5: สมุดโทรศัพท์เล่มเล็ก
นี่คือรหัสที่มา..
ตัวอย่างนี้จะคล้ายกับหน้าตาของเว็บแอปพลิเคชันทั่วไป เป็นแอปพลิเคชันหน้าเดียวที่มี AngularJS, SQLite, Maven และ Jetty Embedded Web Server
นี่คือพื้นที่ที่สามารถโหลดซ้ำได้ในโครงสร้างของเว็บเซิร์ฟเวอร์:
เว็บเซิร์ฟเวอร์จะไม่เก็บการอ้างอิงถึงเซิร์ฟเล็ตจริง ซึ่งต้องอยู่ในพื้นที่ที่สามารถโหลดซ้ำได้ เพื่อทำการโหลดซ้ำ สิ่งที่เก็บไว้คือ stub servlets ซึ่งทุกครั้งที่เรียกใช้บริการจะแก้ไข servlet จริงในบริบทจริงเพื่อให้ทำงาน
ตัวอย่างนี้ยังแนะนำอ็อบเจ็กต์ ReloadingWebContext
ใหม่ ซึ่งจัดเตรียมค่าทั้งหมดให้กับเว็บเซิร์ฟเวอร์เหมือนบริบทปกติ แต่มีการอ้างอิงภายในไปยังอ็อบเจ็กต์บริบทจริงที่สามารถโหลดซ้ำโดย DynamicClassLoader
นี่คือ ReloadingWebContext
ซึ่งจัดเตรียม stub servlets ให้กับเว็บเซิร์ฟเวอร์
ReloadingWebContext
จะเป็น wrapper ของบริบทจริง และ:
- จะรีโหลดบริบทจริงเมื่อมีการเรียก HTTP GET เป็น “/”
- จะจัดเตรียม stub servlets ให้กับเว็บเซิร์ฟเวอร์
- จะตั้งค่าและเรียกใช้เมธอดทุกครั้งที่มีการเริ่มต้นหรือทำลายบริบทจริง
- สามารถกำหนดค่าให้โหลดบริบทซ้ำได้หรือไม่ และตัวโหลดคลาสใดที่ใช้สำหรับการโหลดซ้ำ สิ่งนี้จะช่วยเมื่อเรียกใช้แอปพลิเคชันในเวอร์ชันที่ใช้งานจริง
เนื่องจากเป็นสิ่งสำคัญมากที่จะเข้าใจว่าเราแยกพื้นที่คงเหลือและพื้นที่ที่โหลดซ้ำได้ ต่อไปนี้คือสองคลาสที่ข้ามระหว่างสองช่องว่าง:
คลาส qj.util.funct.F0
สำหรับวัตถุ public F0<Connection> connF
ใน Context
- ฟังก์ชั่นวัตถุจะส่งคืนการเชื่อมต่อทุกครั้งที่มีการเรียกใช้ฟังก์ชัน คลาสนี้อยู่ในแพ็คเกจ qj.util ซึ่งไม่รวมอยู่ใน
DynamicClassLoader
คลาส java.sql.Connection
สำหรับวัตถุ public F0<Connection> connF
ใน Context
- วัตถุการเชื่อมต่อ SQL ปกติ คลาสนี้ไม่ได้อยู่ในเส้นทางคลาสของ
DynamicClassLoader
ดังนั้นจึงไม่ถูกเลือก
สรุป
ในบทช่วยสอนคลาส Java นี้ เราได้เห็นวิธีการรีโหลดคลาสเดียว รีโหลดคลาสเดียวอย่างต่อเนื่อง รีโหลดพื้นที่ทั้งหมดของคลาสหลายคลาส และรีโหลดหลายคลาสแยกจากคลาสที่ต้องคงอยู่ ด้วยเครื่องมือเหล่านี้ ปัจจัยหลักในการโหลดคลาสซ้ำที่เชื่อถือได้คือการออกแบบที่สะอาดหมดจด จากนั้น คุณสามารถจัดการคลาสของคุณและ JVM ทั้งหมดได้อย่างอิสระ
การใช้การรีโหลดคลาส Java นั้นไม่ใช่สิ่งที่ง่ายที่สุดในโลก แต่ถ้าคุณลองดู และเมื่อถึงจุดหนึ่งพบว่าชั้นเรียนของคุณเต็มไปหมด แสดงว่าคุณเกือบจะอยู่ที่นั่นแล้ว จะเหลืออะไรให้ทำอีกเล็กน้อยก่อนที่คุณจะสามารถบรรลุการออกแบบที่ยอดเยี่ยมโดยสิ้นเชิงสำหรับระบบของคุณ
ขอให้เพื่อน ๆ โชคดีและสนุกกับพลังพิเศษที่คุณค้นพบ!