نصيحة @Around ونقطة الانضمام المتقدمة ProceedingJoinPoint
نصيحة @Around ونقطة الانضمام المتقدمة ProceedingJoinPoint
@Around هي أقوى أنواع النصائح في Spring AOP. على خلاف @Before أو @After اللتين تراقبان تنفيذ الميثود من الخارج، تُغلّف @Around الاستدعاء بالكامل. يُنفَّذ كود النصيحة قبل الميثود المستهدفة، ويقرر ما إذا كان سيُنفّذها أصلًا، ثم يُنفَّذ مرة أخرى بعد عودتها — مع وصول كامل إلى قيمة الإرجاع وأي استثناء قد يُرمى. يُعبَّر عن هذه السيطرة من خلال كائن وحيد: ProceedingJoinPoint.
توقيع الميثود
يجب أن يُعلن ميثود @Around عن ProceedingJoinPoint كمعامله الأول، وأن يُرجع Object (قيمة إرجاع الميثود المُنصَّح عليها، ربما بعد تعديلها).
pjp.proceed() وإرجاع نتيجتها. إن نسيت أحدهما، فإن الميثود الأصلية لن تُنفَّذ أبدًا (أو ستُهدر قيمة إرجاعها بصمت)، وهو خطأ في الغالب. لن يُحذّرك المُترجم — هذه مسؤولية وقت التشغيل.
كيف تعمل proceed()
تُفوّض pjp.proceed() إلى النصيحة التالية في السلسلة (أو إلى الميثود المستهدفة الحقيقية إن لم تكن ثمة نصائح أخرى). تُعيد نشر أي استثناء يُرمى من الميثود المستهدفة، ولهذا يجب أن يُعلن توقيع الميثود عن throws Throwable. يمكنك التقاط استثناءات محددة ومعالجتها أو إعادة رميها أو ابتلاعها كليًا — لكن الابتلاع خاطئ تقريبًا في كل الحالات خارج أنماط إعادة المحاولة المحددة جدًا.
فحص نقطة الانضمام
تكشف ProceedingJoinPoint عن سياق ثري حول الاستدعاء المُعترَض:
pjp.getSignature()— اسم الميثود والنوع المُعلِن ونوع الإرجاع.pjp.getArgs()— قيم المعاملات الفعلية وقت الاستدعاء.pjp.getTarget()— الـ bean المستهدف (الكائن الذي يُستدعى ميثوده).pjp.getThis()— الـ proxy نفسه (نادرًا ما تحتاجه).
تعديل المعاملات قبل المتابعة
يمكنك استبدال مصفوفة المعاملات قبل استدعاء proceed(Object[]). تقبل النسخة المُحمَّلة مصفوفة معاملات جديدة يمررها Spring إلى الميثود المستهدفة بدلًا من الأصلية. هذا مفيد لتعقيم المدخلات أو تطبيعها أو حقن البيانات الوصفية للتدقيق.
Object[] args = pjp.getArgs().clone()) حتى لا يتأثر النسخة الأصلية في حالة فحصها من قِبَل نصائح أخرى.
تعديل قيمة الإرجاع
ما تُرجعه pjp.proceed() هو مجرد Object. يمكنك فحصه أو استبداله أو تغليفه قبل إرجاعه للمُستدعي. حالة الاستخدام الشائعة هي التخزين المؤقت: تفحص ذاكرة التخزين المؤقت قبل استدعاء proceed()، ثم تُخزّن النتيجة بعده.
لاحظ أنه عند وجود القيمة في التخزين المؤقت، لا يُستدعى proceed() أبدًا. هذا مقصود وصحيح تمامًا — لكن وثّقه بوضوح، لأنه غير مرئي لمن يقرأ الميثود المستهدفة فقط.
التعامل مع الاستثناءات داخل @Around
يمكنك التقاط الاستثناءات التي ترميها الميثود المستهدفة وتقرير ما تفعله:
مثال كامل: جانب التوقيت الاحترافي
إليك جانب توقيت جاهز للإنتاج يستخدم SLF4J، ويتجاهل الميثودات السريعة دون عتبة معينة، ويُسجّل تحذيرًا للميثودات البطيئة:
يضمن حقل finally تسجيل التوقيت سواء أعادت الميثود قيمة بشكل طبيعي أم رمت استثناءً. استخدام System.nanoTime() بدلًا من System.currentTimeMillis() يتجنب قفزات ساعة الجدار الناجمة عن تعديلات NTP.
المقايضات: متى تستخدم @Around
- استخدم
@Aroundحين تحتاج لقياس الوقت المنقضي، أو التخطي الشرطي للميثود، أو تعديل المعاملات أو قيمة الإرجاع، أو تطبيق منطق إعادة المحاولة أو الاحتياط. - يُفضَّل
@Before/@Afterللاهتمامات المتقاطعة الأبسط كتسجيل الدخول والخروج، لأنها لا تستطيع عن طريق الخطأ ابتلاع الاستثناءات أو نسيان استدعاء الميثود المستهدفة. - الأداء: تُضيف
@Aroundاستدعاء ميثود إضافيًا واحدًا لكل نقطة انضمام مطابقة. عادةً ما تكون التكلفة بضعة ميكروثوانٍ — لا يُذكر إلا في الحلقات الداخلية المكثفة. ضيّق نقطة الاقتطاع لتجنب مطابقة مسارات الكود الحرجة دون ضرورة.
الخلاصة
تمنحك @Around غلافًا كاملًا حول الميثود المُعترَضة. تُسلّم ProceedingJoinPoint.proceed() التحكم للميثود المستهدفة؛ ونسختها المُحمَّلة proceed(Object[]) تتيح استبدال المعاملات أولًا؛ وقيمة الإرجاع Object قابلة للاستبدال قبل وصولها للمُستدعي. عند الاستخدام الصحيح — مع finally للتنظيف، ونموذج ذهني واضح لما يحدث حين لا يُستدعى proceed() — تُشكّل @Around أساس اهتمامات التسجيل والتوقيت والتخزين المؤقت وإعادة المحاولة والأمان المتقاطعة في تطبيقات Spring الإنتاجية.