В этой статье описанный выше метод усовершенствован за счет использования языка выражений 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="${пустой кэшированный фрагмент}"> <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="${emptycachedFragment1}"> <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/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 API языка выражений JSP 2.0
(например, Tomcat) для применения выражений API EL в JSP. странице присваиваются значения и могут использоваться кодом 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 { общедоступная статическая оценка объекта (строковое выражение, тип класса, JspContext jspContext) бросаетJspException { пытаться { if (expr.indexOf("${") == -1) return expr; ExpressionEvaluator evaluator = jspContext.getExpressionEvaluator(); return evaluator.evaluate(выражение, тип, jspContext.getVariableResolver(), ноль); } поймать (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 версия="1.0" кодировка="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 веб-jsptalibrary_2_0.xsd" version="2.0"> <tlib-версия>1.0</tlib-версия> <короткое имя>jc</короткое имя> <uri>http://devsphere.com/articles/jspcache</uri> <тег> <имя>кэш</имя> <тег-класс>com.devsphere.articles.jspcache.CacheTag</tag-class> <body-content>без скриптов</body-content> <атрибут> <имя>id</имя> <обязательно>true</обязательно> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <имя>область</имя> <обязательно>false</обязательно> <rtexprvalue>false</rtexprvalue> </attribute> </тег> <тег> <имя>динамический</имя> <класс тега> com.devsphere.articles.jspcache.DynamicTag</tag-class> <body-content>пусто</body-content> <атрибут> <имя>выражение</имя> <обязательно>true</обязательно> <rtexprvalue>false</rtexprvalue> </attribute> </тег></taglib> |
Файл TLD включен в файл дескриптора веб-приложения (web.xml). Эти пять файлов также содержат начальный параметр, указывающий, доступен ли кэш.
<?xml version="1.0"coding="ISO-8859-1"?> <веб-приложение 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" version="2.4"> <контекст-параметр> <имя-параметра> com.devsphere.articles.jspcache.enabled</имя-параметра> <значение-параметра>истина</значение-параметра> </параметр-контекста> <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; импортировать 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 (идентификатор строки) { 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(); Строка cacheEnabledParam = application.getInitParameter(CACHE_ENABLED); кэшенаблед = кэшенабледпарам! = ноль && кэшEnabledParam.equals("истина"); если (кэш включен) { StringcachedOutput= (String) jspContext.getAttribute(id, область); если (cachedOutput == ноль) { Буфер StringWriter = новый StringWriter(); getJspBody().invoke(буфер); кэшированныйВыход = buffer.toString(); jspContext.setAttribute(id, кэшированныйВыход, область действия); } Строка оцененаВывод = (Строка) JspUtils.eval(cachedOutput, String.class, jspContext); jspContext.getOut().print(evaluatedOutput); } еще getJspBody().invoke(null); } ... } |
Обратите внимание, что один вызов JspUtils.eval() оценивает все выражения ${…}. Потому что текст, содержащий большое количество структур ${...}, тоже является выражением. Каждый фрагмент кэша может обрабатываться как сложное выражение JSP. Метод IsCacheEnabled() возвращает значение параметра cacheEnabled, который был инициализирован функцией doTag().
Метод IsCacheEnabled() возвращает значение параметра cacheEnabled, который был инициализирован функцией doTag(). ...публичный класс CacheTag расширяет SimpleTagSupport { ... общественное логическое значение isCacheEnabled () {returncacheEnabled; } } |
Тег <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 для настройки содержимого кэша для каждого пользователя или запроса. Библиотеки тегов, кратко представленные в этой статье, подходят для небольших веб-приложений и могут повысить производительность приложений среднего размера. Для разработки крупномасштабных приложений корпоративного уровня вам следует рассмотреть возможность использования инфраструктуры, которая поддерживает лучшие механизмы кэширования, а не просто использование переменных JSP.