التبعيات والمستودعات في Maven
التبعيات والمستودعات في Maven
إعلان تبعية في ملف pom.xml أمر بسيط — كتلة XML واحدة وسيتولّى Maven تحليل كل شيء وتنزيله وربطه. التعقيد الحقيقي يكمن في الفهم: ما الذي يجلبه Maven بالنيابة عنك؟ وكيف يختار الإصدار حين تتعارض مكتبتان؟ ولماذا قد يُضخّم النطاق الخاطئ حجم حزمة الإنتاج بصمت أو يُعطل مستهلكًا من المكتبة؟ تحوّل هذه الدرس تلك المعلومات الخلفية إلى إدارة تبعيات احترافية ومقصودة.
كيف يحلّ Maven التبعيات
عند إعلان تبعية يبحث Maven في سلسلة مستودعات بالترتيب حتى يعثر على القطعة المطلوبة:
- المستودع المحلي —
~/.m2/repositoryعلى جهازك. بمجرد تنزيل القطعة تُخزَّن هنا للأبد (حتى تحذفها أو تشغّلmvn dependency:purge-local-repository). - Maven Central — المستودع العام الافتراضي. لا يحتاج إلى أي إعداد.
- مستودعات بعيدة إضافية — تُعلَن في
pom.xmlأوsettings.xml(مثل JitPack أو Spring Milestones أو Nexus/Artifactory الخاص بشركتك).
الإحداثيات — groupId:artifactId:version — تُعرّف قطعةً بشكل فريد. يُحوّلها Maven إلى مسار في أي مستودع: com/google/guava/guava/32.1.3-jre/guava-32.1.3-jre.jar.
نطاقات التبعية (Dependency Scopes)
يتحكّم العنصر <scope> في ثلاثة أشياء في آنٍ واحد: مسارات الفئات التي تظهر عليها التبعية (تجميع / اختبار / تشغيل)، وهل تُضمَّن في الأداة الناتجة، وهل تنتقل إلى مستهلكي مكتبتك كتبعية انتقالية. النطاقات الستة هي:
- compile (الافتراضي) — متاحة في وقت التجميع والتشغيل والاختبار. تُضمَّن في JAR/WAR المُعبَّأ وتنتقل إلى المستهلكين كتبعية انتقالية. استخدمها للواجهات البرمجية التي تكشفها أو الأنواع الظاهرة في توقيعاتك العامة.
- provided — متاحة في وقت التجميع والاختبار لكن غير مُضمَّنة ولا تنتقل. توفّرها بيئة التشغيل (الحاوية أو JDK). المثال الكلاسيكي:
jakarta.servlet-apiفي WAR يُنشر على Tomcat. - runtime — غير مطلوبة للتجميع لكن ضرورية في وقت التشغيل. تُضمَّن لكن تُستبعد من مسار تجميع الكود حتى لا تعتمد الكود بطريقة خاطئة على تفاصيل التنفيذ. مشغّلات JDBC تنتمي هنا.
- test — متاحة فقط في مسارَي تجميع الاختبار وتشغيله. لا تُضمَّن ولا تنتقل أبدًا. JUnit وMockito وAssertJ وTestcontainers.
- system — مثل
providedلكنك تحدد مسارًا صريحًا عبر<systemPath>. تجنّبه: غير محمول وأُهمل فعليًا. - import — صالح فقط في
<dependencyManagement>معtype=pom. يدمج Bill of Materials (BOM) في إدارة التبعيات. يُغطّى في الدرس التاسع.
provided scope (مع توثيق المتطلب) يختار المستهلكون ربط SLF4J الخاص بهم. النطاق جزء من عقد مكتبتك.
التبعيات الانتقالية
حين تعتمد على spring-webmvc فإنها بدورها تعتمد على spring-core وspring-beans وغيرها. يقرأ Maven POM كل تبعية ويحلّ الرسم البياني الكامل بشكل تعاودي. هذا هو حل التبعيات الانتقالية — تعلن قطعة واحدة ويسحب Maven شجرة قطع بصمت.
ينتقل النطاق عبر الرسم البياني وفق هذه القواعد: التبعية الانتقالية ذات نطاق compile تبقى compile؛ وذات نطاق runtime تبقى runtime؛ أما ذات نطاق test أو provided فلا تنتقل إطلاقًا. لهذا السبب لا تتسرّب JUnit أبدًا إلى مسارات الفئات في الإنتاج.
لفحص الرسم البياني المحلول كاملًا:
حل تعارض الإصدارات: قاعدة "الأقرب يفوز"
يحتوي الرسم البياني دائمًا تقريبًا على نفس القطعة بإصدارات متعددة (تبعية الماسة Diamond Dependency). يطبّق Maven قاعدة الأقرب يفوز: الإصدار المُعلَن الأقرب إلى جذر مشروعك في رسم التبعيات يفوز. إن تساوت المسافتان يفوز الأول المُعلَن في POM.
قاعدة الأقرب يفوز حتمية لكنها قد تفاجئك. إن احتاجت المكتبة A إلى Jackson 2.17 واحتاجت المكتبة B إلى Jackson 2.15، وكانت B مُعلَنة أولًا على العمق 2 بينما تسحب A إلى Jackson على العمق 3، ستحصل على 2.15 — وهو قد يكون غير متوافق مع استدعاءات API للمكتبة A.
mvn dependency:tree وmvn dependency:analyze بانتظام. حين يكون إصدار انتقالي خاطئًا تجاوزه صراحةً في <dependencyManagement>: أعلن القطعة بالإصدار الذي تحتاجه فيثبّت Maven كل إشارة في الشجرة على ذلك الإصدار بغض النظر عن العمق.
استبعاد التبعيات الانتقالية غير المرغوبة
أحيانًا تجرّ مكتبة تبعية انتقالية لا تريدها — تنفيذ تسجيل منافس، أو إصدار قديم ضعيف لا يمكنك تجاوزه عالميًا، أو قطعة تتعارض مع بيئة تشغيلك. استخدم <exclusions> لقطعها من الرسم البياني عند موضع الإعلان:
إضافة مستودع بعيد
بعض القطع غير موجودة على Maven Central (إصدارات Spring التجريبية، بناءات JitPack من GitHub، مستودع شركتك الخاص). أعلنها في POM أو في ~/.m2/settings.xml:
<mirrorOf> في settings.xml لتوجيه كل حركة المرور عبر بروكسي شركتك (Nexus / Artifactory) الذي يمكنه فحص القطع قبل أن تصل إلى أجهزة المطوّرين.
أوامر التشخيص العملية
mvn dependency:tree— الرسم البياني المحلول الكامل مع النطاقات ومصادر الإصدارات.mvn dependency:analyze— يُشير إلى التبعيات المستخدمة غير المُعلَنة والمُعلَنة غير المستخدمة.mvn dependency:resolve -Dclassifier=sources— تنزيل ملفات JAR المصادر للتنقل بها في بيئة التطوير.mvn versions:display-dependency-updates— عرض التبعيات المُعلَنة التي يتوفر لها إصدارات أحدث.
الخلاصة
إدارة التبعيات في Maven تنقسم إلى ثلاث طبقات: النطاقات تُحدّد متى وأين تكون التبعية مرئية؛ والحل الانتقالي يؤتمت رسم التبعيات لكنه يُدخل تعارضات في الإصدارات؛ والمستودعات تحدّد أين يبحث Maven عن القطع. إتقان dependency:tree وفهم قاعدة الأقرب يفوز واستخدام <dependencyManagement> لتثبيت الإصدارات — هذه هي المهارات التي تُميّز مطوّرًا يلصق الإحداثيات فحسب عن مطوّر يمتلك بناءه بالفعل.