Подробное объяснение кэширования динамического контента в JSP 2.0.
Автор:Eve Cole
Время обновления:2009-07-03 16:56:34
Кэширование контента — один из наиболее распространенных методов оптимизации веб-приложений, и его можно легко реализовать. Например, вы можете использовать собственный тег JSP — назовем его <jc:cache> — с <jc:cache> и </jc:cache>, чтобы инкапсулировать каждый фрагмент страницы, который необходимо кэшировать. Любой пользовательский тег может контролировать, когда выполняется содержащаяся в нем часть (то есть предварительно упакованный фрагмент страницы), и можно фиксировать результаты динамического вывода. Тег <jc:cache> позволяет контейнеру JSP (например, Tomcat) генерировать контент только один раз в виде переменных JSP всего приложения для хранения каждого фрагмента кэша. Каждый раз, когда страница JSP выполняется, пользовательский тег загружает фрагмент кэшированной страницы без необходимости повторного выполнения кода JSP для генерации выходных данных. В рамках Джакартского проекта с использованием этой технологии была разработана библиотека тегов. Это хорошо работает, когда кэшированный контент не нужно настраивать для каждого пользователя или запроса.
В этой статье описанный выше метод усовершенствован за счет использования языка выражений 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"scope="application">
...
</c:set></c:if>
Фрагмент кэшированной страницы выводит результаты с помощью следующего оператора:
${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="${emptycacheFragment1}">
<c:set var="cachedFragment1"scope="application">
...
</c:set></c:if><c:if test="${emptycachedFragment2}">
<c:set var="cachedFragment2"scope="application">
...
</c:set></c:if>
Вы можете использовать следующий оператор для вывода содержимого кэша:
${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/ статьи/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 просты в использовании и являются хорошим решением для кэширования контента для простых веб-приложений. Однако если приложение генерирует много динамического контента, отсутствие контроля над размером кэша определенно является проблемой. Выделенная платформа кэширования может предоставить более мощное решение, позволяющее осуществлять мониторинг кэша, ограничивать его размер, контролировать политику кэширования и т. д.
Использование API языка выражений JSP 2.0
Контейнеры JSP (например, Tomcat) оценивают выражения на страницах JSP с помощью API EL и могут использоваться кодом Java. Это позволяет осуществлять разработку за пределами веб-страниц с использованием JSP EL, например, для файлов XML, текстовых ресурсов и пользовательских сценариев. API EL также полезен, когда вам нужно контролировать, когда выражениям на веб-странице назначаются или записываются связанные с ними выражения. Например, фрагменты кэшированных страниц могут содержать пользовательские выражения JSP, а API EL будет использоваться для назначения или повторной оценки этих выражений каждый раз, когда создается кэшированный контент.
В статье представлен пример программы (см. раздел ресурсов в конце статьи). Это приложение содержит класс Java (JspUtils) и метод eval() в классе. Этот метод имеет три параметра: выражение JSP, ожидаемый тип. выражения и объекта контекста JSP. Метод Eval() получает ExpressionEvaluator из контекста JSP и вызывает метод Assessment(), передавая выражение, ожидаемый тип выражения и переменную, полученную из контекста JSP. Метод JspUtils.eval() возвращает значение выражения.
пакет com.devsphere.articles.jspcache;
импортировать javax.servlet.jsp.JspContext;
импортировать javax.servlet.jsp.JspException;
импортировать javax.servlet.jsp.PageContext;
импортировать javax.servlet.jsp.el.ELException;
импортировать javax.servlet.jsp.el.ExpressionEvaluator;
импортировать java.io.IOException; публичный класс JspUtils {
общедоступный статический объект eval(
Строковое выражение, тип класса, JspContext jspContext)
выдает JspException {
пытаться {
если (expr.indexOf("${") == -1)
вернуть выражение;
Оценщик ExpressionEvaluator
= jspContext.getExpressionEvaluator();
return evaluator.evaluate(выражение, тип,
jspContext.getVariableResolver(), ноль);
} catch (ELException e) {
выбросить новое JspException(e);
}
}
...}
Примечание. JspUtils.eval() в основном инкапсулирует стандартный ExpressionEvaluator. Если expr не содержит ${, API JSP EL не вызывается, поскольку выражение JSP отсутствует.
Создание файла дескриптора библиотеки тегов (TLD) Библиотеке тегов JSP требуется файл дескриптора библиотеки тегов (TLD) для настройки именования тегов, их атрибутов и классов Java, которые работают с тегом. jspcache.tld описывает два пользовательских тега. <jc:cache> имеет два атрибута: идентификатор фрагмента кэшированной страницы и область действия JSP — область содержимого страницы JSP, которую всегда необходимо сохранять. <jc:dynamic> имеет только один атрибут — выражение JSP, которое должно вычисляться каждый раз при выводе фрагмента кэша. Файл TLD сопоставляет эти два пользовательских тега с классами CacheTag и DynamicTag следующим образом:
<?xml version="1.0"coding="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"
версия="2.0">
<tlib-версия>1.0</tlib-версия>
<short-name>jc</short-name>
<uri>http://devsphere.com/articles/jspcache</uri>
<тег>
<name>кэш</name>
<tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
<body-content>без скриптов</body-content>
<атрибут>
<имя>идентификатор</имя>
<required>истина</required>
<rtexprvalue>истина</rtexprvalue>
</атрибут>
<атрибут>
<name>область</name>
<required>ложь</required>
<rtexprvalue>ложь</rtexprvalue>
</атрибут>
</тег>
<тег>
<имя>динамический</имя>
<tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
<body-content>пусто</body-content>
<атрибут>
<name>выражение</name>
<required>истина</required>
<rtexprvalue>ложь</rtexprvalue>
</атрибут>
</tag></taglib>
Файл TLD включен в файл дескриптора веб-приложения (web.xml). Эти пять файлов также содержат начальный параметр, указывающий, доступен ли кэш.
<?xml version="1.0"coding="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">
<контекстный параметр>
<param-name>com.devsphere.articles.jspcache.enabled</param-name>
<param-value>истина</param-value>
</контекст-парам>
<jsp-конфигурация>
<taglib>
<taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri>
<taglib-location>/WEB-INF/jspcache.tld</taglib-location>
</taglib>
</jsp-config></веб-приложение>
Познакомьтесь с механизмом работы <jc:cache>. Контейнер JSP создает экземпляр CacheTag для каждого тега <jc:cache> на странице JSP для его обработки. JSP-контейнер отвечает за вызов методов setJsp(), setParent() и setJspBody(), которые представляют собой класс CacheTag, унаследованный от SimpleTagSupport. Контейнер JSP также вызывает метод установки для каждого атрибута манипулируемого тега. Методы SetId() и setScope() сохраняют значение атрибута в закрытом поле. Это значение было инициализировано значением по умолчанию с помощью конструктора CacheTag().
пакет com.devsphere.articles.jspcache;
импортировать javax.servlet.ServletContext;
импортировать javax.servlet.jsp.JspContext;
импортировать javax.servlet.jsp.JspException;
импортировать javax.servlet.jsp.PageContext;
импортировать javax.servlet.jsp.tagext.SimpleTagSupport;
импортировать java.io.IOException; импортировать java.io.StringWriter;
общественный класс CacheTag расширяет SimpleTagSupport {
общедоступная статическая финальная строка CACHE_ENABLED
= "com.devsphere.articles.jspcache.enabled";
частный строковый идентификатор;
частная область int;
частный логический кэшEnabled public CacheTag () {
идентификатор = ноль; область = PageContext.APPLICATION_SCOPE;
} Public void setId(String id) {
this.id = идентификатор;
} public void setScope (область действия строки) {
this.scope = JspUtils.checkScope(область);
}
...}
Метод setScope() вызывает JspUtils.checkScope() для проверки значения свойства области, преобразованной из типа String в тип int.
...публичный класс JspUtils {
...
public static int checkScope (область действия строки) {
if ("page".equalsIgnoreCase(область))
вернуть PageContext.PAGE_SCOPE;
иначе, если ("request".equalsIgnoreCase(область))
вернуть PageContext.REQUEST_SCOPE;
иначе, если ("session".equalsIgnoreCase(область))
вернуть PageContext.SESSION_SCOPE;
иначе, если ("application".equalsIgnoreCase(область))
вернуть PageContext.APPLICATION_SCOPE;
еще
выдать новое IllegalArgumentException(
"Неверная область: " + область);
}}
Как только экземпляр CacheTag готов работать с тегом, контейнер JSP вызывает метод doTag() и получает контекст JSP с помощью getJspContext(). Этот объект преобразуется в PageContext, поэтому можно вызвать метод getServletContext(). Контекст сервлета используется для получения значения параметра инициализации, который указывает, включен ли механизм кэширования. Если кэширование включено, doTag() пытается получить фрагмент кэшированной страницы, используя значения атрибутов id иscope. Если фрагмент страницы не был кэширован, doTag() использует getJspBody().invoke() для выполнения кода JSP, инкапсулированного с помощью <jc:cache> и </jc:cache>. Вывод, сгенерированный телом JSP, буферизуется в StringWriter и извлекается методом toStirng(). Таким образом, doTag() вызывает метод setAttribute() контекста JSP для создания новой переменной JSP. Эта переменная управляет содержимым кэша, которое может содержать выражения JSP (${…}). Эти выражения назначаются с помощью JspUtils.eval() перед выводом содержимого с помощью jspContext.getOut().print(). Такое поведение происходит только при включенном кэшировании. В противном случае doTag() просто выполняет тело JSP через getJspBody().invoke(null), и результат вывода не кэшируется.
...публичный класс CacheTag расширяет SimpleTagSupport {
...
public void doTag() выдает JspException, IOException {
JspContext jspContext = getJspContext();
Приложение ServletContext
= ((PageContext) jspContext).getServletContext();
Кэш строкEnabledParam
= application.getInitParameter(CACHE_ENABLED);
кэшенаблед = кэшенабледпарам! = ноль
&& кэшEnabledParam.equals("истина");
если (cacheEnabled) {
Кэшированный вывод строки
= (String) jspContext.getAttribute(id, область действия);
если (cachedOutput == ноль) {
Буфер StringWriter = новый StringWriter();
getJspBody().invoke(буфер);
кэшированныйВыход = buffer.toString();
jspContext.setAttribute(id, кэшированныйВыход, область действия);
} String AssessmentOutput = (String) JspUtils.eval(
кэшированныйВыход, String.class, jspContext);
jspContext.getOut().print(evaluatedOutput);
} еще
getJspBody().invoke(null);
}
...}
Обратите внимание, что один вызов JspUtils.eval() оценивает все выражения ${…}. Потому что текст, содержащий большое количество структур ${...}, тоже является выражением. Каждый фрагмент кэша может обрабатываться как сложное выражение JSP.
Метод IsCacheEnabled() возвращает значение параметра cacheEnabled, который был инициализирован функцией doTag().
...публичный класс CacheTag расширяет SimpleTagSupport {
... общедоступное логическое значение isCacheEnabled() {
вернуть кэш включен;
}}
Тег <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;
импортировать javax.servlet.jsp.JspException;
импортировать javax.servlet.jsp.tagext.SimpleTagSupport;
импортировать java.io.IOException;
публичный класс DynamicTag расширяет SimpleTagSupport {
частное строковое выражение;
public void setExpr(String expr) {
это.выражение = выражение;
} public void doTag() выдает JspException, IOException {
Строковый вывод = "${" + выражение + "}";
Предок CacheTag = (CacheTag) findAncestorWithClass(
это, CacheTag.class);
если (предок == null || !ancestor.isCacheEnabled())
вывод = (String) JspUtils.eval(
вывод, String.class, getJspContext());
getJspContext().getOut().print(выход);
}}
Анализируя приведенный выше код, вы можете заметить, что <jc:cache> и <jc:dynamic> взаимодействуют для достижения максимально эффективного решения. Если кэш доступен, фрагмент страницы помещается в буфер вместе с выражением JSP, сгенерированным <jc:dynamic>, и ему присваивается значение CacheTag. Если кэширование отключено, кэширование становится бессмысленным и <jc:cache> просто выполняет свою часть тела JSP, оставляя DynamicTag для присвоения значений выражению JSP. Отключение кэширования иногда необходимо, особенно в процессе разработки, когда содержимое меняется и страницы JSP перекомпилируются. Разумеется, после разработки кэширование необходимо включить в производственной среде.
Подвести итог
При разработке крупных приложений корпоративного уровня вам следует рассмотреть возможность использования инфраструктуры, которая поддерживает лучшие механизмы кэширования, а не просто использование переменных JSP. Но разобраться в технологии настройки на основе EL API, несомненно, полезно.