أساسيات جافا للويب والـ Servlets

كيف تُشغّل Java الويب

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

كيف تُشغّل Java الويب

قبل كتابة سطر واحد من كود Servlet يستحق الأمر أن تتوقف لفهم البنية التحتية التي يعمل عليها. دعمت Java تطبيقات الويب المؤسسية لأكثر من عقدين — لا بسبب إضافات في الصياغة، بل بسبب بيئة تشغيل محكومة بالحاوية تعالج التزامن ودورة الحياة وترجمة البروتوكول عنك. يرسم هذا الدرس تلك البنية حتى يقع كل مفهوم لاحق في سياقه الصحيح.

نموذج الطلب/الاستجابة HTTP

كل تفاعل على الويب — من تحميل صفحة إلى إرسال نموذج إلى استدعاء نقطة طرفية REST — يتبع النمط ذاته: يرسل العميل طلب HTTP ويعيد الخادم استجابة HTTP. HTTP بروتوكول نصي عديم الحالة. كل تبادل مستقل؛ لا يحتفظ الخادم بأي ذاكرة عن الطلبات السابقة ما لم تبني تلك الطبقة صراحةً بنفسك (cookies والجلسات والرموز).

يتكوّن طلب HTTP من:

  • الطريقة (Method) — النية: GET (قراءة)، POST (إرسال/إنشاء)، PUT/PATCH (تحديث)، DELETE (حذف).
  • URI الطلب — مسار المورد، مثل /products/42.
  • الترويسات (Headers) — بيانات وصفية: Content-Type، Authorization، Accept، إلخ.
  • الجسم (Body) — الحمولة الاختيارية (حقول النموذج أو JSON أو بيانات ثنائية). لا يمتلك GET وDELETE جسمًا بالعرف.

تتكوّن استجابة HTTP من:

  • سطر الحالة — رمز مكوّن من ثلاثة أرقام: 200 OK، 301 Moved Permanently، 404 Not Found، 500 Internal Server Error.
  • الترويساتContent-Type، Cache-Control، Set-Cookie، إلخ.
  • الجسم — الحمولة: HTML أو JSON أو صورة أو ملف للتنزيل.
HTTP عديم الحالة بالتصميم. يصل كل طلب كما لو أن العميل لم يتصل قط من قبل. الحالة المستمرة — جلسات تسجيل الدخول والسلات — تُبنى فوق ذلك عبر الكوكيز أو معاملات URL أو مخازن جلسات على الخادم. ستنفّذ هذا لاحقًا في البرنامج التعليمي؛ احتفظ بهذا الأساس عديم الحالة في ذهنك وأنت تفعل ذلك.

خوادم الويب مقابل حاويات Servlet

يستخدم الناس مصطلحي "خادم الويب" و"خادم التطبيقات" باستهتار، لكن هناك فرقًا ذا معنى يؤثر مباشرةً في طريقة هيكلة تطبيقات Java للويب.

خادم الويب (Nginx أو Apache httpd) محسَّن لخدمة المحتوى الثابت — ملفات HTML والصور وCSS وJavaScript — بأسرع ما يمكن. يتحدث HTTP بطلاقة ويعمل وكيلًا عكسيًا، لكنه لا يملك قدرةً أصيلة على تنفيذ كود Java أو إدارة مكونات التطبيق.

حاوية Servlet (تُسمى أيضًا حاوية الويب) هي بيئة تشغيل Java تقوم بما يلي:

  1. قبول اتصالات HTTP الواردة.
  2. تحليل بايتات HTTP الخام إلى كائنات يمكن لكودك استخدامها (HttpServletRequest، HttpServletResponse).
  3. تعيين URL الطلب إلى فئة Servlet الصحيحة.
  4. إنشاء وإدارة نسخ Servlet بما في ذلك دورة حياتها بالكامل.
  5. إدارة مجمّعات الخيوط (thread pools) حتى تُعالَج الطلبات المتعددة بشكل متزامن.
  6. تسلسل كائن الاستجابة مرةً أخرى إلى بايتات HTTP وإرسالها إلى العميل.

Apache Tomcat هو التطبيق المرجعي والحاوية الأكثر انتشارًا. إنه خفيف الوزن وقابل للتضمين (يضمّنه Spring Boot افتراضيًا)، ويطبّق مواصفة Jakarta Servlet (كانت تُسمى javax.servlet قبل انتقال مساحة الاسم إلى jakarta.*).

إصدارات Tomcat وانقسام مساحة الاسم. ينفّذ Tomcat 9.x مواصفة Servlet 4.0 (javax.servlet). ينفّذ Tomcat 10.x+ مواصفة Servlet 5.0+ (jakarta.servlet) عقب نقل Oracle لـ Java EE إلى مؤسسة Eclipse. إذا كنت تبدأ مشروعًا جديدًا اليوم فاستخدم Tomcat 10+ وواردات Jakarta EE 10. قواعد الكود القديمة على Tomcat 9 لا تزال تستخدم مساحة الاسم javax. يستخدم هذا البرنامج التعليمي مساحة الاسم الحديثة jakarta.* طوال الوقت.

أين تقع Servlet في المنظومة

Servlet هي ببساطة فئة Java تستدعيها الحاوية عند وصول طلب HTTP مطابق. تقع بين طبقة الشبكة الخام (Tomcat) ومنطق تطبيقك (الخدمات والمستودعات وكائنات النطاق). فكّر فيها باعتبارها نقطة دخول HTTP — المكان الذي يتحول فيه طلب HTTP إلى استدعاء لطريقة Java.

يبدو التدفق كما يلي:

المتصفح / العميل │ │ طلب HTTP (مقبس TCP، بايتات خام) ▼ Tomcat (حاوية Servlet) │ ── يحلّل البايتات → كائن HttpServletRequest │ ── يطابق URL → YourServlet.class │ ── يستدعي doGet() / doPost() على خيط من مجمّعه ▼ YourServlet │ ── يقرأ معاملات الطلب والترويسات والجسم │ ── يستدعي طبقة Service / Repository │ ── يكتب في HttpServletResponse (الحالة والترويسات والجسم) ▼ Tomcat │ ── يسلسل HttpServletResponse → بايتات HTTP ▼ المتصفح / العميل (يستقبل HTML / JSON / ملف)

لا يتعامل كودك أبدًا مع مقابس TCP أو تحليل HTTP أو إدارة الخيوط. تتولى الحاوية كل ذلك. أنت تنفّذ منطق العمل والإخراج — الجزء الذي تعلمه أنت وحدك.

مواصفة Servlet

Servlet ليست مفهومًا خاصًا بإطار عمل — إنها معيار رسمي. تحدّد مواصفة Jakarta Servlet الواجهة jakarta.servlet.Servlet وطرق دورة الحياة التي يجب على الحاوية استدعاؤها، ونموذج الخيوط، وتعريفات الجلسة، وتنسيق الوصف النشري (web.xml). أي حاوية متوافقة — Tomcat أو Jetty أو WildFly أو GlassFish — يجب أن تحترم هذا العقد. الكود المكتوب وفق المواصفة قابل للنقل عبر الحاويات دون تعديل.

التسلسل الهرمي للفئات التي ستستخدمها أكثر هو:

jakarta.servlet.Servlet (واجهة — العقد الجذر) └── jakarta.servlet.GenericServlet (مجردة — محايدة البروتوكول) └── jakarta.servlet.http.HttpServlet (مجردة — خاصة بـ HTTP) └── YourServlet (فئتك المحددة)

تمدّ HttpServlet وتتجاوز فقط معالجات طرق HTTP التي تحتاجها: doGet()، doPost()، doPut()، doDelete(). تستدعي الحاوية المناسب منها بناءً على طريقة الطلب الوارد.

هذا هو الهيكل العظمي الأدنى — ستملأه في الدروس التالية:

package com.example.web; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/hello") // تعيين هذه الـ Servlet إلى /hello public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = resp.getWriter()) { out.println("<html><body>"); out.println("<h1>Hello from a Servlet!</h1>"); out.println("</body></html>"); } } }
تحلّ التعليقة التوضيحية @WebServlet محلّ تعيين web.xml. قبل Servlet 3.0 كان لكل servlet أن يُصرَّح عنه ويُعيَّن في وصف نشري WEB-INF/web.xml. التعليقة التوضيحية أنظف وتُبقي تعيين URL قربًا من الفئة التي تنتمي إليها. يعمل الأسلوبان؛ وأُطر مثل Spring MVC تستخدم عادةً التسجيل البرمجي عوضًا عن ذلك.

كيف يعالج Tomcat التزامن

يحتفظ Tomcat بمجمّع خيوط (افتراضيًا: حتى 200 خيط). عند وصول طلب يُرسَل إلى خيط متاح. يستدعي ذلك الخيط doGet() الخاصة بـ servlet (أو doPost() إلخ)، ينتظر عودتها، ثم يُصرَّف الرد ويعود الخيط إلى المجمّع.

الإفادة الحرجة: نسخة servlet الواحدة تعالج طلبات متزامنة كثيرة على خيوط مختلفة في آنٍ واحد. تنشئ الحاوية نسخةً واحدة من servlet لكل تعيين (افتراضيًا) وتعيد استخدامها لكل طلب. هذا يعني:

  • متغيرات النسخ في servlet مشتركة عبر كل الخيوط — مصدر شهير لحالات التسابق (race conditions).
  • يجب أن تعيش كل الحالة المرتبطة بالطلب في متغيرات محلية داخل الطريقة، أو على كائنات الطلب/الجلسة.
  • إذا خزّنت عدادًا أو ذاكرةً مؤقتة أو أي حالة قابلة للتغيير في حقل servlet، يجب مزامنتها صراحةً.
لا تخزّن أبدًا بيانات مرتبطة بالطلب في متغيرات نسخة servlet. نمط مثل this.currentUser = req.getParameter("user") داخل doGet() هو حالة تسابق: ستُستبدَل قيمة الخيط A بقيمة الخيط B قبل أن ينتهي الخيط A من استخدامها. احتفظ بالحالة في HttpSession أو خصائص الطلب أو المتغيرات المحلية للطريقة.

الصورة الأشمل: MVC وما وراءه

تُعدّ Servlet الخام الأساس، لكن معظم تطبيقات Java للويب الحديثة تطبّق إطار عمل فوقها. إن DispatcherServlet في Spring MVC هو نفسه servlet واحد يستقبل كل طلب ويُفوّضه إلى طرق @Controller الخاصة بك. يُدير Jakarta Faces (JSF) دورة حياته من خلال FacesServlet. فهم Servlet يعني فهم ما تفعله تلك الأُطر عنك — وتستطيع تصحيح أخطائها حين تتسرّب التجريد.

الخلاصة

HTTP بروتوكول طلب/استجابة عديم الحالة. يعالج Tomcat (حاوية Servlet) طبقة الشبكة — تحليل بايتات HTTP وإدارة الخيوط وتعيين URLs — حتى يتركّز كودك بالكامل على منطق التطبيق. Servlet هي فئة Java تنضمّ إلى هذه الحاوية عبر مواصفة Jakarta Servlet؛ تمدّ HttpServlet وتتجاوز معالج طريقة HTTP المناسب وتقرأ من HttpServletRequest وتكتب في HttpServletResponse. لأن نسخة servlet واحدة تخدم خيوطًا متزامنة كثيرة، يجب ألّا تخزّن الحالة الخاصة بكل طلب في حقول النسخة. بهذا النموذج الذهني جاهزًا، أنت مستعد لإعداد مشروع Tomcat حقيقي وكتابة أول servlet عملية لك في الدرس القادم.