تعمل هذه المقالة على تحسين التقنية الموضحة أعلاه باستخدام لغة التعبير JSP 2.0 (EL)، مما يسمح لصفحات JSP بتخصيص المحتوى المخزن مؤقتًا لكل طلب ومستخدم. يمكن أن تحتوي أجزاء صفحة ذاكرة التخزين المؤقت على تعبيرات JSP لا يتم تقييمها بواسطة حاوية JSP، ويتم تحديد قيم هذه التعبيرات بواسطة علامات مخصصة في كل مرة يتم فيها تنفيذ الصفحة. لذلك، تم تحسين إنشاء المحتوى الديناميكي، ولكن الأجزاء المخزنة مؤقتًا يمكن أن تحتوي على أجزاء من المحتوى الذي تم إنشاؤه بواسطة كل طلب باستخدام لغة تعبير JSP الأصلية. بمساعدة JSP 2.0 EL API، يمكن لمطوري Java جعل ذلك ممكنًا باستخدام لغات التعبير.
التخزين المؤقت للمحتوى مقابل التخزين المؤقت للبيانات
لا يعد التخزين المؤقت للمحتوى هو الخيار الوحيد. على سبيل المثال، يمكن أيضًا تخزين البيانات المستخرجة من قاعدة البيانات مؤقتًا. في الواقع، قد يكون التخزين المؤقت للبيانات أكثر كفاءة نظرًا لأن المعلومات المخزنة لا تحتوي على علامات HTML وتتطلب ذاكرة أقل. ومع ذلك، في كثير من الحالات، يكون تنفيذ التخزين المؤقت في الذاكرة أسهل. افترض أنه في حالة معينة، يتكون التطبيق من عدد كبير من كائنات المعاملات، ويحتل موارد مهمة لوحدة المعالجة المركزية، ويقوم بإنشاء بيانات معقدة، ويستخدم صفحات JSP لتقديم البيانات. كان كل شيء يسير على ما يرام حتى يوم واحد زاد الحمل على الخادم فجأة وكان هناك حاجة إلى حل طارئ. في هذا الوقت، يعد إنشاء طبقة ذاكرة تخزين مؤقت بين كائن المعاملة وطبقة العرض حلاً جيدًا وفعالاً للغاية. لكن صفحات JSP التي تخزن المحتوى الديناميكي مؤقتًا يجب تعديلها بسرعة وسلاسة كبيرة. بالنسبة إلى التحرير البسيط لصفحة JSP، تتطلب التغييرات في منطق أعمال التطبيق عادةً المزيد من العمل والاختبار؛ بالإضافة إلى ذلك، إذا قامت الصفحة بتجميع المعلومات من مصادر مركبة متعددة، فإن طبقة الويب تحتاج فقط إلى قدر صغير من التغيير. تكمن المشكلة في ضرورة تحرير مساحة ذاكرة التخزين المؤقت عندما تصبح المعلومات المخزنة مؤقتًا قديمة، ويجب أن يعرف كائن المعاملة متى يحدث ذلك. ومع ذلك، عند اختيار تنفيذ التخزين المؤقت للمحتوى أو التخزين المؤقت للبيانات أو تقنيات التحسين الأخرى، هناك العديد من العوامل التي يجب أخذها في الاعتبار، وفي بعض الأحيان تكون هناك متطلبات خاصة للبرنامج الذي يتم تطويره. التخزين المؤقت للبيانات والتخزين المؤقت للمحتوى لا يستبعدان بالضرورة بعضهما البعض، بل يمكن استخدامهما معًا. على سبيل المثال، في تطبيق يعتمد على قاعدة البيانات، يتم تخزين البيانات المستخرجة من قاعدة البيانات وHTML الذي يقدم البيانات بشكل منفصل. يشبه هذا إلى حد ما استخدام JSP لإنشاء قوالب في الوقت الفعلي. توضح تقنيات API المستندة إلى EL والتي تمت مناقشتها في هذه المقالة كيفية استخدام JSP EL لتحميل البيانات في قوالب العرض.
تخزين المحتوى الديناميكي مؤقتًا باستخدام متغيرات JSP
عندما تقوم بتنفيذ آلية تخزين مؤقت، فإنك تحتاج إلى طريقة لتخزين كائنات ذاكرة التخزين المؤقت. في هذه المقالة، يتم تضمين كائنات من النوع String. أحد الخيارات هو استخدام إطار عمل التخزين المؤقت للكائنات، أو استخدام خرائط Java لتنفيذ نظام تخزين مؤقت مخصص. يحتوي JSP بالفعل على ما يسمى "السمات المحددة النطاق" أو "متغيرات JSP" لتوفير تعيين كائن المعرف، وهو ما تتطلبه آلية التخزين المؤقت. لاستخدام نطاق الصفحة أو الطلب، لا يكون هذا منطقيًا، بينما في نطاق التطبيق يعد هذا مكانًا جيدًا لتخزين المحتوى المخزن مؤقتًا نظرًا لأنه مشترك بين جميع المستخدمين والصفحات. يمكن أيضًا استخدام نطاق الجلسة عند الحاجة إلى تخزين مؤقت منفصل لكل مستخدم، لكن هذا ليس فعالاً للغاية. يمكن استخدام مكتبة علامات JSTL مع ذلك لتخزين المحتوى مؤقتًا، وذلك باستخدام متغيرات JSP كما هو موضح في المثال التالي:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${empty cachedFragment}"> <c:set var="cachedFragment" نطاق = "application"> ... </ج:مجموعة></ج:إذا> |
يقوم جزء الصفحة المخزنة مؤقتًا بإخراج النتائج باستخدام العبارة التالية:
${applicationScope.cachedFragment} |
ماذا يحدث عندما يلزم تخصيص جزء ذاكرة التخزين المؤقت لكل طلب؟
على سبيل المثال، إذا كنت تريد تضمين عداد، فأنت بحاجة إلى تخزين جزأين مؤقتًا:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${sessionScope.counter == null}"> <c:set var="counter"scope="session" value="0"/> </c:if><c:set var="counter" value="${counter+1}"scope="session"/> <c:if test="${empty cachedFragment1}"> <c:set var="cachedFragment1" نطاق = "application"> ... </c:set></c:if><c:if test="${فارغة ذاكرة التخزين المؤقت2}"> <c:set var = "cachedFragment2" نطاق = "التطبيق"> ... </ج:مجموعة></ج:إذا> |
يمكنك استخدام العبارة التالية لإخراج محتوى ذاكرة التخزين المؤقت:
${cachedFragment1} ${counter} ${cachedFragment2} |
بمساعدة مكتبة العلامات المخصصة، يصبح التخزين المؤقت لأجزاء الصفحة التي تتطلب التخصيص أمرًا سهلاً للغاية. كما ذكرنا سابقًا، يمكن تغليف محتوى ذاكرة التخزين المؤقت بعلامات البداية (<jc:cache>) وعلامات النهاية (</jc:cache>). يمكن التعبير عن كل تخصيص باستخدام علامة أخرى (<jc:dynamic expr="..."/>) لإخراج تعبير JSP (${...}). يتم تخزين المحتوى الديناميكي مؤقتًا باستخدام تعبيرات JSP ويتم تعيينه في كل مرة يتم فيها إصدار المحتوى المخزن مؤقتًا. يمكنك أن ترى كيف يتم تحقيق ذلك في القسم أدناه. يقوم Counter.jsp بتخزين جزء من الصفحة يحتوي على عداد، وسيزيد العداد تلقائيًا بمقدار 1 في كل مرة يقوم فيها المستخدم بتحديث الصفحة.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %> <c:if test="${sessionScope.counter == null}"> <c:set var="counter"scope="session" value="0"/> </c:if><c:set var="counter" value="${counter+1}"scope="session"/> <jc:cache id="cachedFragmentWithCounter"> ... <jc:dynamic expr="sessionScope.counter"/> ... </jc:cache> |
متغيرات JSP سهلة الاستخدام وهي حل جيد للتخزين المؤقت للمحتوى لتطبيقات الويب البسيطة. ومع ذلك، إذا كان التطبيق ينشئ الكثير من المحتوى الديناميكي، فإن عدم التحكم في حجم ذاكرة التخزين المؤقت يمثل مشكلة بالتأكيد. يمكن أن يوفر إطار عمل ذاكرة التخزين المؤقت المخصص حلاً أكثر قوة، مما يسمح بمراقبة ذاكرة التخزين المؤقت، والحد من حجم ذاكرة التخزين المؤقت، والتحكم في سياسة ذاكرة التخزين المؤقت، وما إلى ذلك...
استخدام حاوية JSP 2.0 Expression Language API
JSP (مثل Tomcat) لتطبيق تعبيرات EL API في JSP يتم تعيين قيم للصفحة ويمكن استخدامها بواسطة كود Java. وهذا يسمح بالتطوير خارج صفحات الويب باستخدام JSP EL، على سبيل المثال، على ملفات XML، والموارد المستندة إلى النص، والبرامج النصية المخصصة. تعد EL API مفيدة أيضًا عندما تحتاج إلى التحكم في وقت تعيين التعبيرات الموجودة في صفحة الويب أو التعبيرات المكتوبة المتعلقة بها. على سبيل المثال، يمكن أن تحتوي أجزاء الصفحة المخزنة مؤقتًا على تعبيرات JSP مخصصة، وسيتم استخدام EL API لتعيين هذه التعبيرات أو إعادة تقييمها في كل مرة يتم فيها إصدار المحتوى المخزن مؤقتًا.
توفر المقالة نموذجًا لبرنامج (راجع قسم الموارد في نهاية المقالة). يحتوي هذا التطبيق على فئة Java (JspUtils) وطريقة eval() في الفئة. تحتوي هذه الطريقة على ثلاث معلمات: تعبير JSP، النوع المتوقع للتعبير وكائن سياق JSP. تحصل طريقة Eval () على ExpressionEvaluator من سياق JSP وتستدعي طريقة التقييم () وتمرير التعبير ونوع التعبير المتوقع والمتغير الذي تم الحصول عليه من نص JSP. يقوم الأسلوب JspUtils.eval() بإرجاع قيمة التعبير.
الحزمة com.devsphere.articles.jspcache; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.el.ELException; import javax.servlet.jsp.el.ExpressionEvaluator; import java.io.IOException;public class JspUtils { تقييم الكائن الثابت العام (سلسلة expr، نوع الفئة، JspContext jspContext) throwsJspException { يحاول { if (expr.indexOf("${") == -1) يُرجع expr; ExpressionEvaluator evaluator= jspContext.getExpressionEvaluator(); تقييم الإرجاع. تقييم (expr، type، jspContext.getVariableResolver(), null); } قبض (ELEException ه) { رمي JspException (e) جديد ؛ } } ... } |
ملاحظة: يقوم JspUtils.eval() بشكل أساسي بتغليف ExpressionEvaluator القياسي. إذا لم يحتوي expr على ${، فلن يتم استدعاء JSP EL API لأنه لا يوجد تعبير JSP.
إنشاء ملف واصف مكتبة العلامات (TLD)
تتطلب مكتبة علامات JSP ملف واصف مكتبة العلامات (TLD) لتخصيص تسمية العلامات وسماتها وفئات Java التي تعمل على العلامة. يصف jspcache.tld علامتين مخصصتين. يحتوي <jc:cache> على سمتين: معرف جزء الصفحة المخزنة مؤقتًا ونطاق JSP — نطاق محتوى صفحة JSP التي يجب تخزينها دائمًا. يحتوي <jc:dynamic> على سمة واحدة فقط، وهي أنه يجب تعيين تعبير JSP في كل مرة يتم فيها إخراج جزء ذاكرة التخزين المؤقت. يقوم ملف TLD بتعيين هاتين العلامتين المخصصتين إلى فئتي CacheTag وDynamicTag كما يلي:
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" version="2.0"> <إصدار tlib>1.0</إصدار tlib> <اسم قصير>jc</اسم قصير> <uri>http://devsphere.com/articles/jspcache</uri> <العلامة> <الاسم>ذاكرة التخزين المؤقت>/الاسم> <tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class> <محتوى الجسم>بدون نص</محتوى الجسم> <السمة> <الاسم>المعرف</الاسم> <مطلوب>صحيح</مطلوب> <rtexprvalue>صحيح</rtexprvalue> </سمة> <سمة> <الاسم>النطاق</الاسم> <مطلوب>خطأ</مطلوب> <rtexprvalue>خطأ</rtexprvalue> </سمة> </tag> <tag> <اسم>ديناميكي</اسم> <tag-class> com.devsphere.articles.jspcache.DynamicTag</tag-class> <محتوى الجسم>فارغ</محتوى الجسم> <السمة> <الاسم>expr</الاسم> <مطلوب>صحيح</مطلوب> <rtexprvalue>خطأ</rtexprvalue> </سمة> </علامة></taglib> |
يتم تضمين ملف TLD في ملف واصف تطبيق الويب (web.xml). تحتوي هذه الملفات الخمسة أيضًا على معلمة أولية تشير إلى ما إذا كانت ذاكرة التخزين المؤقت متاحة أم لا.
<?xml version="1.0" ترميز="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd" الإصدار = "2.4"> <context-param> <param-name> com.devsphere.articles.jspcache.enabled</param-name> <قيمة بارام>صحيح</قيمة بارام> </سياق-بارام> <jsp-config> <taglib> <taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri> <taglib-location>/WEB-INF/jspcache.tld</taglib-location> </taglib> </jsp-config></web-app> |
فهم آلية عمل <jc:cache>.
تقوم حاوية JSP بإنشاء مثيل CacheTag لكل علامة <jc:cache> في صفحة JSP لمعالجتها. حاوية JSP مسؤولة عن استدعاء أساليب setJsp() وsetParent() وsetJspBody()، والتي ترثها فئة CacheTag من SimpleTagSupport. تستدعي حاوية JSP أيضًا طريقة الضبط لكل سمة من سمات العلامة التي تم التعامل معها. تقوم طريقتا SetId() وsetScope() بتخزين قيمة السمة في حقل خاص. تمت تهيئة هذه القيمة بالقيمة الافتراضية باستخدام مُنشئ CacheTag().
الحزمة com.devsphere.articles.jspcache; import javax.servlet.ServletContext; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException;import java.io.StringWriter; تمتد فئة CacheTag العامة إلى SimpleTagSupport { السلسلة النهائية العامة الثابتة CACHE_ENABLED = "com.devsphere.articles.jspcache.enabled"; معرف السلسلة الخاصة؛ نطاق int الخاص؛ تم تمكين ذاكرة التخزين المؤقت المنطقية الخاصة؛ { معرف = نطاق فارغ = PageContext.APPLICATION_SCOPE؛ }معرف مجموعة الفراغ العام (معرف السلسلة) { this.id = id; } نطاق الفراغ العام (نطاق السلسلة) { this.scope = JspUtils.checkScope(scope); } ... } يستدعي الأسلوب setScope() JspUtils.checkScope() للتحقق من قيمة خاصية النطاق الذي تم تحويله من نوع String إلى النوع int. ...الفئة العامة JspUtils { ... نطاق الفحص الثابت العام (نطاق السلسلة) { إذا ("الصفحة".equalsIgnoreCase(النطاق)) إرجاع PageContext.PAGE_SCOPE؛ وإلا إذا ("طلب".equalsIgnoreCase(نطاق)) إرجاع PageContext.REQUEST_SCOPE؛ وإلا إذا ("الجلسة".equalsIgnoreCase(النطاق)) إرجاع PageContext.SESSION_SCOPE؛ وإلا إذا ("التطبيق".equalsIgnoreCase(النطاق)) إرجاع PageContext.APPLICATION_SCOPE؛ وإلا قم برمي IllegalArgumentException جديد ("النطاق غير صالح:" + النطاق }})؛ |
بمجرد أن يصبح مثيل CacheTag جاهزًا للعمل على العلامة، تستدعي حاوية JSP طريقة doTag() وتحصل على سياق JSP باستخدام getJspContext(). تم تحويل هذا الكائن إلى PageContext، بحيث يمكن استدعاء طريقة getServletContext(). يتم استخدام سياق servlet للحصول على قيمة معلمة التهيئة، والتي تشير إلى ما إذا كانت آلية التخزين المؤقت ممكّنة أم لا. إذا تم تمكين التخزين المؤقت، فسيحاول doTag() الحصول على جزء الصفحة المخزنة مؤقتًا باستخدام قيم سمات المعرف والنطاق. إذا لم يتم تخزين جزء الصفحة مؤقتًا، فإن doTag() يستخدم getJspBody().invoc() لتنفيذ كود JSP المغلف بواسطة <jc:cache> و</jc:cache>. يتم تخزين المخرجات التي تم إنشاؤها بواسطة نص JSP مؤقتًا في StringWriter ويتم استردادها بواسطة طريقة toStirng (). بهذه الطريقة، تستدعي doTag() طريقة setAttribute() لسياق JSP لإنشاء متغير JSP جديد يتحكم هذا المتغير في محتوى ذاكرة التخزين المؤقت التي قد تحتوي على تعبيرات JSP (${…}). يتم تعيين هذه التعبيرات بواسطة JspUtils.eval() قبل إخراج المحتوى باستخدام jspContext.getOut().print(). يحدث هذا السلوك فقط عند تمكين التخزين المؤقت. بخلاف ذلك، تقوم doTag() فقط بتنفيذ نص JSP من خلال getJspBody().invoc(null) ولا يتم تخزين نتيجة الإخراج مؤقتًا.
... تمتد فئة CacheTag العامة إلى SimpleTagSupport { ... public void doTag() يلقي JspException، IOException { JspContext jspContext = getJspContext(); تطبيق ServletContext = ((PageContext) jspContext).getServletContext(); StringacheEnabledParam= application.getInitParameter(CACHE_ENABLED); CacheEnabled = CacheEnabledParam != null && CacheEnabledParam.equals("true"); إذا (تم تمكين ذاكرة التخزين المؤقت) { String cachedOutput= (String) jspContext.getAttribute(id,scope); إذا (cachedOutput == فارغ) { StringWriter buffer = new StringWriter(); getJspBody().invoc(buffer); cachedOutput = buffer.toString(); jspContext.setAttribute(id,cachedOutput,scope); } سلسلة تقييم الإخراج = (سلسلة) JspUtils.eval(cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } else getJspBody().invoc(null); } ... } |
لاحظ أن استدعاء JspUtils.eval() واحد يقوم بتقييم جميع تعبيرات ${...} . لأن النص الذي يحتوي على عدد كبير من بنيات ${...} هو أيضًا تعبير. يمكن معالجة كل جزء من ذاكرة التخزين المؤقت كتعبير JSP معقد. تقوم طريقة IsCacheEnabled () بإرجاع قيمة ذاكرة التخزين المؤقت التي تمت تهيئتها بواسطة doTag ().
تقوم طريقة IsCacheEnabled () بإرجاع قيمة ذاكرة التخزين المؤقت التي تمت تهيئتها بواسطة doTag (). ...تمتد فئة CacheTag العامة إلى SimpleTagSupport { ... public boolean isCacheEnabled() { return CacheEnabled; } } |
تسمح العلامة <jc:cache> لمطوري الصفحة باختيار معرف أجزاء الصفحة المخزنة مؤقتًا. يسمح هذا بمشاركة أجزاء الصفحة المخزنة مؤقتًا بواسطة صفحات JSP متعددة، وهو أمر مفيد عند إعادة استخدام كود JSP. ولكن لا تزال هناك حاجة إلى بعض بروتوكولات التسمية لتجنب التعارضات المحتملة. يمكن تجنب هذا التأثير الجانبي عن طريق تعديل فئة CacheTag لتضمين عنوان URL داخل المعرف التلقائي.
فهم ما يفعله <jc:dynamic>
تتم معالجة كل <jc:dynamic> بواسطة مثيل لفئة DynamicTag، ويقوم الأسلوب setExpr() بتخزين قيمة السمة expr في حقل خاص. تقوم طريقة DoTag() بإنشاء تعبير JSP وتضيف البادئة ${ واللاحقة } إلى قيمة السمة expr. بعد ذلك، يستخدم doTag() findAncestorWithClass() للعثور على معالج CacheTag الخاص بـ <jc:cache> الذي يحتوي على عنصر العلامة <jc:dynamic>. إذا لم يتم العثور عليه أو تم تعطيل التخزين المؤقت، فسيتم تقييم تعبير JSP باستخدام JspUtils.eval() ويتم إخراج القيمة. بخلاف ذلك، ستُخرج الدالة doTag() تعبيرًا لا قيمة له.
الحزمة com.devsphere.articles.jspcache; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; يمتد DynamicTag العام إلى SimpleTagSupport { سلسلة خاصة expr؛ مجموعة الفراغات العامة Expr(String expr) { this.expr = expr; } public void doTag() يلقي JspException، IOException { إخراج السلسلة = "${" + expr + "}"; سلف CacheTag = (CacheTag) findAncestorWithClass (هذا، CacheTag.class)؛ إذا (السلف == فارغ || !ancestor.isCacheEnabled()) الإخراج = (سلسلة)JspUtils.eval (الإخراج، String.class، getJspContext())؛ getJspContext().getOut().print(output); } } |
من خلال تحليل الكود أعلاه، يمكنك ملاحظة أن <jc:cache> و <jc:dynamic> يتعاونان لتحقيق حل فعال قدر الإمكان. إذا كانت ذاكرة التخزين المؤقت متاحة، فسيتم وضع جزء الصفحة في المخزن المؤقت مع تعبير JSP الذي تم إنشاؤه بواسطة <jc:dynamic> وتعيين قيمة CacheTag. إذا تم تعطيل التخزين المؤقت، يصبح التخزين المؤقت بلا معنى ويقوم <jc:cache> ببساطة بتنفيذ الجزء الأساسي الخاص بـ JSP، تاركًا DynamicTag لتعيين قيم لتعبير JSP. يعد تعطيل التخزين المؤقت ضروريًا في بعض الأحيان، خاصة أثناء عملية التطوير عندما يتغير المحتوى ويتم إعادة ترجمة صفحات JSP. بالطبع، يجب تمكين التخزين المؤقت في بيئة الإنتاج بعد التطوير.
يعد التخزينالمؤقت للمحتوى
طريقة سهلة الاستخدام للغاية لتحسين أداء تطبيقات الويب. تركز هذه المقالة على استخدام لغة تعبير JSP لتخصيص محتوى ذاكرة التخزين المؤقت لكل مستخدم أو طلب. تعد مكتبات العلامات التي تم تقديمها باختصار خلال هذه المقالة مناسبة لتطبيقات الويب الصغيرة ويمكنها تحسين أداء التطبيقات متوسطة الحجم. لتطوير تطبيقات واسعة النطاق على مستوى المؤسسة، يجب عليك التفكير في استخدام إطار عمل يدعم آليات تخزين مؤقت أفضل، بدلاً من استخدام متغيرات JSP فقط.