البناء والتعبئة باستخدام Maven
البناء والتعبئة باستخدام Maven
الفهم العميق لدورة حياة Maven ونظام الإضافات الخاص بها هو ما يُميّز المطوّرين الذين يستخدمون Maven فحسب عن أولئك الذين يتحكّمون فيه حقًا. في هذا الدرس ستتعلّم بدقّة ما يحدث حين تشغّل mvn package، وكيف تُنتج ملفات JAR قابلة للتنفيذ وملفات JAR "ضخمة" (uber-JAR)، وكيف تضبط الإضافات لسيناريوهات النشر الحقيقية.
دورة حياة بناء Maven بالتفصيل
يُعرّف Maven ثلاث دورات حياة مدمجة: default (من التجميع حتى النشر)، وclean (تحذف مخرجات البناء)، وsite (تُولّد التوثيق). يقع معظم العمل اليومي داخل دورة default. تنفَّذ مراحلها بترتيب صارم — استدعاء مرحلة متأخّرة يعني دائمًا تشغيل كل مرحلة قبلها:
validate— يتحقّق من صحة ملف POM وهيكل المشروع.compile— يُجمّعsrc/main/javaإلىtarget/classes.test-compile— يُجمّعsrc/test/javaإلىtarget/test-classes.test— يُنفّذ اختبارات الوحدة عبر Surefire؛ يفشل البناء إن فشلت الاختبارات.package— يُعبّئ الكلاسات المُجمَّعة في ملف JAR (أو WAR/EAR).verify— يُشغّل اختبارات التكامل وفحوصات الجودة الأخرى.install— ينسخ القطعة (artifact) إلى مستودعك المحلي~/.m2.deploy— يرفع القطعة إلى مستودع بعيد (Nexus أو Artifactory أو غيره).
compiler:compile). عند تشغيل مرحلة، يُنفّذ Maven كل هدف مرتبط بها. يمكنك أيضًا استدعاء هدف مباشرةً: mvn compiler:compile.
إنتاج ملف JAR قياسي
إضافة maven-jar-plugin مرتبطة بمرحلة package تلقائيًا ولا تحتاج إلى إعداد صريح للمشاريع البسيطة. تشغيل mvn package يُنتج target/my-app-1.0.jar يحتوي على الكلاسات المُجمَّعة والموارد. غير أن هذا الملف لا يحتوي على إدخال Main-Class في الملف التعريفي (manifest) وبالتالي لا يمكن تنفيذه بـ java -jar مباشرةً.
لجعل ملف JAR قابلًا للتنفيذ، اضبط الإضافة في ملف POM الخاص بك:
بتفعيل addClasspath، يكتب Maven إدخال Class-Path في الملف التعريفي يشير إلى مجلد lib/ بجانب ملف JAR. عليك حينئذٍ شحن التبعيات في ذلك المجلد. هذا الأسلوب خفيف الوزن لكنّه هشّ للتوزيع — يجب الحفاظ على المسارات النسبية كما هي.
إنتاج Uber-JAR قابل للتنفيذ باستخدام إضافة Shade
uber-JAR (المعروف أيضًا بـ fat JAR) يُجمع كودك وجميع ملفات JAR التبعية في أرشيف واحد مكتفٍ بذاته. إضافة maven-shade-plugin هي الأداة القياسية في Maven لهذا الغرض:
بعد mvn package، سيحتوي مجلد target/ على كلٍّ من my-app-1.0.jar (خفيف) وmy-app-1.0-exec.jar (shaded، قابل للتشغيل). شغّله بالأمر:
maven-shade-plugin حين تحتاج uber-JAR قابلًا للتشغيل. استخدم maven-assembly-plugin حين تحتاج أرشيف توزيع مُخصَّصًا (zip/tar مع سكربتات وملفات ضبط). لتطبيقات Spring Boot استخدم إضافة Spring Boot Maven المخصّصة — فهي تُنتج JAR متعدّد الطبقات مُحسَّنًا للتخزين المؤقّت في Docker.
التحكّم في إضافة المُجمِّع
إضافة maven-compiler-plugin مرتبطة بمرحلتي compile وtest-compile. لمشاريع Java 17+، اضبطها صراحةً — لا تعتمد على إصدار الهدف الافتراضي في Maven (Java 5 في تثبيتات Maven القديمة):
الخاصية release (Java 9+) مُفضَّلة على الزوج القديم source/target لأنها تضبط أيضًا bootstrap classpath، وتمنع الاستخدام العرضي لواجهات برمجية غير موجودة في تلك الإصدارة.
تخطّي الاختبارات وتشغيلها بشكل انتقائي
تُشغّل إضافة Surefire اختبارات الوحدة خلال مرحلة test. قد تحتاج أحيانًا لتخطّي الاختبارات أو تصفيتها:
-DskipTests في خطوط CI/CD أبدًا. الهدف من البنيات الآلية هو رصد الانحدارات. تخطّ الاختبارات محليًا فقط حين تتكرّر بسرعة على كود غير اختباري، وشغّل الحزمة الكاملة دائمًا قبل الرفع.
دورة حياة Clean
دورة clean لها مرحلة واحدة ذات معنى: clean، التي تحذف مجلد target/. ادمجها مع مراحل البناء لضمان بناء نظيف:
شغّل دائمًا clean في CI وقبل الإصدار. يمكن للكلاسات المُجمَّعة القديمة من تشغيل سابق أن تُنتج إخفاقات "يعمل على جهازي" المُضلِّلة.
ربط أهداف مخصَّصة بالمراحل
يمكن ربط أي هدف لأي إضافة بأي مرحلة في دورة الحياة. نمط شائع هو تشغيل تحليل Checkstyle أو SpotBugs خلال مرحلة verify:
الآن، mvn verify يُجمّع ويختبر ويُعبّئ ثم يُجري التحليل الثابت — كله بأمر واحد، دون الحاجة لتذكّر استدعاءات منفصلة.
قراءة مخرجات البناء
يطبع Maven المرحلة والهدف المُنفَّذَين على كل سطر. تعلّم قراءة نمط المخرجات:
سطر BUILD FAILURE مصحوب دائمًا بالإضافة والهدف المحدّد الذي فشل. ابدأ تشخيصك من هناك، لا من أسفل جدار من تتبّعات المكدّس.
الخلاصة
تتدفّق دورة حياة Maven الافتراضية من validate حتى deploy؛ استدعاء أي مرحلة يشغّل تلقائيًا جميع المراحل التي قبلها. تُنتج maven-jar-plugin ملف JAR خفيفًا؛ بينما تُنتج maven-shade-plugin uber-JAR مكتفيًا بذاته قابلًا للتنفيذ. اضبط إضافة المُجمِّع لتحديد إصدار Java صراحةً. ارتبط أهداف التحليل الثابت بمرحلة verify كي تعمل بوّابات الجودة تلقائيًا. أدرج دائمًا clean في بنيات CI للتخلّص من القطع القديمة.