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

ServletContext ومعاملات التهيئة

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

ServletContext ومعاملات التهيئة

يعمل كل تطبيق قائم على سيرفلت داخل سياق تطبيق ويب واحد — بيئة تشغيل مشتركة تديرها حاوية السيرفلت. الكائن الذي يمثّل هذه البيئة هو ServletContext. إنّ فهمه هو مفتاح ربط إعدادات التطبيق على مستوى كامل، ومشاركة الكائنات بين السيرفلتات، وتجنّب الاقتران الضيّق الناتج عن تضمين القيم مباشرةً داخل فئات السيرفلت.

ما هو ServletContext؟

عندما تشغّل الحاوية (Tomcat أو Jetty أو WildFly وغيرها) تطبيق الويب الخاص بك، تُنشئ نسخة واحدة فقط من ServletContext لذلك التطبيق. تشترك جميع السيرفلتات والفلاتر والمستمعين في ملف .war ذاته في هذه النسخة الواحدة. فكّر فيها على أنها الذاكرة العامة ومخزن الإعدادات للتطبيق.

يمكنك الحصول عليها من داخل أي سيرفلت بـ:

ServletContext ctx = getServletContext(); // داخل HttpServlet

أو من خلال كائن ServletConfig:

ServletContext ctx = getServletConfig().getServletContext();
سياق واحد لكل تطبيق، وتطبيق واحد لكل وحدة نشر. إذا نشرت ملفَّي .war، فكل منهما يحصل على ServletContext خاص به ولا يمكنه رؤية سمات الآخر مباشرةً. هذا حدٌّ عزل مقصود.

معاملات تهيئة السياق

معاملات تهيئة السياق هي أزواج اسم-قيمة مُعلَنة في web.xml (أو ما يُعادله القائم على التعليقات التوضيحية) وتكون مرئية لكل سيرفلت وفلتر في التطبيق. هي المكان المناسب لإعدادات التطبيق الشاملة التي لا ينبغي تضمينها بشكل ثابت — عناوين URL لقواعد البيانات، وعناوين واجهات API، وأعلام الميزات، وأسماء البيئات.

أعلن عنها في web.xml:

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.0"> <context-param> <param-name>app.env</param-name> <param-value>production</param-value> </context-param> <context-param> <param-name>db.url</param-name> <param-value>jdbc:postgresql://db-host:5432/shop</param-value> </context-param> </web-app>

اقرأها من أي سيرفلت:

import jakarta.servlet.ServletContext; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/info") public class InfoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { ServletContext ctx = getServletContext(); String env = ctx.getInitParameter("app.env"); // "production" String dbUrl = ctx.getInitParameter("db.url"); resp.setContentType("text/plain"); resp.getWriter().printf("ENV=%s DB=%s%n", env, dbUrl); } }

تُعيد getInitParameter(String name) القيمة null إذا لم يكن الاسم موجودًا، لذا احرص دائمًا على الحماية من ذلك في كود الإنتاج.

معاملات تهيئة السيرفلت

أحيانًا يكون المعامل منطقيًا فقط لسيرفلت واحد بعينه — مهلة خاصة بسيرفلت، أو مسار قالب، أو حجم صفحة. أعلن عن هذه المعاملات كـ معاملات تهيئة سيرفلت، إمّا في web.xml داخل عنصر <servlet> أو عبر سمة initParams لتعليق @WebServlet.

باستخدام التعليق التوضيحي (Jakarta EE 9+):

import jakarta.servlet.annotation.WebInitParam; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet( urlPatterns = "/products", initParams = { @WebInitParam(name = "pageSize", value = "25"), @WebInitParam(name = "sortDefault", value = "name") } ) public class ProductListServlet extends HttpServlet { private int pageSize; private String sortDefault; @Override public void init() { pageSize = Integer.parseInt(getInitParameter("pageSize")); sortDefault = getInitParameter("sortDefault"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.getWriter().printf( "Listing products — page size: %d, default sort: %s%n", pageSize, sortDefault ); } }

لاحظ النمط: اقرأ معاملات التهيئة مرة واحدة في init() وخزّنها في حقول نسخة. استدعاء getInitParameter() في كل طلب مسموح به لكنه مُكلف؛ فالقيم لا تتغير بعد النشر.

فضّل استخدام init() للإعداد لمرة واحدة. حلّل معاملات التهيئة وتحقّق منها وخزّنها مؤقتًا هناك. إذا كان معامل مطلوب مفقودًا أو مشوّهًا، ارمِ ServletException — ستمتنع الحاوية عن تشغيل السيرفلت، مما يجعل الإعداد الخاطئ مرئيًا فورًا بدلًا من الانهيار الصامت وقت الطلب.

سمات السياق — مشاركة الكائنات الحية

تحمل معاملات التهيئة قيم String فقط مقروءة من ملفات الإعداد. لمشاركة كائنات تطبيق حية بين السيرفلتات (تجمّع اتصالات، أو سجل خدمات، أو كائن إعدادات محلَّل)، استخدم سمات السياق. هذه مراجع كائنات Object اعتباطية مخزنة تحت مفتاح نصي.

// تُخزَّن مرة واحدة — عادةً في ServletContextListener import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; import jakarta.servlet.annotation.WebListener; import javax.sql.DataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @WebListener public class AppStartupListener implements ServletContextListener { private HikariDataSource pool; @Override public void contextInitialized(ServletContextEvent sce) { HikariConfig cfg = new HikariConfig(); cfg.setJdbcUrl(sce.getServletContext().getInitParameter("db.url")); cfg.setMaximumPoolSize(10); pool = new HikariDataSource(cfg); // شاركه مع كل سيرفلت sce.getServletContext().setAttribute("dataSource", pool); } @Override public void contextDestroyed(ServletContextEvent sce) { if (pool != null) pool.close(); // حرّر الاتصالات عند الإغلاق } }

يمكن لأي سيرفلت عندئذٍ استرداد التجمّع المشترك:

DataSource ds = (DataSource) getServletContext().getAttribute("dataSource"); try (Connection conn = ds.getConnection()) { // ... }
سمات السياق مشتركة بين جميع الخيوط. كل طلب متزامن يعمل في خيط مختلف لكنها جميعًا تقرأ نفس ServletContext. خزّن كائنات آمنة للخيوط (thread-safe) أو ثابتة (immutable) أو مزامَنة بالكامل كسمات سياق فقط — DataSource آمن؛ أما ArrayList العادية فلا.

مقارنة سمات التهيئة — دليل القرار

  • معامل تهيئة السياق (<context-param>): إعدادات مشتركة على مستوى التطبيق — عنوان URL لقاعدة البيانات، اسم البيئة، اللغة الافتراضية.
  • معامل تهيئة السيرفلت (@WebInitParam / <init-param>): إعدادات خاصة بسيرفلت واحد وغير ذات صلة بالآخرين — حجم الصفحة، اسم القالب، المهلة.
  • سمة السياق (setAttribute): كائنات حية تحتاج إلى مشاركة بين السيرفلتات في وقت التشغيل — تجمّعات الاتصالات، ونسخ الخدمات، والذاكرات المؤقتة على مستوى التطبيق.

التسجيل العملي مع ServletContext

يعرض ServletContext أيضًا طريقة تسجيل أساسية تكتب في ملف سجل الحاوية — مفيدة لتشخيص بدء التشغيل:

@Override public void init() throws jakarta.servlet.ServletException { String env = getServletContext().getInitParameter("app.env"); getServletContext().log("ProductListServlet starting in env: " + env); }

في تطبيقات الإنتاج ستستبدل هذا بإطار تسجيل مناسب (SLF4J مع Logback)، لكنه خلال التطوير طريقة سريعة للتأكد من أن المعاملات تُقرأ بشكل صحيح.

الخلاصة

ServletContext هو البيئة المشتركة على مستوى التطبيق — نسخة واحدة لكل ملف .war منشور. استخدم معاملات تهيئة السياق (<context-param>) للإعدادات النصية الشاملة للتطبيق، ومعاملات تهيئة السيرفلت (@WebInitParam) للإعدادات الخاصة بكل سيرفلت، وسمات السياق (setAttribute / getAttribute) لمشاركة الكائنات الحية الآمنة للخيوط بين السيرفلتات. قراءة معاملات التهيئة مرة واحدة في init() وتخزينها في الحقول هو النمط الموحّد والفعّال. في الدرس التالي ستستخدم توجيه الطلبات والتضمين لتأليف استجابات السيرفلت.