مستمعو الويب
مستمعو الويب
تعترض مرشّحات (Filters) الـ Servlet الطلبات الفردية. لكنّ كثيرًا من المخاوف المتقاطعة — تهيئة بدء التشغيل، وإحماء تجمّع الاتصالات، وعدّادات المقاييس، ومسارات التدقيق — لا علاقة لها بأي طلب بعينه. إنها تهتم بـ أحداث دورة الحياة: بدأ التطبيق، أُنشئت جلسة، أُزيل سمة من طلب. هذا بالضبط ما تُتيحه مستمعات الويب (Web Listeners).
تعرّف مواصفة Servlet (Jakarta EE 10، الحزمة jakarta.servlet) ثمانية واجهات مستمع منظَّمة في ثلاثة نطاقات: السياق (context) أي التطبيق بأكمله، والجلسة (session)، والطلب (request). تُنفّذ الواجهة وتُزيّن الفئة بـ @WebListener، فيستدعي الحاوي (container) أساليبك في اللحظات المناسبة — دون تمديد (wiring) ودون XML (ما لم تُفضّل ذلك).
مستمعات السياق — نطاق التطبيق
ServletContextListener هي أهم مستمع ستكتبه. أسلوباه يحيطان بعمر تطبيق الويب بالكامل:
contextInitialized(ServletContextEvent)— يُستدعى مرة واحدة بعد أن ينتهي الحاوي من تحميل التطبيق، لكن قبل معالجة أي طلب. استخدمه للأعمال التي تُنجز مرة واحدة عند البدء.contextDestroyed(ServletContextEvent)— يُستدعى مرة واحدة حين يُغلق الحاوي التطبيق (إلغاء النشر، إعادة تشغيل الخادم). استخدمه لتحرير الموارد بصورة رشيقة.
يمكن لأي Servlet الآن استرداد التجمّع بـ (DataSource) request.getServletContext().getAttribute("dataSource") — دون حاجة لـ static singletons أو إطار حقن تبعيات.
ServletContext يربط دورة حياته بالتطبيق لا بـ JVM. عند إعادة نشر التطبيق دون إعادة تشغيل الخادم، يعمل contextDestroyed أولًا فيُغلق التجمّع نظيفًا. أما الحقل الـ static فسيظل حيًّا بعد انتهاء السياق ويُسرّب اتصالات للـ class loader القديم.
ServletContextAttributeListener هي الواجهة المرافقة: تُطلق أحداث attributeAdded وattributeReplaced وattributeRemoved كلما نادى أحد setAttribute / removeAttribute على السياق. مفيدة للتدقيق أو الاستجابة لتغييرات الإعداد في وقت التشغيل.
مستمعات الجلسة — نطاق الجلسة
تتتبع HttpSessionListener ولادة الجلسات وانتهاءها عبر جميع المستخدمين:
sessionCreated(HttpSessionEvent)— تُطلق حين تنشئrequest.getSession(true)جلسة جديدة تمامًا.sessionDestroyed(HttpSessionEvent)— تُطلق حين تُبطل الجلسة (session.invalidate()) أو تنتهي صلاحيتها.
sessionDestroyed حين تنتهي الجلسة بالتوقيت. بحلول الوقت الذي يستدعي فيه الحاوي sessionDestroyed لجلسة انتهت صلاحيتها، ربما يكون قد أزال سماتها بالفعل. اقرأ السمات التي تحتاجها في sessionCreated أو خزّنها في مكان آخر إذا أردت استخدامها لاحقًا. حين يُستدعى session.invalidate() صراحةً تظل السمات موجودة في لحظة الاستدعاء.
تُطلق HttpSessionAttributeListener أحداثًا عند تغيير السمات داخل أي جلسة — مفيدة لتتبّع وضع كائن المستخدم في الجلسة أو إزالته منها (مسار تدقيق تسجيل الدخول والخروج).
HttpSessionBindingListener مختلفة: تُنفَّذ على كائن القيمة نفسه (مثل فئة User الخاصة بك)، لا على فئة مستمع منفصلة. حين يُعيَّن كائن ينفّذ هذه الواجهة كسمة جلسة يستدعي الحاوي valueBound عليه؛ وحين يُزال يستدعي valueUnbound. هذا يتيح للكائن إدارة تنظيف نفسه دون مستمع عالمي يفحص كل جلسة.
تدعم HttpSessionActivationListener عملية المرور (passivation)، أي تسلسل الجلسات إلى القرص أو عقدة أخرى. sessionWillPassivate يُستدعى قبل التسلسل؛ وsessionDidActivate بعد إلغاء التسلسل. نفّذ هذه الواجهة على أي سمة جلسة تحمل موردًا غير قابل للتسلسل (مثل اتصال مباشر) كي تُحرّره وتُعيد اقتناءه بصواب.
مستمعات الطلب — نطاق الطلب
ServletRequestListener هو المقابل على مستوى الطلب لـ ServletContextListener. أسلوباه يُحيطان بكل طلب HTTP فردي يدخل التطبيق:
على خلاف المرشح (Filter)، لا يجلس هذا المستمع في سلسلة المعالجة، لذا لا يستطيع حجب الطلب أو تعديله. دوره مراقبة بحتة — القياس عن بُعد (telemetry)، وإعداد سياق MDC لكل طلب من أجل التسجيل، ومخاوف متقاطعة مماثلة لا ينبغي أن تؤثر في الاستجابة.
اختيار المستمع المناسب
- أعمال البدء والإيقاف (تهيئة التجمّع، إحماء الكاش، بدء/إيقاف الوظائف المجدولة) ←
ServletContextListener - إحصاء المستخدمين النشطين، وتطبيق حدود تسجيل الدخول المتزامن ←
HttpSessionListener - مسار تدقيق تسجيل الدخول والخروج ←
HttpSessionAttributeListenerأوHttpSessionBindingListener - تمرير الجلسات في البيئات المُجمَّعة (clustered) ←
HttpSessionActivationListener - قياس الطلبات، وإعداد MDC ←
ServletRequestListener
ترتيب المستمعات وسلامة الخيوط
يُستدعى المستمعون من النوع نفسه وفق الترتيب الذي يكتشفهم فيه الحاوي (ترتيب الاكتشاف المبني على التعليقات التوضيحية غير محدد؛ استخدم مدخلات <listener> في web.xml إن كان الترتيب مهمًا). يُستدعى contextInitialized وcontextDestroyed على خيط واحد؛ أما مستمعات الجلسة والطلب فقد تُستدعى بصورة متزامنة عبر خيوط متعددة. أي حالة قابلة للتغيير مشتركة بين استدعاءات المستمع يجب أن تكون آمنة للخيوط — استخدم AtomicInteger أو ConcurrentHashMap أو كتل synchronized وفقًا لذلك.
@WebListener — سجّل المستمعين كـ Spring beans باستخدام أساليب @Bean التي تُعيد تسجيلات EventListener، أو نفّذ الواجهة المعنية وزيّنها بـ @Component. يُغلّف Spring أحداث حاوي Servlet في نظام أحداث التطبيق الخاص به، لذا يمكنك الاستماع لـ ApplicationReadyEvent أو ContextClosedEvent بدلًا من واجهات Servlet المباشرة. لكن حين تعمل مع حاويات Jakarta EE خالصة (Tomcat أو Jetty أو WildFly بدون Spring)، تكون واجهات المستمع الأصلية هي ما تلجأ إليه.
الخلاصة
تمنحك مستمعات الويب خطّافات دورة الحياة (lifecycle hooks) على ثلاثة نطاقات. استخدم ServletContextListener لتهيئة موارد التطبيق وتفكيكها عند البدء والإيقاف — تجمّعات الاتصالات والذواكر المؤقتة والخيوط الخلفية. استخدم HttpSessionListener لإحصاء الجلسات وتطبيق الحدود أو الاحتفاظ بسجل تدقيق لعمليات الدخول والخروج. استخدم ServletRequestListener للرصد (observability): القياس الزمني وسياق التسجيل لكل طلب والمقاييس التي لا ينبغي أن تُغيّر الاستجابة. المستمع الصحيح في النطاق الصحيح أنظف بكثير من تشتيت المنطق نفسه عبر كل Servlet أو Filter.