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

عناصر البرمجة النصية في JSP

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

عناصر البرمجة النصية في JSP

صُمّمت صفحات JavaServer Pages (JSP) كوسيلة لتضمين المخرجات الديناميكية مباشرةً داخل HTML. لتحقيق ذلك، عرّفت المواصفة الأصلية ثلاثة عناصر برمجية نصية تتيح كتابة Java داخل ملف .jsp: الكتل النصية (scriptlets)، والتعبيرات (expressions)، والتصريحات (declarations). ينبغي لكل مطوّر JSP أن يتعرّف على هذه البنى — سواء لصيانة الكود القديم أو لفهم السبب الجوهري الذي يجعل JSP الحديثة تتجنّبها عمدًا.

كيف تعمل ترجمة JSP

قبل الخوض في التفاصيل، من المفيد أن تفهم ما يفعله حاوي servlet فعليًا بملف JSP. عندما يصل طلب لمورد .jsp، يترجم الحاوي (Tomcat أو WildFly أو غيرهما) الملف إلى ملف Java مصدري يمتد من HttpJspBase، ثم يصرّفه إلى bytecode، ومن ثمّ يخدم الطلبات اللاحقة من servlet المُصرَّفة — تمامًا كـ HttpServlet المكتوبة يدويًا. عناصر البرمجة النصية هي كود Java حرفي يُدمج في الفئة المُولَّدة. وهذا النموذج التحويلي هو السبب في كون عناصر البرمجة النصية قويةً وخطرةً في آنٍ واحد.

الكتل النصية: <% ... %>

تضع الكتلة النصية جمل Java اعتباطية داخل الدالة _jspService() المُولَّدة. الصياغة هي <% javaCode; %>.

<%@ page import="java.util.List" %> <!DOCTYPE html> <html> <body> <% // كتلة نصية: تُنفَّذ داخل _jspService() List<String> names = (List<String>) request.getAttribute("names"); if (names == null || names.isEmpty()) { %> <p>لا توجد أسماء.</p> <% } else { for (String name : names) { %> <p><%= name %></p> <% } } %> </body> </html>

ينسج الحاوي مقاطع HTML بين الكتل النصية داخل استدعاءات out.write("..."). والنتيجة تُصرَّف وتعمل، لكن الكود المُولَّد يتشابك فيه Java وكتابة السلاسل النصية بطريقة تجعل اختباره أو صيانته شبه مستحيل.

الكتل النصية تكسر الفصل بين الاهتمامات. تتسرّب منطق الأعمال إلى طبقة العرض، ويتشابك HTML وJava بإحكام، ويصبح اختبار الوحدة مستحيلًا لأن الكود لا يعمل إلا داخل حاوي servlet حي. هذا هو السبب الجوهري الذي دفع الصناعة إلى التخلي عن الكتل النصية.

التعبيرات: <%= ... %>

يُقيّم عنصر التعبير تعبير Java واحدًا ويكتب قيمة toString() الخاصة به مباشرةً في الاستجابة. لا يوجد فاصلة منقوطة داخل وسم التعبير.

<p>مرحبًا، <%= request.getAttribute("username") %>!</p> <p>اليوم هو <%= java.time.LocalDate.now() %></p> <p>عناصر في السلة: <%= ((java.util.List<?>) session.getAttribute("cart")).size() %></p>

يترجم الحاوي <%= expr %> إلى out.print(expr);. انتبه إلى أنه إذا أسفر التعبير عن null، تُكتب السلسلة "null" في المخرجات — لا توجد حماية من null.

التعبيرات مقابل الكتل النصية: التعبير اختصار لكتلة نصية من سطر واحد تحتوي out.print(). لا يمكنك استخدام الجمل (الإسنادات، الحلقات، الشروط) داخل وسم التعبير — بل تعبيرٌ واحد فقط يُنتج قيمة.

التصريحات: <%! ... %>

تضع التصريح كودًا على مستوى الفئة في servlet المُولَّدة، خارج _jspService(). يمكنك تصريح متغيرات النسخة والدوال هنا.

<%! // مُصرَّح كمتغير نسخة في فئة servlet المُولَّدة private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger("MyPage"); private String formatPrice(double amount) { return String.format("$%.2f", amount); } %> <!-- استخدام الدالة المُصرَّحة من تعبير --> <p>السعر: <%= formatPrice(49.99) %></p>
متغيرات النسخة في التصريحات مشتركة بين جميع الطلبات المتزامنة. servlet المُولَّدة من JSP كائن وحيد (singleton) — نسخة واحدة تخدم جميع الخيوط. أي حالة قابلة للتغيير في التصريح هي سباق بيانات (data race) ينتظر فرصته. وهذا خفيّ ويُعدّ مصدرًا شائعًا لأخطاء يصعب إعادة إنتاجها في تطبيقات JSP القديمة.

توجيه الصفحة (Page Directive)

تبدأ كل ملفات JSP تقريبًا بـتوجيه الصفحة، الذي يُهيّئ إعدادات servlet المُولَّدة. إنه ليس عنصر برمجة نصية، لكنه يظهر دائمًا بجانبها.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.List, java.util.Map" errorPage="/WEB-INF/views/error.jsp" isErrorPage="false" session="true" %>

السمات الرئيسية: import يضيف جمل import إلى الفئة المُولَّدة؛ contentType يضبط ترويسة Content-Type للاستجابة؛ errorPage يحوّل الاستثناءات غير الملتقطة إلى صفحة خطأ مخصصة.

لماذا تتجنّب JSP الحديثة الكتل النصية

توصّل مجتمع Java EE إلى توافق واسع — رُسّخ في كتاب Core J2EE Patterns من Sun ولاحقًا في توجيهات مواصفة JSP 2.0 — مفاده أن الكتل النصية لا ينبغي أن تظهر في ملفات JSP الإنتاجية. الأسباب ملموسة:

  • كود غير قابل للاختبار. لا يمكن تشغيل المنطق داخل الكتلة النصية إلا بنشر الصفحة وإرسال طلب HTTP. لا توجد طريقة لكتابة اختبار وحدة لكتلة نصية.
  • صياغة معادية للمصمّمين. لا يستطيع مصمّمو HTML/CSS ومهندسو الواجهة الأمامية العمل مع ملفات JSP مليئة بكود Java. تعاني أدوات مثل بيئات التطوير المتكاملة والمحللات الساكنة من الصياغة المختلطة.
  • تكلفة الصيانة. تضاعف كل تغيير العبء المعرفي عند تشابك Java وHTML. تُظهر دراسات قواعد الكود القديمة باستمرار أن الكتل النصية في JSP تمثّل نقطة ساخنة للصيانة.
  • توجد بدائل أفضل. توفّر لغة التعبير (EL) وJSTL — التي تُغطَّى في الدروس القادمة — بدائل تصريحية وسهلة التعامل للمصمّمين لكل نمط شائع من الكتل النصية (التكرار، الشروط، التنسيق). تذهب Thymeleaf وFreeMarker أبعد من ذلك بكونهما HTML صالحًا حتى قبل معالجة القوالب.
مواصفة JSP 2.0 الصادرة عن Sun/Oracle نفسها تقول: "نوصي بتجنّب استخدام الكتل النصية." يمكنك فرض ذلك على مستوى الحاوي بإضافة <scripting-invalid>true</scripting-invalid> إلى <jsp-property-group> في ملف web.xml. يحوّل هذا أي كتلة نصية متبقية في المشروع إلى خطأ وقت التصريف — حاجز مفيد عند تهيئة قاعدة كود موروثة.
<!-- web.xml: تعطيل الكتل النصية على مستوى المشروع --> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <scripting-invalid>true</scripting-invalid> <el-ignored>false</el-ignored> </jsp-property-group> </jsp-config>

مرجع سريع: العناصر الثلاثة للبرمجة النصية

  • <% statements; %>الكتلة النصية: جمل Java اعتباطية، تُوضع في _jspService(). تجنّبها في الكود الجديد.
  • <%= expression %>التعبير: تعبير Java واحد تُطبع قيمته في الاستجابة. يحلّ محله EL بصيغة ${expression}.
  • <%! declaration %>التصريح: أعضاء على مستوى الفئة (حقول، دوال). محفوف بالمخاطر بسبب الحالة المشتركة القابلة للتغيير؛ نادرًا ما تكون مطلوبة.

الخلاصة

تتيح الكتل النصية والتعبيرات والتصريحات تضمين Java مباشرةً داخل HTML — وهذا بالضبط ما يجعلها إشكالية. فهي تخلط مخاوف العرض بالمنطق، وتُنتج كودًا غير قابل للاختبار، وتستلزم فهم نموذج الترجمة (فئة servlet المُولَّدة، الدالة _jspService()) للاستدلال على سلوكها. يستبدل تطوير JSP الحديث الثلاثة بـ EL وJSTL، اللتين توفّران المخرجات الديناميكية ذاتها دون خلط اللغتين. في الدرس القادم ستستكشف الكائنات الضمنية المتاحة دائمًا في كل JSP — فهمها يُكمل الصورة قبل الانتقال إلى EL وJSTL.