تعبيرات نقاط القطع
تعبيرات نقاط القطع
معرفة كيفية كتابة أسلوب الاستشارة (advice) ليست سوى نصف القصة. النصف الآخر — والجانب الذي يُميّز الجوانب القابلة للصيانة عن تلك الهشّة — هو إخبار Spring بدقة بنقاط الدمج التي يجب أن تعترضها تلك الاستشارة. هذا هو دور تعبير نقطة القطع. أتقن لغة التعابير وستحصل على سيطرة جراحية على اهتماماتك المشتركة دون لمس سطر واحد من الكود التجاري.
ما هو تعبير نقطة القطع في حقيقته
تعبير نقطة القطع هو سلسلة نصية مكتوبة بلغة تعابير Spring AOP (مستعارة من AspectJ). يُقيَّم عند بدء تشغيل السياق: يفحص Spring كل حبّة (bean)، ويحلّل التعبير مقابل كل أسلوب، ويبني وكيلًا (proxy) لأي حبّة تتطابق أساليبها مع التعبير. وقت التشغيل، يعترض الوكيل الاستدعاءات المتطابقة فقط — أما كل شيء آخر فيمرّ مباشرة دون أي تكلفة.
يمكنك إرفاق التعبير إما مباشرةً على تعليق الاستشارة أو على أسلوب @Pointcut مستقل، ثم الإشارة إلى ذلك الأسلوب بالاسم:
@Pointcut مسمّاة. إذا لصقت نفس سلسلة التعبير في خمسة تعليقات استشارة، فإن تغيير حرف واحد في اسم حزمة يكسر الكل بصمت عند بدء التشغيل — لكنك تُصلح نقطة قطع واحدة فقط بدلًا من تعديل كل تكرار.
مُحدِّد execution()
يُعدّ execution() العمود الفقري لـ Spring AOP. يُطابق نقاط دمج تنفيذ الأسلوب استنادًا إلى نمط توقيعه. القواعد الكاملة هي:
الأقواس المربعة تُشير إلى أجزاء اختيارية. عمليًا ستستخدم ثلاثة أو أربعة أجزاء. إليك مجموعة تدريجية من الأمثلة الحقيقية، كل منها أكثر تحديدًا من السابق:
رموز الأنماط التي تحتاج معرفتها:
*— يُطابق أي جزء مفرد (مستوى حزمة واحد، اسم نوع واحد، اسم أسلوب واحد، نوع إرجاع واحد). لا يتجاوز النقاط...— في موضع الحزمة يُطابق أي عدد من الحزم الفرعية؛ في موضع المعاملات يُطابق أي عدد وأي نوع من المعاملات.()— بدون وسيطات تمامًا.(*)— وسيط واحد بالضبط من أي نوع.(..)— صفر وسيطات أو أكثر من أي نوع (الأكثر شيوعًا).
execution(* *(..)) يُطابق كل أسلوب في كل حبّة مُخوَّل، بما فيها حبّات البنية التحتية التي لم تقصد اعتراضها. ابدأ بالحزمة الكاملة المسمّاة ووسّع فقط عند الحاجة.
مُحدِّد within()
يقصر within() التطابق على نقاط الدمج داخل نوع (فئة أو مجموعة فئات). لا يهتم بتوقيعات الأساليب — فقط بمكان وجود الأسلوب. هذا يجعله الخيار الصحيح عندما تريد اعتراض كل النشاط داخل طبقة أو فئة محددة، بغض النظر عن أسماء الأساليب أو أنواع الإرجاع:
الشكل الأخير — التطابق على تعليق على مستوى النوع — قوي بشكل خاص. كل فئة تُعلّقها بـ @Service تُغطّى تلقائيًا دون سرد أسماء الحزم. إذا أضفت لاحقًا خدمة في حزمة جديدة، تلتقطها نقطة القطع بدون أي تغيير.
execution() مقابل within() — اختيار الأداة المناسبة
غالبًا ما يتداخل هذان المُحدِّدان، لكنهما ليسا قابلَين للتبادل:
- استخدم
execution()عندما يكون التوقيع هو المهم — نوع إرجاع محدد، اصطلاح تسمية كـfind*أوsave*، أو قائمة معاملات معيّنة. - استخدم
within()عندما يكون الموقع هو المهم — "اعترض كل شيء في طبقة الخدمة" أو "اعترض كل شيء في هذه الفئة المحددة". - استخدم كليهما معًا عندما تحتاج القيدَين: نمط اسم أسلوب داخل حزمة محددة.
&& (AND) و|| (OR) و! (NOT) لتركيب التعبيرات. في ضبط XML يجب عليك كتابة and وor وnot لأن XML يُفلت محرف العطف — لكن في التعليقات تعمل السلسلة && مباشرةً.
نقاط القطع المستندة إلى التعليقات مع @annotation()
نمط شائع في الواقع العملي هو تعريف تعليق مخصص ثم كتابة نقطة قطع تُطابق أي أسلوب يحمل ذلك التعليق. يمنح هذا مؤلفي المكتبات ومصمّمي الأطر ربطًا نظيفًا قائمًا على الاختيار الصريح:
هذا النمط أكثر قابلية للصيانة بكثير من التعبيرات المستندة إلى الحزم عندما تحتاج تحكمًا دقيقًا، لأن التعليق يرافق الأسلوب حتى لو انتقل إلى حزم مختلفة.
الأخطاء الشائعة وكيفية تجنّبها
new مباشرةً لن يُطلَق أبدًا، لأنه لا يوجد وكيل. أدر دائمًا حبّاتك عبر حاوية IoC الخاصة بـ Spring.
- الاستدعاء الذاتي لا يُعترَض. إذا استدعى
OrderService.methodA()داخليًاthis.methodB()، يتجاوز وكيل AOP الأسلوبَmethodB. الحل هو حقن الحبّة في نفسها (يتعامل Spring 6 مع التبعية الدائرية) أو إعادة الهيكلة إلى حبّة مستقلة. - الأساليب الخاصة (private) ومحدودة الرؤية للحزمة لا تُطابَق قط بواسطة Spring AOP (يعتمد على وكلاء JDK الديناميكية أو CGLIB، وكلاهما يُجاوز الأساليب العامة والمحمية فقط). إذا بدا أن نقطة قطعك لا تُطلَق، تحقق من رؤية الأسلوب أولًا.
- الرمز
..في نمط الحزمة يُطابق صفر مقاطع أو أكثر، لذا يُطابقcom.example..الحزمةَcom.exampleنفسها وأي حزم متداخلة. هذا مصدر شائع لتطابقات أوسع مما قُصِد.
التحقق من صحة تعبيراتك
بدلًا من التخمين في مطابقة النمط، اكتب اختبار تكامل سريعًا وسجّل نقاط الدمج:
اربطه بـ Spring Profile حتى لا يصل إلى الإنتاج أبدًا. شغّل التطبيق، مارس ميزاتك، وافحص مخرجات الطرفية. إذا لم يظهر أسلوب توقعت تطابقه — تحقق من الرؤية وإدارة الحبّة والاستدعاء الذاتي.
الخلاصة
يمنحك execution() دقة على مستوى التوقيع: التطابق على نوع الإرجاع واسم الأسلوب وأنواع المعاملات. يمنحك within() دقة على مستوى الموقع: تطابق كل شيء داخل نوع أو حزمة. اجمعهما مع && و|| و!، واستخدم @annotation() للاعتراض القائم على الاشتراك الصريح. سمّ التعبيرات المشتركة بأساليب @Pointcut لإعادة الاستخدام، واجعل التعبيرات محددة بقدر ما تتطلبه حالة الاستخدام. في الدرس التالي ستطبّق هذه الأدوات على أكثر أنواع الاستشارات مرونة: @Around.