ใน โครงการพัฒนา Java เวิร์กโฟลว์ทั่วไปเกี่ยวข้องกับการรีสตาร์ทเซิร์ฟเวอร์พร้อมกับการเปลี่ยนคลาสทุกครั้งและไม่มีใครบ่นเกี่ยวกับเรื่องนี้ นั่นคือข้อเท็จจริงเกี่ยวกับการพัฒนา Java เราทำงานแบบนั้นมาตั้งแต่วันแรกกับ Java แต่การรีโหลดคลาส Java นั้นยากที่จะบรรลุหรือไม่? และปัญหานั้นทั้งท้าทายและน่าตื่นเต้นในการแก้ไขหรือไม่ นักพัฒนา Java ที่มีทักษะ เหรอ? ในบทช่วยสอนคลาส Java นี้ฉันจะพยายามแก้ไขปัญหาช่วยให้คุณได้รับประโยชน์ทั้งหมดจากการโหลดคลาสแบบ on-the-fly และเพิ่มประสิทธิภาพการทำงานของคุณอย่างมหาศาล
มักไม่ค่อยมีการพูดถึงการรีโหลดคลาส Java และมีเอกสารประกอบการสำรวจกระบวนการนี้น้อยมาก ฉันมาที่นี่เพื่อเปลี่ยนแปลงสิ่งนั้น บทช่วยสอนคลาส Java นี้จะให้คำอธิบายทีละขั้นตอนเกี่ยวกับกระบวนการนี้และช่วยให้คุณเชี่ยวชาญเทคนิคที่น่าทึ่งนี้ โปรดทราบว่าการนำการโหลดคลาส Java มาใช้นั้นต้องใช้ความระมัดระวังเป็นอย่างมาก แต่การเรียนรู้วิธีการทำจะทำให้คุณอยู่ในลีกใหญ่ทั้งในฐานะนักพัฒนา Java และในฐานะสถาปนิกซอฟต์แวร์ นอกจากนี้ยังจะไม่เจ็บที่จะเข้าใจ วิธีหลีกเลี่ยงข้อผิดพลาด Java 10 ข้อที่พบบ่อยที่สุด .
ซอร์สโค้ดทั้งหมดสำหรับบทช่วยสอนนี้อัปโหลดบน GitHub ที่นี่ .
ในการรันโค้ดในขณะที่คุณทำตามบทช่วยสอนนี้คุณจะต้อง Maven , ไป และอย่างใดอย่างหนึ่ง คราส หรือ IntelliJ IDEA .
mvn eclipse:eclipse
เพื่อสร้างไฟล์โครงการของ Eclipsetarget/classes
pom
ของโปรเจ็กต์ ไฟล์.Alt+B E
run_example*.bat
ตั้งค่าคอมไพเลอร์อัตโนมัติของ IntelliJ เป็น true จากนั้นทุกครั้งที่คุณเปลี่ยนไฟล์ java ใด ๆ IntelliJ จะคอมไพล์โดยอัตโนมัติตัวอย่างแรกจะทำให้คุณเข้าใจทั่วไปเกี่ยวกับตัวโหลดคลาส 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: [email protected] [email protected] And different age values: 11 10
ดังที่คุณเห็นที่นี่แม้ว่า User
คลาสมีชื่อเดียวกันจริงๆแล้วเป็นคลาสสองคลาสที่แตกต่างกันและสามารถจัดการและจัดการได้อย่างอิสระ ค่าอายุแม้ว่าจะประกาศเป็นแบบคงที่ แต่ก็มีอยู่ในสองเวอร์ชันโดยแนบแยกกันกับแต่ละคลาสและสามารถเปลี่ยนแปลงได้อย่างอิสระเช่นกัน
วิธีออกแบบเว็บไซต์ shopify
ในโปรแกรม Java ปกติ ClassLoader
เป็นพอร์ทัลที่นำคลาสเข้าสู่ JVM เมื่อคลาสหนึ่งต้องการให้โหลดคลาสอื่นมันเป็นงานของ ClassLoader
ที่จะโหลด
อย่างไรก็ตามในตัวอย่างคลาส Java นี้กำหนดเอง ClassLoader
ชื่อ DynamicClassLoader
ใช้เพื่อโหลด User
เวอร์ชันที่สอง ชั้นเรียน. ถ้าแทนที่จะเป็น DynamicClassLoader
เราต้องใช้ตัวโหลดคลาสเริ่มต้นอีกครั้ง (ด้วยคำสั่ง StaticInt.class.getClassLoader()
) แล้วเหมือนกัน User
จะใช้คลาสเนื่องจากคลาสที่โหลดทั้งหมดจะถูกแคช
DynamicClassLoader
สามารถมีหลาย classloaders ในโปรแกรม Java ปกติ อันที่โหลดคลาสหลักของคุณ ClassLoader
เป็นคลาสเริ่มต้นและจากโค้ดของคุณคุณสามารถสร้างและใช้ตัวโหลดคลาสได้มากเท่าที่คุณต้องการ นี่คือกุญแจสำคัญในการโหลดคลาสใน Java DynamicClassLoader
อาจเป็นส่วนที่สำคัญที่สุดของบทช่วยสอนทั้งหมดนี้ดังนั้นเราต้องเข้าใจว่าการโหลดคลาสแบบไดนามิกทำงานอย่างไรก่อนที่เราจะบรรลุเป้าหมาย
ซึ่งแตกต่างจากพฤติกรรมเริ่มต้นของ ClassLoader
DynamicClassLoader
ของเรา สืบทอดกลยุทธ์เชิงรุกมากขึ้น classloader ปกติจะให้พาเรนต์ ClassLoader
ลำดับความสำคัญและโหลดเฉพาะคลาสที่พาเรนต์ไม่สามารถโหลดได้ ซึ่งเหมาะสำหรับสถานการณ์ปกติ แต่ไม่ใช่ในกรณีของเรา แทน DynamicClassLoader
จะพยายามตรวจสอบพา ธ คลาสทั้งหมดและแก้ไขคลาสเป้าหมายก่อนที่จะให้สิทธิ์กับพาเรนต์
ในตัวอย่างของเราด้านบน DynamicClassLoader
ถูกสร้างขึ้นด้วยพา ธ คลาสเดียวเท่านั้น: 'target/classes'
(ในไดเรกทอรีปัจจุบันของเรา) ดังนั้นจึงสามารถโหลดคลาสทั้งหมดที่อยู่ในตำแหน่งนั้นได้ สำหรับคลาสทั้งหมดที่ไม่ได้อยู่ในนั้นจะต้องอ้างถึงตัวโหลดคลาสหลัก ตัวอย่างเช่นเราต้องโหลด 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
สามารถรวบรวมขยะร่วมกับคลาสและอ็อบเจ็กต์ที่โหลดทั้งหมดด้วยความสามารถในการโหลดและใช้คลาสเดียวกันสองเวอร์ชันตอนนี้เรากำลังคิดที่จะทิ้งเวอร์ชันเก่าและโหลดเวอร์ชันใหม่มาแทนที่ ในตัวอย่างต่อไปเราจะทำอย่างนั้น…อย่างต่อเนื่อง
ตัวอย่าง 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
s และเก่า User
ชั้นเรียนจะถูกยกเลิกการเชื่อมโยงและอยู่ภายใต้การรวบรวมขยะ
หากคุณคุ้นเคยกับ JVM HotSpot เป็นที่น่าสังเกตว่าโครงสร้างคลาสสามารถเปลี่ยนแปลงและโหลดซ้ำได้เช่นกัน: the playFootball
วิธีการคือการลบและ playBasketball
เพิ่มวิธีการแล้ว สิ่งนี้แตกต่างจาก HotSpot ซึ่งอนุญาตให้เปลี่ยนเนื้อหาเมธอดเท่านั้นหรือไม่สามารถโหลดคลาสซ้ำได้
ตอนนี้เราสามารถโหลดคลาสซ้ำได้แล้วก็ถึงเวลาลองโหลดคลาสหลาย ๆ คลาสพร้อมกัน ลองดูในตัวอย่างถัดไป
ผลลัพธ์ของตัวอย่างนี้จะเหมือนกันกับตัวอย่างที่ 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
วิธีการที่นี่จะมีวัตถุบริบทค้างไว้และ นั่นคือลิงค์เดียว กับทุกสิ่งที่ต้องทิ้ง ถ้าเราทำลายลิงก์นั้นอ็อบเจ็กต์บริบทและคลาสบริบทและเซอร์วิสอ็อบเจ็กต์ ... ทั้งหมดจะถูกเก็บรวบรวมขยะ
คำอธิบายเล็กน้อยเกี่ยวกับสาเหตุที่ชั้นเรียนปกติมีอยู่อย่างต่อเนื่องและไม่ได้รับการเก็บรวบรวมขยะ:
จากตัวอย่างนี้เราจะเห็นว่าการโหลดคลาสของแอปพลิเคชันทั้งหมดซ้ำนั้นค่อนข้างง่าย เป้าหมายเป็นเพียงเพื่อให้การเชื่อมต่อที่บางและหยดได้จากเธรดสดไปยังตัวโหลดคลาสไดนามิกที่ใช้งานอยู่ แต่ถ้าเราต้องการให้วัตถุบางอย่าง (และคลาสของพวกเขา) เป็นอย่างไร ไม่ ต้องโหลดซ้ำและนำมาใช้ซ้ำระหว่างรอบการโหลดซ้ำ? ลองดูตัวอย่างถัดไป
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
วัตถุและ UserService
วัตถุที่อ้างถึง 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 ซ้ำเช่นเดียวกับภาษาสคริปต์
ตัวอย่างนี้จะคล้ายกับลักษณะของเว็บแอปพลิเคชันทั่วไป เป็นแอปพลิเคชันหน้าเดียวที่มี AngularJS, SQLite, Maven และ Jetty Embedded Web Server .
นี่คือพื้นที่ที่โหลดซ้ำได้ในโครงสร้างของเว็บเซิร์ฟเวอร์:
เว็บเซิร์ฟเวอร์จะไม่เก็บการอ้างอิงถึง servlets จริงซึ่งจะต้องอยู่ในพื้นที่ที่โหลดซ้ำได้เพื่อที่จะโหลดซ้ำ สิ่งที่เก็บไว้คือ Stub servlets ซึ่งทุกครั้งที่เรียกใช้เมธอดเซอร์วิสจะแก้ไข servlet จริงในบริบทจริงเพื่อรัน
ตัวอย่างนี้ยังแนะนำอ็อบเจ็กต์ใหม่ ReloadingWebContext
ซึ่งจัดเตรียมค่าทั้งหมดให้กับเว็บเซิร์ฟเวอร์เช่นบริบทปกติ แต่ภายในจะเก็บการอ้างอิงไปยังอ็อบเจ็กต์บริบทจริงที่สามารถโหลดซ้ำได้โดย DynamicClassLoader
มันคือ ReloadingWebContext
ซึ่งให้บริการที่สมบูรณ์แก่เว็บเซิร์ฟเวอร์
ReloadingWebContext
จะเป็นตัวห่อของบริบทจริงและ:
เนื่องจากเป็นสิ่งสำคัญมากที่จะต้องทำความเข้าใจว่าเราแยกพื้นที่ที่มีอยู่และพื้นที่ที่โหลดซ้ำได้อย่างไรต่อไปนี้คือคลาสสองคลาสที่ข้ามระหว่างช่องว่างทั้งสอง:
ชั้น qj.util.funct.F0
สำหรับวัตถุ public F0 connF
ใน Context
DynamicClassLoader
ชั้น java.sql.Connection
สำหรับวัตถุ public F0 connF
ใน Context
DynamicClassLoader
ของเราดังนั้นคลาสนี้จะไม่ถูกเลือกในบทช่วยสอนคลาส Java นี้เราได้เห็นวิธีการโหลดคลาสเดียวโหลดคลาสเดียวซ้ำอย่างต่อเนื่องโหลดพื้นที่ทั้งหมดของคลาสหลายคลาสและรีโหลดคลาสหลายคลาสแยกจากคลาสที่ต้องคงอยู่ ด้วยเครื่องมือเหล่านี้ปัจจัยสำคัญในการรีโหลดคลาสที่เชื่อถือได้คือการออกแบบที่ดูสะอาดตา จากนั้นคุณสามารถจัดการชั้นเรียนของคุณและ JVM ทั้งหมดได้อย่างอิสระ
การนำคลาส Java มาใช้ซ้ำไม่ใช่สิ่งที่ง่ายที่สุดในโลก แต่ถ้าคุณลองยิงและเมื่อถึงจุดหนึ่งพบว่าชั้นเรียนของคุณถูกโหลดทันทีแสดงว่าคุณเกือบจะอยู่ที่นั่นแล้ว จะเหลือน้อยมากที่จะทำก่อนที่คุณจะได้รับการออกแบบที่ยอดเยี่ยมโดยสิ้นเชิงสำหรับระบบของคุณ
ขอให้โชคดีเพื่อน ๆ และสนุกไปกับพลังพิเศษที่เพิ่งค้นพบ