JSP وJSTL وطبقة العرض

الكائنات الضمنية في JSP

18 دقيقة الدرس 3 من 13

الكائنات الضمنية في JSP

تُترجَم كل صفحة JSP بواسطة الحاوية إلى servlet. داخل الـ servlet المُولَّد، تُعلن الحاوية مسبقًا عن تسعة متغيرات معروفة قبل أن يُنفَّذ أي من كود الـ scriptlet الخاص بك. هذه هي الكائنات الضمنية — فهي موجودة بالفعل في النطاق، ومُهيَّأة بالكامل، ولا تحتاج إلى استيراد أو إنشاء. تستخدمها تمامًا كما تستخدم أي متغير محلي. يركّز هذا الدرس على الخمسة التي ستتعامل معها في كل تطبيق ويب تقريبًا: request وresponse وsession وapplication وout.

لماذا توجد الكائنات الضمنية: تُعرض هذه الكائنات عبر Servlet API كمعاملات للأساليب (doGet(HttpServletRequest, HttpServletResponse)) أو عبر getServletContext(). ما يفعله مُترجم JSP ببساطة هو سحبها وربطها بأسماء قصيرة ومتوقعة حتى يظل كود العرض موجزًا. في المنهجية MVC تقوم وحدات التحكم بالعمل الثقيل وتمرّر النتائج إلى صفحات JSP — لكن فهم الكائنات الأساسية لا يزال ضروريًا لأغراض التنقيح والأمان والوصول الاضطراري من طبقة العرض.

1. request — HttpServletRequest

الكائن request هو نسخة من jakarta.servlet.http.HttpServletRequest. يُغلّف كل ما أرسله المتصفح إلى الخادم في هذه الدورة HTTP: الترويسات والمعاملات وملفات الكوكيز وعنوان URI للطلب والمستخدم المُصادَق عليه وأي سمات حدّدها servlet أو فلتر سابق.

العمليات الشائعة للقراءة داخل صفحة JSP (أو تعبير EL يُقيَّم منها):

<%-- الحصول على معامل من سلسلة الاستعلام أو النموذج --%> <% String keyword = request.getParameter("q"); %> <%-- الحصول على سمة مُمرَّرة من servlet --%> <% List<Product> products = (List<Product>) request.getAttribute("products"); %> <%-- فحص أسلوب HTTP وعنوان URI --%> <% String method = request.getMethod(); // "GET" أو "POST" String uri = request.getRequestURI(); %> <%-- قراءة ترويسة الطلب --%> <% String accept = request.getHeader("Accept-Language"); %>
تحقق دائمًا من صحة مخرجات getParameter() وعقّمها. يجب ألا تُكتب السلاسل التي يوفرها المستخدم مباشرةً في HTML — فذلك يُمثّل ثغرة cross-site scripting (XSS). استخدم <c:out> في JSTL أو الهروب التلقائي في Expression Language، وهو ما ستتعلمه في الدروس التالية.

2. response — HttpServletResponse

الكائن response هو نسخة من jakarta.servlet.http.HttpServletResponse. يُمثّل استجابة HTTP الصادرة التي سترسلها الحاوية إلى المتصفح. في تصاميم MVC النقية نادرًا ما تتعامل معه مباشرةً داخل JSP، لكنه ضروري لعمليات إعادة التوجيه والتعامل مع ملفات الكوكيز وضبط الترويسات المخصصة.

<%-- إعادة توجيه المتصفح إلى عنوان URL آخر (يُرسل الاستجابة فورًا) --%> <% response.sendRedirect(request.getContextPath() + "/login"); %> <%-- إضافة ملف كوكيز إلى الاستجابة --%> <% Cookie pref = new Cookie("theme", "dark"); pref.setMaxAge(60 * 60 * 24 * 30); // 30 يومًا pref.setHttpOnly(true); pref.setSecure(true); // HTTPS فقط response.addCookie(pref); %> <%-- ضبط ترويسة استجابة مخصصة --%> <% response.setHeader("X-Frame-Options", "DENY"); %>
استدع sendRedirect() قبل كتابة أي مخرجات. حين تبدأ الحاوية في إرسال بايتات HTML تكون ترويسات الاستجابة قد أُرسلت فعلًا؛ ومحاولة إعادة التوجيه بعد ذلك ترمي IllegalStateException. تنتمي عمليات إعادة التوجيه إلى وحدات التحكم أو الفلاتر لا إلى منطق مخرجات JSP — ضع هذا في ذهنك عند هيكلة MVC.

3. session — HttpSession

الكائن session هو نسخة من jakarta.servlet.http.HttpSession. HTTP بروتوكول عديم الحالة؛ وكائن الجلسة هو إجابة الحاوية على هذا القيد. تربط الحاوية الجلسة بالعميل باستخدام ملف كوكيز (JSESSIONID افتراضيًا) وتحتفظ بخريطة من السمات على جانب الخادم تبقى عبر طلبات متعددة من المتصفح نفسه.

<%-- تخزين المستخدم المسجَّل دخوله في الجلسة بعد المصادقة --%> <% session.setAttribute("currentUser", authenticatedUser); %> <%-- قراءتها في أي JSP لاحقة --%> <% User user = (User) session.getAttribute("currentUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login"); return; // إيقاف عرض هذه الصفحة } %> <%-- إبطال الجلسة عند تسجيل الخروج (يحذف كل السمات ويُنشئ معرف جلسة جديد) --%> <% session.invalidate(); %> <%-- التحقق من مدة خمول الجلسة (بالثواني) --%> <% int idle = (int)((System.currentTimeMillis() - session.getLastAccessedTime()) / 1000); %>
لا تخزّن رسوم بيانية كبيرة من الكائنات في الجلسة أبدًا. تعيش بيانات الجلسة في ذاكرة الخادم (أو مخزن الجلسات). حشو قوائم الكيانات الكاملة في الجلسة يهدر الذاكرة ويُعقّد التجميع (clustering). خزّن فقط معرّفات صغيرة — معرّف المستخدم والدور واللغة — وأعد الاستعلام من قاعدة البيانات لكل شيء آخر مع كل طلب.

4. application — ServletContext

الكائن application هو نسخة من jakarta.servlet.ServletContext. يُمثّل تطبيق الويب بالكامل لا طلبًا أو جلسةً واحدة. يوجد ServletContext واحد فقط لكل تطبيق مُنشر، تشترك فيه كل خيوط التنفيذ وكل المستخدمين. يُستخدم للإعدادات على مستوى التطبيق وذاكرة التخزين المؤقت المشتركة والتواصل بين الـ servlets.

<%-- قراءة <context-param> من web.xml --%> <% String environment = application.getInitParameter("app.environment"); %> <%-- تخزين واسترجاع قيمة على مستوى التطبيق (مثل عداد الزيارات) --%> <% synchronized (application) { Integer hits = (Integer) application.getAttribute("hitCount"); if (hits == null) hits = 0; application.setAttribute("hitCount", ++hits); } %> <%-- الحصول على المسار الفعلي لمورد في نظام الملفات --%> <% String configPath = application.getRealPath("/WEB-INF/config.properties"); %> <%-- تسجيل رسالة عبر مسجّل الحاوية --%> <% application.log("Application started at: " + new java.util.Date()); %>
سُلَّم النطاق — request < session < application. تعيش السمات في أضيق نطاق يلبّي الحاجة. تختفي سمات الطلب بنهاية الاستجابة. تختفي سمات الجلسة عند انتهاء صلاحيتها أو إبطالها. تعيش سمات التطبيق طوال عمر ملف WAR المُنشَر. الإفراط في النطاق (وضع بيانات الطلب في نطاق التطبيق) يتسبب في أخطاء يصعب جدًا إعادة إنتاجها في بيئات التزامن.

5. out — JspWriter

الكائن out هو نسخة من jakarta.servlet.jsp.JspWriter، وهو كاتب حروف مُخزَّن مؤقتًا يُغذّي مباشرةً جسم استجابة HTTP. في كل مرة تصادف فيها الحاوية HTML حرفيًا في صفحة JSP الخاصة بك تكتبه تلقائيًا عبر out. تستخدم out صراحةً في الـ scriptlets حين تحتاج إلى كتابة نص محسوب.

<%-- الاستخدام الصريح لـ out (مطابق لـ <%= expr %>) --%> <% String greeting = "Hello, " + user.getFirstName() + "!"; out.println("<p class='greeting'>" + greeting + "</p>"); %> <%-- مسح المخزن المؤقت يدويًا (نادرًا ما تحتاج إليه؛ فضّل المسح التلقائي الافتراضي) --%> <% out.flush(); %> <%-- التحقق من المساحة المتبقية في المخزن المؤقت --%> <% int remaining = out.getRemaining(); %>
فضّل <%= expr %> أو EL ${expr} على الاستدعاء الصريح لـ out.print(). كلاهما يُحوَّل إلى نفس عملية الكتابة الأساسية، لكن صيغة التعبير أسهل في القراءة وأقل عرضة للأخطاء وتتكامل بصورة طبيعية مع المعالجة التلقائية للقيم الخالية والهروب من HTML في EL (عند استخدامه مع JSTL). احتفظ باستدعاءات out الخام للحالة النادرة التي تحتاج فيها إلى كتابة ترميز مشروط داخل منطق Java معقّد — واسأل نفسك إن كان هذا المنطق ينتمي إلى طبقة العرض أصلًا.

الكائنات الضمنية الأربعة الأخرى

للاكتمال، تشمل المجموعة الكاملة من تسعة كائنات ضمنية أيضًا:

  • pageContextjakarta.servlet.jsp.PageContext: واجهة برمجية موحَّدة للوصول إلى كل النطاقات وإعادة التوجيه/التضمين؛ يُستخدم كثيرًا داخل تطبيقات الوسوم المخصصة.
  • page — نسخة الـ servlet المُولَّدة نفسها (this)؛ نادرًا ما تُستخدم مباشرةً.
  • configjakarta.servlet.ServletConfig: معاملات التهيئة المضبوطة لـ servlet الـ JSP نفسه.
  • exception — متاح فقط في صفحات الخطأ (صفحات JSP ذات isErrorPage="true")؛ يحمل الـ Throwable المُرمى.

الوصول إلى الكائنات الضمنية عبر Expression Language

حين تستخدم EL (${...}) بدلًا من الـ scriptlets، تكون الكائنات الأساسية نفسها متاحةً عبر كائنات EL الضمنية المُسمّاة. تُقرأ سمات request عبر ${requestScope.productList}، وسمات الجلسة عبر ${sessionScope.currentUser}، وسمات التطبيق عبر ${applicationScope.hitCount}. تُقرأ المعاملات عبر ${param.q}. يُغطَّى EL بعمق في الدرس الرابع، لكن إدراك الصلة بكائنات servlet الضمنية مبكرًا يمنع الالتباس.

الخلاصة

الكائنات الضمنية الخمسة الرئيسية تُغلّف كائنات Servlet API التي يعرفها كودك بالفعل. يحمل request البيانات الواردة؛ ويتحكم response في التدفق الصادر؛ ويُبقي session الحالة عبر الطلبات؛ ويحمل application البيانات المشتركة على مستوى التطبيق؛ ويكتب out في جسم الاستجابة. جميع الكائنات التسعة مُهيَّأة مسبقًا بواسطة الحاوية — ولا تُنشئها أنت أبدًا. في الدرس القادم سترى كيف يتيح لك Expression Language القراءة من جميع النطاقات الأربعة دون كتابة سطر واحد من Java.