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

مقدمة إلى JSP

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

مقدمة إلى JSP

JavaServer Pages — أو JSP اختصارًا — هي تقنية من جانب الخادم تُتيح لك تضمين منطق Java مباشرةً داخل ترميز HTML. تُنتج ملف قالب يُترجمه الخادم إلى Servlet كامل، يُنفِّذه ثم يُرسل استجابته إلى المتصفح بوصفها HTML عادية. إنّ فهم سبب اختراع JSP — وما يحدث بالضبط بين ملف .jsp واستجابة HTTP — هو الأساس المفاهيمي لكل ما يلي في هذا البرنامج التعليمي.

المشكلة التي صُمِّمت JSP لحلّها

قبل وجود JSP، كان إنتاج محتوى ويب ديناميكي في Java يعني كتابة Servlet: فئة Java عادية تستجيب لطلبات HTTP. تعمل Servlets بشكل ممتاز، لكن توليد HTML من داخل تابع Java أمر مُربك:

// Servlet خالص — توليد HTML بالطريقة الصعبة protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html><head><title>المنتجات</title></head><body>"); out.println("<h1>قائمة المنتجات</h1>"); out.println("<ul>"); for (String name : productService.findAll()) { out.println("<li>" + name + "</li>"); // خطر XSS هنا أيضًا } out.println("</ul></body></html>"); }

أي تغيير في HTML يستلزم إعادة تصريف ونشر الكود. مصمم يفهم HTML لكنه لا يعرف Java لا يستطيع تعديل الملف بأمان. السلاسل يصعب قراءتها، ولا تستطيع أدوات التطوير تمييز الترميز بألوان، والكود مقترن بشدة بتفاصيل العرض التي تتغير كثيرًا أكثر من المنطق التجاري.

تعكس JSP هذه العلاقة: تكتب HTML أولًا، وتُدرج جُزرًا صغيرة من Java حيثما تحتاج إلى محتوى ديناميكي.

JSP مقابل Servlets — شريكان لا منافسان

تحلّ JSP و Servlets نصفَي المشكلة ذاتها. يُخصّص النمط الناضج (MVC الذي تتناوله الدرس السابع) لكلٍّ منهما دوره المناسب:

  • Servlet — يعالج الطلب، يتحقق من المدخلات، يستدعي الخدمات/المستودعات، يضع النتائج في سمات الطلب، ثم يُعيد التوجيه إلى JSP.
  • JSP — تقرأ تلك السمات وتُصيِّر HTML. لا تحتوي على منطق أعمال، ولا استدعاءات قاعدة بيانات، ومن المثالي ألّا تضمّ أي كتل برمجية Java خام.
القاعدة العملية: إذا كانت صفحة JSP ستبقى ذات معنى من حيث البنية عند حذف كل تعبير Java فيها واستبداله بنص وهمي، فأنت قد حققت الفصل الصحيح. JSP هي تقنية عرض، وليست متحكمًا (controller).

كيف يتحوّل ملف JSP إلى Servlet

حين يصل طلب للحصول على products.jsp، يُشغِّل حاوي Jakarta EE (Tomcat أو WildFly أو GlassFish وغيرها) الملف عبر خط أنابيب محدد. إنّ فهم هذا الخط يُزيل الغموض عن رسائل الخطأ ويُخبرك متى تُعاد عملية التصريف.

  1. مرحلة الترجمة — يقرأ الحاوي مصدر .jsp ويولّد ملف مصدر Java. هذا الملف هو فئة تمتد من jakarta.servlet.http.HttpJspBase، التي تمتد بدورها من HttpServlet. يتحوّل ترميز HTML إلى سلسلة من استدعاءات out.write("<p>...");؛ أما تعبيرات Java المضمّنة فتصبح تعليمات إخراج مباشرة.
  2. مرحلة التصريف — يُصرِّف الحاوي مصدر Java المولَّد إلى ملف .class باستخدام مُصرِّف Java المدمج (javac). إذا كان كود Java المضمَّن خاطئًا نحويًا، فسترى خطأ تصريفٍ هنا، كثيرًا ما يتضمن رقم سطر يُشير إلى المصدر المولَّد لا إلى JSP الأصلية — وهذا ما يجعل رسائل أخطاء JSP محيِّرةً في البداية.
  3. التحميل والتهيئة — يُحمَّل الملف المُصرَّف في JVM ويُنشأ منه كائن تمامًا كأي Servlet آخر. تُستدعى jspInit() مرة واحدة.
  4. معالجة الطلبات — لكل طلب لاحق، تُستدعى _jspService(HttpServletRequest, HttpServletResponse) على الكائن القائم. يحتوي هذا التابع على كل كود الإخراج المولَّد.
  5. التخزين المؤقت — يُخزِّن الحاوي الفئة المُصرَّفة مؤقتًا. عند الطلب التالي يتحقق مما إذا كان مصدر .jsp أحدث من الفئة المُصرَّفة؛ إن لم يكن كذلك تجاوز مرحلتَي الترجمة والتصريف كليًا. لهذا تحتاج معظم الحاويات إلى إخطارها بالتحديثات في الإنتاج (أو تضبط development="false" في conf/web.xml الخاص بـ Tomcat لتعطيل فحص الطابع الزمني بأكمله من أجل الأداء).
<!-- products.jsp (ما تكتبه) --> <%@ page contentType="text/html;charset=UTF-8" %> <!DOCTYPE html> <html> <head><title>المنتجات</title></head> <body> <h1>قائمة المنتجات</h1> <ul> <% for (String p : (java.util.List<String>) request.getAttribute("products")) { %> <li><%= p %></li> <% } %> </ul> </body> </html>

يُترجم الحاوي تلك الصفحة إلى شيء مكافئ هيكليًا لما يلي:

// Servlet مولَّد مبسَّط (كود شبه حقيقي — الإخراج الفعلي أطول) public final class products_jsp extends HttpJspBase { public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html;charset=UTF-8"); JspWriter out = pageContext.getOut(); out.write("<!DOCTYPE html>\n<html>\n<head>..."); out.write("<h1>قائمة المنتجات</h1>\n<ul>\n"); for (String p : (java.util.List<String>) request.getAttribute("products")) { out.write("<li>"); out.print(p); // <%= p %> تصبح out.print(...) out.write("</li>\n"); } out.write("</ul>\n</body>\n</html>"); } }
ابحث عن المصدر المولَّد على القرص. في Tomcat، تقع ملفات Java المولَّدة في $CATALINA_HOME/work/Catalina/localhost/<app>/. قراءة الملف المولَّد عند حدوث خطأ غامض كثيرًا ما تكون أسرع طريقة لفهم المشكلة — فأرقام الأسطر في stack trace تُشير إلى ذلك الملف المولَّد.

دورة حياة JSP باختصار

بما أن JSP هي في نهاية المطاف Servlet، فإن دورة حياتها تُعكس دورة حياة Servlet:

  • jspInit() — تُستدعى مرة واحدة بعد تحميل الفئة. يمكنك تجاوزها في كتلة تصريح JSP لإعداد الموارد (نادرًا ما يُحتاج إليه).
  • _jspService() — تُستدعى لكل طلب. هذا ما تكتبه فعليًا حين تؤلّف جسم JSP. لا يمكنك تجاوزها مباشرةً — الحاوي يُولِّدها.
  • jspDestroy() — تُستدعى مرة واحدة عند إيقاف تشغيل الحاوي أو إعادة تحميل JSP. أتلف أي موارد تهيّأت في jspInit().
JSP ليست آمنة للخيوط المتزامنة بشكل افتراضي. بما أن كائنًا واحدًا يعالج الطلبات المتزامنة، فإن أي متغير نسخة تُعلنه في كتلة تصريح JSP مُشترَك بين جميع الطلبات. لا تخزّن بيانات خاصة بطلب معين في متغيرات النسخة — استخدم سمات الطلب، أو المتغيرات المحلية داخل _jspService()، أو سمات الجلسة. هذا هو ذات الفخ الموجود في Servlets العادية.

مكانة JSP اليوم

كثيرًا ما تستبدل تطبيقات Jakarta EE و Spring الحديثة JSP بـ Thymeleaf أو FreeMarker أو واجهة أمامية بـ JavaScript. ومع ذلك تظل JSP تقنية العرض الأساسية المدرَّسة في مواصفة Jakarta EE، ولا تزال كميات هائلة من كود Java المؤسسي تستخدمها. كذلك يمنحك فهم JSP بعمق نموذجًا ذهنيًا دقيقًا لطريقة عمل أي محرك قوالب من جانب الخادم: جميعها في نهاية المطاف تُنتج تدفقًا من الحروف إلى Writer، وتستخدم شكلًا من أشكال تقييم التعبيرات لإدراج القيم الديناميكية. المفاهيم التي ستتعلمها هنا تنتقل مباشرةً إلى تلك التقنيات.

الخلاصة

وُجدت JSP لأن كتابة HTML داخل كود Servlet في Java عُرضة للأخطاء وصعبة الصيانة. ملف JSP هو HTML يحتوي على أجزاء Java مضمَّنة؛ يُترجمه الحاوي ويُصرِّفه إلى Servlet كامل خلف الكواليس. يُفسِّر خط أنابيب الترجمة-التصريف-التخزين المؤقت لماذا تُشير رسائل خطأ JSP أحيانًا إلى ملفات مصدر مولَّدة، ولماذا لا تعكس الصفحة تغييرًا ما لم يُعد الحاوي تصريفها. JSP و Servlets شريكان: تتولى Servlets تدفق التحكم والوصول إلى البيانات، وتتولى JSP التصيير. في الدرس التالي ستستكشف عناصر النصوص البرمجية الثلاثة في JSP — التصريحات، والكتل البرمجية، والتعبيرات — التي تُتيح لك تضمين Java داخل ترميز HTML ذاك.