إطلاق تطبيق Spring Boot برمجيًا

نشرت: 2022-03-11

ستوضح هذه المقالة كيفية بدء تطبيق Spring Boot من برنامج Java آخر. عادةً ما يتم إنشاء تطبيق Spring Boot في أرشيف JAR واحد قابل للتنفيذ. يحتوي على جميع التبعيات بالداخل ، ويتم تجميعها في شكل JARs المتداخلة.

وبالمثل ، عادةً ما يتم إنشاء مشروع Spring Boot كملف JAR قابل للتنفيذ بواسطة مكون إضافي مافن يقوم بجميع الأعمال القذرة. والنتيجة هي ملف JAR واحد مناسب يسهل مشاركته مع الآخرين ونشره على الخادم وما إلى ذلك.

يعد بدء تطبيق Spring Boot سهلاً مثل كتابة java -jar mySpringProg.jar ، وسيقوم التطبيق بطباعة بعض رسائل المعلومات المنسقة بشكل جيد على وحدة التحكم.

ولكن ماذا لو أراد مطور Spring Boot تشغيل تطبيق من برنامج Java آخر دون تدخل بشري؟

كيف تعمل JARs المتداخلة

لحزم برنامج Java مع جميع التبعيات في ملف JAR واحد قابل للتشغيل ، يجب توفير التبعيات التي هي أيضًا ملفات JAR وتخزينها بطريقة ما داخل ملف JAR النهائي القابل للتشغيل.

"التظليل" هو أحد الخيارات. تبعيات التظليل هي عملية تضمين التبعيات وإعادة تسميتها ، وإعادة تحديد موقع الفئات ، وإعادة كتابة الرمز الثانوي والموارد المتأثرة من أجل إنشاء نسخة مجمعة جنبًا إلى جنب مع الكود الخاص بالتطبيق (المشروع).

يسمح التظليل للمستخدمين بفك حزم جميع الفئات والموارد من التبعيات وحزمها مرة أخرى في ملف JAR قابل للتشغيل. قد يعمل هذا مع سيناريوهات بسيطة ، ومع ذلك ، إذا احتوت تبعيتان على نفس ملف المورد أو الفئة بنفس الاسم والمسار بالضبط ، فسوف تتداخلان وقد لا يعمل البرنامج.

يتخذ Spring Boot نهجًا مختلفًا ويحزم JARs التبعية داخل JAR القابل للتشغيل ، مثل JARs المتداخلة.

 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. توجد فئات محمل الإقلاع الربيعي في مسار org/springframework/boot/loader ، بينما توجد فئات المستخدم والتبعيات في BOOT-INF/classes و BOOT-INF/lib .

ملاحظة: إذا كنت جديدًا في فصل الربيع ، فقد ترغب أيضًا في إلقاء نظرة على مقالتنا العشرة الأكثر شيوعًا حول أخطاء إطار عمل الربيع .

يحتوي ملف Spring Boot JAR النموذجي على ثلاثة أنواع من الإدخالات:

  • فئات المشروع
  • مكتبات JAR المتداخلة
  • فئات محمل الإقلاع الربيعي

سيقوم Spring Boot Classloader أولاً بتعيين مكتبات JAR في مسار الفصل ثم فئات المشروع ، مما يحدث فرقًا بسيطًا بين تشغيل تطبيق 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 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 ستحمل كائنات Class المعينة لأسماء الحزم الخاصة بها ، على سبيل المثال ، String value 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 هي خريطة مليئة بكائنات فئة 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 المتداخلة
  • فئات محمل الإقلاع الربيعي

تتم معالجة كلا فئتي المشروع ( BOOT-INF/classes ) و JARs المتداخلة ( BOOT-INF/lib ) بواسطة محمل الفئة نفسه LaunchedURLClassLoader . يوجد هذا المُحمل في جذر تطبيق Spring Boot JAR.

سيقوم LaunchedURLClassLoader بتحميل محتوى الفصل ( BOOT-INF/classes ) بعد محتوى المكتبة ( BOOT-INF/lib ) ، والذي يختلف عن IDE. على سبيل المثال ، سيضع Eclipse محتوى الفئة أولاً في مسار الفصل ثم المكتبات (التبعيات).

يمتد LaunchedURLClassLoader java.net.URLClassLoader ، والذي يتم إنشاؤه باستخدام مجموعة من عناوين URL التي سيتم استخدامها لتحميل الفصل. قد يشير عنوان URL إلى موقع مثل أرشيف JAR أو مجلد الفئات. عند إجراء تحميل فئة ، سيتم اجتياز جميع الموارد المحددة بواسطة عناوين URL بالترتيب الذي تم توفير عناوين URL فيه ، وسيتم استخدام المورد الأول الذي يحتوي على الفئة التي تم البحث عنها.

تغليف

يتطلب تطبيق Java الكلاسيكي تعداد جميع التبعيات في وسيطة classpath ، مما يجعل إجراء بدء التشغيل مرهقًا ومعقدًا إلى حد ما.

في المقابل ، فإن تطبيقات Spring Boot سهلة الاستخدام وسهلة التشغيل من سطر الأوامر. إنهم يديرون جميع التبعيات ، ولا يحتاج المستخدم النهائي إلى القلق بشأن التفاصيل.

ومع ذلك ، فإن بدء تطبيق Spring Boot من برنامج Java آخر يجعل الإجراء أكثر تعقيدًا ، حيث يتطلب تحميل فئات محمل Spring Boot ، وإنشاء كائنات متخصصة مثل JarFileArchive و JarLauncher ، ثم استخدام انعكاس Java لاستدعاء طريقة launch .

الخلاصة : يمكن أن يتولى Spring Boot الكثير من المهام الوضيعة تحت الغطاء ، مما يتيح للمطورين توفير الوقت والتركيز على المزيد من الأعمال المفيدة مثل إنشاء ميزات جديدة والاختبار وما إلى ذلك.