การเปิดตัวแอปพลิเคชัน Spring Boot โดยทางโปรแกรม

เผยแพร่แล้ว: 2022-03-11

บทความนี้จะสาธิตวิธีการเริ่มแอปพลิเคชัน Spring Boot จากโปรแกรม Java อื่น โดยทั่วไปแล้ว แอปพลิเคชัน Spring Boot จะถูกสร้างไว้ในไฟล์เก็บถาวร JAR ที่เรียกใช้ได้ไฟล์เดียว มีการพึ่งพาทั้งหมดภายใน บรรจุเป็น JAR ที่ซ้อนกัน

ในทำนองเดียวกัน โปรเจ็กต์ Spring Boot มักจะสร้างเป็นไฟล์ JAR ที่เรียกใช้งานได้โดยปลั๊กอิน maven ที่ให้มาซึ่งทำงานสกปรกทั้งหมด ผลลัพธ์ที่ได้คือไฟล์ JAR เดียวที่สะดวกและง่ายต่อการแชร์กับผู้อื่น ปรับใช้บนเซิร์ฟเวอร์ และอื่นๆ

การเริ่มต้นแอปพลิเคชัน Spring Boot นั้นง่ายพอๆ กับการพิมพ์ java -jar mySpringProg.jar และแอปพลิเคชันจะพิมพ์ข้อความข้อมูลที่มีรูปแบบสวยงามบนคอนโซล

แต่ถ้าผู้พัฒนา Spring Boot ต้องการเรียกใช้แอปพลิเคชันจากโปรแกรม Java อื่นโดยปราศจากการแทรกแซงของมนุษย์

JAR ที่ซ้อนกันทำงานอย่างไร

ในการแพ็คโปรแกรม Java ที่มีการขึ้นต่อกันทั้งหมดลงในไฟล์ JAR ที่รันได้ไฟล์เดียว จำเป็นต้องมีการจัดเตรียมการพึ่งพาที่เป็นไฟล์ JAR และเก็บไว้ในไฟล์ JAR ที่รันได้สุดท้าย

“การแรเงา” เป็นทางเลือกหนึ่ง การพึ่งพาการแรเงาเป็นกระบวนการของการรวมและการเปลี่ยนชื่อการพึ่งพา การย้ายคลาส และการเขียนไบต์โค้ดและทรัพยากรที่ได้รับผลกระทบใหม่เพื่อสร้างสำเนาที่รวมเข้ากับโค้ดของแอปพลิเคชัน (โครงการ)

การแรเงาอนุญาตให้ผู้ใช้แตกคลาสและทรัพยากรทั้งหมดจากการพึ่งพา และแพ็คกลับเข้าไปในไฟล์ JAR ที่รันได้ วิธีนี้อาจใช้ได้ในสถานการณ์ทั่วไป อย่างไรก็ตาม หากการขึ้นต่อกันสองรายการมีไฟล์ทรัพยากรหรือคลาสเดียวกันที่มีชื่อและเส้นทางเหมือนกันทุกประการ สิ่งเหล่านี้จะทับซ้อนกันและโปรแกรมอาจไม่ทำงาน

Spring Boot ใช้แนวทางที่แตกต่างออกไปและแพ็ค JAR ที่พึ่งพาภายใน JAR ที่รันได้ เป็น JAR ที่ซ้อนกัน

 example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar

ไฟล์เก็บถาวร JAR ถูกจัดระเบียบเป็นไฟล์ JAR ที่รันด้วย Java มาตรฐาน คลาส Spring Boot loader อยู่ที่เส้นทาง org/springframework/boot/loader ขณะที่คลาสของผู้ใช้และการพึ่งพาจะอยู่ที่ BOOT-INF/classes และ BOOT-INF/lib

หมายเหตุ: หากคุณเพิ่งเริ่มใช้ Spring คุณอาจต้องการดูบทความ 10 ข้อผิดพลาดที่พบบ่อยที่สุดของ Spring Framework ของเรา

ไฟล์ Spring Boot JAR ทั่วไปประกอบด้วยรายการสามประเภท:

  • ชั้นเรียนโครงการ
  • ไลบรารี JAR ที่ซ้อนกัน
  • คลาสตัวโหลด Spring Boot

Spring Boot Classloader จะตั้งค่าไลบรารี JAR ใน classpath ก่อน จากนั้นจึงตามด้วยคลาสของโปรเจ็กต์ ซึ่งทำให้มีความแตกต่างเล็กน้อยระหว่างการรันแอปพลิเคชัน Spring Boot จาก IDE (Eclipse, IntelliJ) และจากคอนโซล

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการแทนที่คลาสและตัวโหลดคลาส คุณสามารถอ่านบทความนี้

การเปิดตัวแอพพลิเคชั่น Spring Boot

การเปิดใช้แอปพลิเคชัน Spring Boot ด้วยตนเองจากบรรทัดคำสั่งหรือเชลล์ทำได้ง่ายโดยพิมพ์ข้อความต่อไปนี้:

 java -jar example.jar

อย่างไรก็ตาม การเริ่มแอปพลิเคชัน Spring Boot โดยทางโปรแกรมจากโปรแกรม Java อื่นต้องใช้ความพยายามมากขึ้น จำเป็นต้องโหลด org/springframework/boot/loader/*.class ใช้การสะท้อน Java เล็กน้อยเพื่อสร้างอินสแตนซ์ JarFileArchive , JarLauncher และเรียกใช้เมธอด launch(String[])

เราจะดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการดำเนินการในส่วนต่อไปนี้

กำลังโหลดคลาส Spring Boot Loader

ดังที่เราได้กล่าวไปแล้ว ไฟล์ Spring Boot JAR ก็เหมือนกับไฟล์เก็บถาวร JAR ใดๆ เป็นไปได้ที่จะโหลดรายการ org/springframework/boot/loader/*.class สร้างอ็อบเจ็กต์ Class และใช้เพื่อเปิดแอปพลิเคชัน Spring Boot ในภายหลัง

 import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; . . . public static void loadJar(final String pathToJar) throws IOException . . . { // Class name to Class object mapping. final Map<String, Class<?>> classMap = new HashMap<>(); final JarFile jarFile = new JarFile(pathToJar); final Enumeration<JarEntry> jarEntryEnum = jarFile.entries(); final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);

ที่นี่เราจะเห็น classMap จะเก็บอ็อบเจ็กต์คลาสที่แมปกับชื่อแพ็คเกจที่เกี่ยวข้อง เช่น ค่าสตริง org.springframework.boot.loader.JarLauncher จะถูกแมปกับอ็อบเจ็กต์ JarLauncher.class

 while (jarEntryEnum.hasMoreElements()) { final JarEntry jarEntry = jarEntryEnum.nextElement(); if (jarEntry.getName().startsWith("org/springframework/boot") && jarEntry.getName().endsWith(".class") == true) { int endIndex = jarEntryName.lastIndexOf(".class"); className = jarEntryName.substring(0, endIndex).replace('/', '.'); try { final Class<?> loadedClass = urlClassLoader.loadClass(className); result.put(loadedClass.getName(), loadedClass); } catch (final ClassNotFoundException ex) { } } } jarFile.close();

ผลลัพธ์สุดท้ายของ while loop คือแผนที่ที่บรรจุอ็อบเจ็กต์คลาส Spring Boot loader

เปิดใช้งานการเปิดตัวจริงโดยอัตโนมัติ

เมื่อโหลดเสร็จแล้ว เราสามารถดำเนินการเปิดใช้อัตโนมัติให้เสร็จสิ้นและใช้เพื่อเริ่มแอปของเราได้จริง

การสะท้อนของ Java ช่วยให้สามารถสร้างวัตถุจากคลาสที่โหลดได้ ซึ่งค่อนข้างมีประโยชน์ในบริบทของบทช่วยสอนของเรา

ขั้นตอนแรกคือการสร้างวัตถุ JarFileArchive

 // Create JarFileArchive(File) object, needed for JarLauncher. final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive"); final Constructor<?> jarFileArchiveConstructor = jarFileArchiveClass.getConstructor(File.class); final Object jarFileArchive = jarFileArchiveConstructor.newInstance(new File(pathToJar));

ตัวสร้างของวัตถุ JarFileArchive รับวัตถุ File(String) เป็นอาร์กิวเมนต์ จึงต้องระบุ

ขั้นตอนต่อไปคือการสร้างออบเจ็กต์ JarLauncher ซึ่งต้องมีการ Archive ในตัวสร้าง

 final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive"); // Create JarLauncher object using JarLauncher(Archive) constructor. final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass); jarLauncherConstructor.setAccessible(true); final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);

เพื่อหลีกเลี่ยงความสับสน โปรดทราบว่าจริงๆ แล้ว Archive เป็นอินเทอร์เฟซ ในขณะที่ JarFileArchive เป็นหนึ่งในการใช้งาน

ขั้นตอนสุดท้ายในกระบวนการคือการเรียกเมธอด launch(String[]) บนอ็อบเจ็กต์ jarLauncher ที่สร้างขึ้นใหม่ สิ่งนี้ค่อนข้างตรงไปตรงมาและต้องใช้โค้ดเพียงไม่กี่บรรทัด

 // Invoke JarLauncher#launch(String[]) method. final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher"); final Method launchMethod = launcherClass.getDeclaredMethod("launch", String[].class); launchMethod.setAccessible(true); launchMethod.invoke(jarLauncher, new Object[]{new String[0]});

ในที่สุด invoke(jarLauncer, new Object[]{new String[0]}) จะเริ่มต้นแอปพลิเคชัน Spring Boot โปรดทราบว่าเธรดหลักจะหยุดและรอที่นี่เพื่อให้แอปพลิเคชัน Spring Boot ยุติการทำงาน

คำเกี่ยวกับ Spring Boot Classloader

การตรวจสอบไฟล์ Spring Boot JAR ของเราจะเปิดเผยโครงสร้างต่อไปนี้:

 +--- mySpringApp1-0.0.1-SNAPSHOT.jar +--- META-INF +--- BOOT-INF | +--- classes # 1 - project classes | | | | | +--- com.example.mySpringApp1 | | \--- SpringBootLoaderApplication.class | | | +--- lib # 2 - nested jar libraries | +--- javax.annotation-api-1.3.1 | +--- spring-boot-2.0.0.M7.jar | \--- (...) | +--- org.springframework.boot.loader # 3 - Spring Boot loader classes +--- JarLauncher.class +--- LaunchedURLClassLoader.class \--- (...)

สังเกตรายการสามประเภท:

  • ชั้นเรียนโครงการ
  • ไลบรารี JAR ที่ซ้อนกัน
  • คลาสตัวโหลด Spring Boot

คลาสของโปรเจ็กต์ทั้งสอง ( BOOT-INF/classes ) และ JAR ที่ซ้อนกัน ( BOOT-INF/lib ) ได้รับการจัดการโดย LaunchedURLClassLoader โหลดคลาสเดียวกัน ตัวโหลดนี้อยู่ในรูทของแอปพลิเคชัน Spring Boot JAR

LaunchedURLClassLoader จะโหลดเนื้อหาคลาส ( BOOT-INF/classes ) หลังจากเนื้อหาไลบรารี ( BOOT-INF/lib ) ซึ่งแตกต่างจาก IDE ตัวอย่างเช่น Eclipse จะวางเนื้อหาคลาสใน classpath ก่อนแล้วจึงตามด้วยไลบรารี (การพึ่งพา)

LaunchedURLClassLoader ขยาย java.net.URLClassLoader ซึ่งสร้างด้วยชุด URL ที่จะใช้สำหรับการโหลดคลาส URL อาจชี้ไปที่ตำแหน่ง เช่น ไฟล์เก็บถาวร JAR หรือโฟลเดอร์คลาส เมื่อทำการโหลดคลาส ทรัพยากรทั้งหมดที่ระบุโดย URL จะถูกสำรวจตามลำดับที่มีการระบุ URL และทรัพยากรแรกที่มีคลาสที่ค้นหาจะถูกใช้

ห่อ

แอปพลิเคชัน Java แบบคลาสสิกต้องการการพึ่งพาทั้งหมดเพื่อระบุในอาร์กิวเมนต์ classpath ทำให้ขั้นตอนการเริ่มต้นค่อนข้างยุ่งยากและซับซ้อน

ในทางตรงกันข้าม แอปพลิเคชัน Spring Boot นั้นสะดวกและง่ายต่อการเริ่มต้นจากบรรทัดคำสั่ง พวกเขาจัดการการพึ่งพาทั้งหมด และผู้ใช้ไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียด

อย่างไรก็ตาม การเริ่มแอปพลิเคชัน Spring Boot จากโปรแกรม Java อื่นทำให้ขั้นตอนมีความซับซ้อนมากขึ้น เนื่องจากต้องโหลดคลาสตัวโหลดของ Spring Boot สร้างอ็อบเจ็กต์พิเศษ เช่น JarFileArchive และ JarLauncher จากนั้นใช้การสะท้อน Java เพื่อเรียก launch วิธีการเรียกใช้

สิ่งสำคัญ ที่สุด : Spring Boot สามารถจัดการงานเล็กๆ น้อยๆ ได้มากมาย ช่วยให้นักพัฒนามีเวลามากขึ้นและมีสมาธิกับงานที่มีประโยชน์มากขึ้น เช่น การสร้างคุณสมบัติใหม่ การทดสอบ และอื่นๆ