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

إعادة استخدام العروض: التضمين وملفات التاجات

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

إعادة استخدام العروض: التضمين وملفات التاجات

تواجه كل تطبيقات JSP في بيئة الإنتاج مشكلة مألوفة: الترويسات والتذييلات وأشرطة التنقل وأدوات الشريط الجانبي يجب أن تظهر على عشرات الصفحات — والحفاظ على اتساقها يعني مركزتها في مكان واحد. يوفر JSP ثلاث آليات متميزة لهذا الغرض: توجيه التضمين الثابت، وإجراء <jsp:include> الديناميكي، وملفات التاجات المخصصة الأكثر قوة. اختيار الآلية المناسبة لكل موقف هو جزء من كتابة كود عرض قابل للصيانة.

التضمين الثابت: توجيه include

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

<%-- في header.jspf (JSP Fragment) --%> <header class="site-header"> <h1>تطبيقي</h1> <nav> <a href="${pageContext.request.contextPath}/home">الرئيسية</a> <a href="${pageContext.request.contextPath}/products">المنتجات</a> </nav> </header> <%-- في الصفحة الرئيسية --%> <%@ include file="/WEB-INF/fragments/header.jspf" %>
اتفاقية: تُعطى ملفات أجزاء JSP عادةً امتداد .jspf للإشارة إلى أنها ليست صفحات مستقلة. وضعها تحت /WEB-INF/ يمنع المتصفحات من الوصول إليها مباشرة، وهو ما يُعدّ من أفضل الممارسات.

نظرًا لأن التضمين يُحلّ في وقت التجميع، إذا قمت بتحديث header.jspf يجب أن تجبر على إعادة تجميع كل صفحة تضمّنته. في خوادم التطوير يحدث هذا عادةً تلقائيًا، لكن من المفيد الإدراك به في البيئات التي تستخدم تخزينًا مؤقتًا قويًا للكلاسات.

التضمين الديناميكي: <jsp:include>

يُحلّ إجراء <jsp:include> في وقت الطلب. في كل مرة يُخدَّم فيها الصفحة، يُرسَل طلب فرعي إلى المورد المضمَّن ويُدمج ناتجه في تدفق استجابة الصفحة الأم. هذا يعني أن الصفحة المضمَّنة تعمل كوحدة مستقلة — لا يمكنها رؤية متغيرات نطاق الصفحة الأم، لكن يمكنها قبول معاملات، ويمكنها أن تكون موردًا ديناميكيًا بالكامل (حتى servlet).

<%-- تمرير معاملات إلى صفحة مضمَّنة --%> <jsp:include page="/WEB-INF/fragments/sidebar.jsp"> <jsp:param name="category" value="${currentCategory}" /> </jsp:include> <%-- في sidebar.jsp، اقرأ باستخدام request.getParameter --%> <aside> <h3>تصفح: ${param.category}</h3> <!-- محتوى خاص بالفئة --> </aside>

التوازن الرئيسي: التضمينات الديناميكية أكثر مرونة وتعكس دائمًا أحدث حالة للملف المضمَّن دون إعادة تجميع الصفحة الأم، لكنها تحمل تكلفة إرسال طلب فرعي مع كل طلب.

الاختيار بين التضمين الثابت والديناميكي

  • استخدم الثابت (توجيه include) للأجزاء الثابتة فعلًا — تذييلات حقوق النشر، التنقل الثابت الذي لا يتغير في وقت التشغيل، تصريحات taglib المشتركة. الدمج في وقت التجميع يعني صفر تكلفة في وقت التشغيل.
  • استخدم الديناميكي (<jsp:include>) عندما يحتاج الجزء نطاقه المستقل، أو عندما تحتاج لتمرير معاملات، أو عندما يتغير ناتج الجزء مع كل طلب (مثل أداة "المستخدم المسجَّل حاليًا").
تصريحات Taglib مع التضمين الثابت: نمط شائع وأنيق هو إنشاء ملف taglibs.jspf واحد يحتوي على جميع تصريحات <%@ taglib ... %> وتضمينه بشكل ثابت في مقدمة كل صفحة. لأنه تضمين ثابت، تصبح التصريحات جزءًا من كل صفحة في وقت التجميع — لا تكرار، ومكان واحد لإضافة أو حذف مكتبة.

ملفات التاجات المخصصة

يمنحك التضمين الثابت والديناميكي إعادة استخدام على مستوى جزء الصفحة. ترفع ملفات التاجات هذا إلى مستوى أعلى: تتيح لك تعريف تاج JSP مخصص بسمات مسمّاة، مما يجعل ترميزك واضح الدلالة وموثَّق بذاته. ملف التاج هو ملف .tag عادي (أو .tagx لصيغة XML) يُخزَّن في /WEB-INF/tags/ — لا Java، ولا واصف TLD مطلوب.

افترض أن لديك مكوّن "بطاقة" يتكرر في تطبيقك. عرّفه مرة واحدة:

<%-- /WEB-INF/tags/card.tag --%> <%@ tag description="مكوّن بطاقة بنمط Bootstrap" pageEncoding="UTF-8" %> <%@ attribute name="title" required="true" rtexprvalue="true" %> <%@ attribute name="subtitle" required="false" rtexprvalue="true" %> <%@ taglib prefix="c" uri="jakarta.tags.core" %> <div class="card shadow-sm mb-3"> <div class="card-header"> <strong>${title}</strong> <c:if test="${not empty subtitle}"> <span class="text-muted ms-2">${subtitle}</span> </c:if> </div> <div class="card-body"> <jsp:doBody /> </div> </div>

استخدمه من أي JSP بتصريح بادئة taglib تشير إلى مجلد التاجات:

<%@ taglib prefix="ui" tagdir="/WEB-INF/tags" %> <ui:card title="ملف تعريف المستخدم" subtitle="آخر تحديث اليوم"> <p>الاسم: ${user.fullName}</p> <p>البريد الإلكتروني: ${user.email}</p> </ui:card> <ui:card title="سجل الطلبات"> <p>لديك ${orderCount} طلبات.</p> </ui:card>
<jsp:doBody /> هو مكافئ ملف التاج لـ slot أو yield — يُصيّر أي محتوى وضعه المُستدعي بين التاج الفاتح والمغلق. إذا حذفته من ملف التاج سيُتجاهل الجسم بصمت.

سمات ملف التاج بالتفصيل

يشبه توجيه <%@ attribute %> معاملة الدالة. خياراته الرئيسية:

  • name — اسم السمة المستخدم في الترميز المُستدعي.
  • requiredtrue أو false؛ سيرفض الحاوي الصفحة في وقت الترجمة إذا كانت سمة مطلوبة مفقودة.
  • rtexprvaluetrue يسمح بتعبيرات EL (${...}) كقيمة؛ false يقصرها على سلسلة نصية حرفية.
  • type — نوع Java للسمة (مثل java.lang.Integer)؛ يُجري الحاوي تحويل السلسلة تلقائيًا.
<%-- /WEB-INF/tags/paginator.tag --%> <%@ tag pageEncoding="UTF-8" %> <%@ attribute name="currentPage" required="true" type="java.lang.Integer" %> <%@ attribute name="totalPages" required="true" type="java.lang.Integer" %> <%@ attribute name="baseUrl" required="true" rtexprvalue="true" %> <%@ taglib prefix="c" uri="jakarta.tags.core" %> <nav aria-label="تنقل الصفحات"> <ul class="pagination"> <c:forEach var="i" begin="1" end="${totalPages}"> <li class="page-item ${i == currentPage ? 'active' : ''}"> <a class="page-link" href="${baseUrl}?page=${i}">${i}</a> </li> </c:forEach> </ul> </nav>
<%-- استدعاء المُقسِّم في صفحة قائمة المنتجات --%> <ui:paginator currentPage="${currentPage}" totalPages="${totalPages}" baseUrl="${pageContext.request.contextPath}/products" />

بناء قالب تخطيط باستخدام ملفات التاجات

من أقوى الأنماط مع ملفات التاجات قالب تخطيط كامل للصفحة — تاج واحد يلفّ كل صفحة بإطار متسق، مما يتيح للصفحات الفرعية حقن عنوانها ومحتواها.

<%-- /WEB-INF/tags/layout.tag --%> <%@ tag pageEncoding="UTF-8" %> <%@ attribute name="pageTitle" required="true" rtexprvalue="true" %> <%@ attribute name="cssClass" required="false" rtexprvalue="true" %> <!DOCTYPE html> <html lang="ar" dir="rtl"> <head> <meta charset="UTF-8"> <title>${pageTitle} | تطبيقي</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/css/app.css"> </head> <body class="${not empty cssClass ? cssClass : 'default-layout'}"> <%@ include file="/WEB-INF/fragments/topbar.jspf" %> <main class="container"> <jsp:doBody /> </main> <%@ include file="/WEB-INF/fragments/footer.jspf" %> </body> </html>

تصبح الصفحات الفردية نظيفة ومركّزة:

<%@ taglib prefix="ui" tagdir="/WEB-INF/tags" %> <ui:layout pageTitle="لوحة التحكم" cssClass="dashboard-page"> <h2>مرحبًا بعودتك، ${user.firstName}!</h2> <ui:card title="إحصاءاتك"> <p>الطلبات: ${stats.orderCount} | التقييمات: ${stats.reviewCount}</p> </ui:card> </ui:layout>
ملفات التاجات مقابل Tiles / لهجة تخطيط Thymeleaf: ملفات التاجات هي حل JSP خالص بلا تبعيات — مثالية عند البناء بـ Jakarta EE الأصلية دون أطر عمل إضافية. بمجرد انتقالك إلى Spring MVC، فكّر في استخدام لهجة تخطيط Thymeleaf أو Apache Tiles لنفس النمط مع دعم أفضل من بيئات التطوير المتكاملة.

الخلاصة

ثلاث أدوات لثلاث حالات استخدام: توجيه التضمين الثابت يدمج المحتوى في وقت التجميع بلا تكلفة في وقت التشغيل، مثالي لتصريحات taglib المشتركة والأجزاء الثابتة؛ <jsp:include> يُرسل طلبًا فرعيًا في وقت التشغيل مانحًا كل جزء نطاقه المستقل وإمكانية تمرير المعاملات؛ ملفات التاجات ترفع إعادة الاستخدام إلى نموذج مكوّنات — سمات مسمّاة وفتحات للمحتوى ودعم كامل لـ JSTL في ملف .tag عادي. معًا تمنحك كل ما تحتاجه لبناء طبقة عرض متسقة وقابلة للصيانة دون تكرار سطر واحد من ترميز التخطيط.