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

لغة التعبير (EL)

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

لغة التعبير (EL)

قبل وجود لغة التعبير (EL)، كان مطوّرو JSP يطبعون خصائص الـ bean بكتابة scriptlets من قبيل <%= ((User) request.getAttribute("user")).getName() %>. كان هذا النهج يخلط تحويلات Java واستدعاءات الدوال مباشرةً داخل الصفحة، مما جعل القوالب هشّة وصعبة القراءة. قُدِّمت EL في JSTL 1.0 ثم أُدرجت كجزء من مواصفة Servlet/JSP (Jakarta EL، الإصدار 5.0 حاليًا). واليوم هي الطريقة الوحيدة التي ينبغي بها إخراج البيانات الديناميكية في صفحة JSP.

بنية ${...} — الأساسيات

كل تعبير EL محاط بـ ${ ... }. عند تصيير الصفحة يُقيّم الحاوي التعبير ويكتب النتيجة كسلسلة نصية. إذا كانت النتيجة null فتُخرج EL سلسلة فارغة بصمت — ولا ترمي NullPointerException.

<!-- تُخرج "Welcome, Alice!" إذا كان user.name هو "Alice" --> <p>Welcome, ${user.name}!</p> <!-- عمليات حسابية --> <p>الإجمالي مع الضريبة: ${order.subtotal * 1.08}</p> <!-- دمج السلاسل النصية (EL 3.0+) --> <p>${firstName += ' ' += lastName}</p>
التقييم المؤجّل — #{...}: قد تصادف أيضًا #{ ... } في Jakarta Faces (JSF). في JSP العادية، التزم بـ ${ ... }. يتصرف الشكلان بالطريقة نفسها في سياقات EL للقراءة فقط داخل JSP، لكن #{ } تحمل معنى خاصًا في أُطر عمل المكوّنات.

كيف تحلّ EL الأسماء — سلسلة النطاقات

حين تصادف EL اسمًا مجرّدًا مثل ${product} تبحث عن سمة اسمها product بهذا الترتيب: page → request → session → application. أول تطابق يفوز. يعكس هذا الكائنات الضمنية التي تُعيّنها بـ request.setAttribute() وsession.setAttribute() وغيرها. يمكنك التصريح الصريح باستخدام خرائط النطاق المدمجة:

${pageScope.product} <!-- نطاق الصفحة فقط --> ${requestScope.product} <!-- نطاق الطلب فقط --> ${sessionScope.cart} <!-- نطاق الجلسة فقط --> ${applicationScope.config} <!-- نطاق التطبيق فقط -->

فضّل الشكل الصريح دائمًا حين يمكن أن يظهر الاسم نفسه في نطاقات متعددة — فذلك يجعل الصفحة موثّقة ذاتيًا ويتفادى أخطاء التظليل الخفية.

قراءة خصائص الـ Bean

تستخدم EL عامل النقطة للتنقّل عبر خصائص JavaBean. ${user.name} تستدعي user.getName() — تتبع EL اصطلاح JavaBeans (احذف اسم الخاصية، حوّل أوله إلى حرف كبير، أضف "get"). الخصائص المنطقية تستخدم is: ${product.available} تستدعي product.isAvailable().

// Servlet — يضع bean في نطاق الطلب User user = userService.findById(id); request.setAttribute("user", user); // JSP — يقرأ الـ bean بـ EL <p>الاسم: ${user.name}</p> <p>البريد: ${user.email}</p> <p>مدير؟ ${user.admin}</p> <!-- تستدعي user.isAdmin() --> <p>الشركة: ${user.address.company}</p> <!-- تنقّل متسلسل -->

التنقّل المتسلسل (user.address.company) يواصل الاجتياز طالما أن كل خطوة تُعيد كائنًا غير null. أي null في أيّ مكان بالسلسلة يوقف الاجتياز ويُعيد سلسلة فارغة بدلًا من رمي استثناء.

عامل الأقواس — المجموعات والمفاتيح الديناميكية

عامل الأقواس ${expr[key]} أقوى من عامل النقطة لأن المفتاح يمكن أن يكون بدوره تعبيرًا. يعمل على الخرائط والقوائم والمصفوفات والـ beans:

<!-- وصول إلى Map --> <p>${settings['theme']}</p> <p>${settings[preferredKey]}</p> <!-- مفتاح ديناميكي من سمة أخرى --> <!-- وصول إلى List / مصفوفة بالفهرس --> <p>أوّل وسم: ${post.tags[0]}</p> <p>آخر وسم: ${post.tags[post.tags.size() - 1]}</p> <!-- استدعاءات دوال EL 3.0 --> <!-- خاصية bean عبر سلسلة نصية (مكافئ للنقطة) --> <p>${user['name']}</p>
النقطة مقابل الأقواس: استخدم عامل النقطة لأسماء الخصائص الثابتة المعروفة — فالأسلوب أوضح. استخدم الأقواس حين يكون المفتاح ديناميكيًا، أو رقميًا (فهرس مصفوفة/قائمة)، أو يحتوي على محارف غير صالحة كمعرّف EL (واصلات، مسافات).

العوامل داخل تعبيرات EL

تدعم EL مجموعة ثرية من العوامل كي تحسب القيم وتُشكّل الشروط مباشرةً في الصفحة دون اللجوء إلى scriptlets.

  • حسابية: +، -، *، / (أو div% (أو mod)
  • علائقية: == (أو eq!= (أو ne< (lt> (gt<= (le>= (ge)
  • منطقية: && (أو and|| (أو or! (أو not)
  • ثلاثية: condition ? valueIfTrue : valueIfFalse
  • اختبار الفراغ: empty expr — صحيح إذا كانت القيمة null أو سلسلة فارغة أو مصفوفة فارغة أو مجموعة فارغة
  • الإسناد (EL 3.0+): = — نادر الاستخدام في صفحات JSP العادية، لكنه متاح
<!-- فئة CSS شرطية --> <tr class="${item.outOfStock ? 'disabled' : 'active'}"> <!-- فحص الفراغ بأمان --> <c:if test="${not empty errors}"> <div class="alert">يرجى تصحيح الأخطاء أدناه.</div> </c:if> <!-- الأسماء المستعارة لتفادي الإفلات في السمات --> <c:if test="${count gt 0 and count lt 100}">...</c:if>
تجنّب المنطق المعقّد في EL. صُمِّمت EL لـقراءة بيانات النموذج لا لتشغيل منطق الأعمال. إذا امتدّ تعبير إلى أكثر من ثلاثية قصيرة واحدة فالمنطق ينتمي إلى الـ Servlet أو فئة مساعدة، لا إلى القالب. حافظ على قابلية القراءة والاختبار في العروض.

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

تعرض EL مجموعة من الخرائط للقراءة فقط ككائنات ضمنية، تشبه المتغيرات الضمنية في scriptlet لكنها أكثر ملاءمة:

  • param — معامل الطلب بالاسم، مثل ${param.q}
  • paramValues — معاملات الطلب متعددة القيم (تُعيد String[])
  • header / headerValues — رؤوس طلب HTTP
  • cookie — خريطة كائنات Cookie؛ استخدم ${cookie.JSESSIONID.value}
  • initParam — معاملات تهيئة سياق servlet من web.xml
  • pageContext — كائن PageContext الكامل، يمنح وصولًا إلى الطلب والاستجابة والجلسة والمزيد
<!-- قراءة معامل سلسلة الاستعلام بأمان --> <p>بحثتَ عن: ${param.q}</p> <!-- المستخدم الحالي من الجلسة --> <p>مسجّل الدخول باسم: ${sessionScope.currentUser.displayName}</p> <!-- URI الطلب عبر pageContext --> <p>المسار: ${pageContext.request.requestURI}</p>

استدعاء الدوال الثابتة والبانيات (EL 3.0+)

تتيح Jakarta EL 3.0 (Servlet 3.1 / Tomcat 8+) استدعاء الدوال الثابتة إذا استوردتَ الفئة أولًا عبر واجهات importClass أو importStatic. عمليًا، الاستخدام الأكثر شيوعًا هو من خلال دوال الأدوات المسجّلة عبر مكتبة دوال JSTL — وهذا يُغطَّى في الدرس التالي. في الوقت الراهن، اعلم أن EL تستطيع استدعاء أدوات مساعدة بأسلوب String.format حين تُوصَّل بشكل صحيح، لكن هذا التوصيل يكون في مكتبة الوسوم لا في EL الخام.

تركيب EL مع سمات HTML

تتألف تعبيرات EL بسلاسة داخل قيم سمات HTML. هنا يظهر تفوّقها على scriptlets:

<a href="/orders/${order.id}/detail">عرض الطلب</a> <img src="${pageContext.request.contextPath}/images/${product.imageFile}" alt="${product.name}"> <input type="text" name="username" value="${fn:escapeXml(param.username)}">
دائمًا أفلت القيم التي يوفّرها المستخدم بـ fn:escapeXml() عند تصييرها في سمات HTML أو المحتوى. لا تُفلت EL المخرجات افتراضيًا. تصيير ${param.search} مباشرةً في سمة ثغرة XSS. دالة fn:escapeXml جزء من مكتبة دوال JSTL التي تُغطَّى في الدرس السادس.

الخلاصة

تمنح لغة التعبير صفحات JSP طريقةً نظيفة وآمنة من حيث القيم الخالية لقراءة السمات ذات النطاق والتنقّل عبر خصائص الـ bean والوصول إلى المجموعات بالفهرس أو المفتاح وحساب الشروط البسيطة — كل ذلك دون سطر Java واحد من الـ scriptlet. تمنحك سلسلة النطاقات (page → request → session → application) وخرائط النطاق الصريحة تحكمًا دقيقًا في مصدر البيانات. استخدم عامل النقطة للـ beans، والأقواس للخرائط والقوائم، وكلمة empty للفحوصات الآمنة من القيم الخالية، والأسماء المستعارة (lt، gt، and) لإبقاء قيم السمات HTML صالحة. في الدرس التالي ستُضيف JSTL Core Tags فوق EL للتعامل مع التكرار والتفريع بشكل تصريحي.