Статьи

Объясненные простые теги JSP 2.0

В JSP 2.0 появилось много новых вкусностей, многие из которых направлены на облегчение жизни разработчика. В этой статье я рассмотрю один из моих любимых: простые обработчики тегов. Простые обработчики тегов позволяют создавать собственные теги, которые превосходят решения на основе файлов тегов, и их гораздо проще писать, чем теги на основе предыдущего API пользовательских тегов.

Поскольку теперь есть несколько различных способов написания пользовательских тегов, я также предоставлю несколько советов о том, как решить, использовать ли простые обработчики тегов, файлы тегов и что теперь Sun Microsystems называет «классическими» тегами.

Давайте начнем с выяснения потенциального заблуждения. «Простой» в «обработчике простых тегов» относится к легкости, с которой такие пользовательские теги могут быть написаны, а не к каким-либо ограничениям, которые они имеют. Почти во всех случаях простые теги столь же эффективны, как и теги, написанные с использованием классического API тегов, единственное предостережение в том, что вы не можете включить код скриптлета в тело простого тега. Однако вы можете включать теги JSTL, выражения EL и другие настраиваемые действия, поэтому это должно редко, если вообще, создавать проблему.

Первые шаги

Простой обработчик тега подклассов поддерживает класс поддержки, называемый 'SimpleTagSupport' . Этот класс является очень удобной реализацией интерфейса 'SimpleTag' . Он предоставляет реализации всех 5 методов этого интерфейса, наиболее важным из которых является метод doTag() . Метод doTag() в SimpleTagSupport фактически ничего не делает — вы, разработчик, можете переопределить этот метод и кодировать функциональность вашего тега. Давайте погрузимся прямо в пример. Код ниже показывает этот метод в действии:

 package demo.tags;   import javax.servlet.jsp.tagext.*;  import javax.servlet.jsp.*;   public class Greeter extends SimpleTagSupport {     public void doTag() throws JspException {         PageContext pageContext = (PageContext) getJspContext();        JspWriter out = pageContext.getOut();         try {             out.println("Hello World");         } catch (Exception e) {            // Ignore.        }     }  } 

Здесь нет ничего особенного. Этот класс просто использует doTag() для вывода «Hello World» в выходной поток. Мы приведем этот пример в порядок, но пока есть несколько вещей, на которые следует обратить внимание.

Два оператора импорта дают нам доступ ко всем необходимым классам; для компиляции этого кода вам понадобятся классы Servlet и JSP API на вашем пути к классам. Пользователи Tomcat найдут их в каталоге Common / lib как jasper-api.jar и servlet-api.jar .

По причинам, которые только что обсуждались, этот класс расширяет класс SimpleTagSupport и ожидает, что мы переопределим метод doTag() . Другим следствием расширения класса SimpleTagSupport является то, что контейнер с doTag() setJspContext() был вызван контейнером до doTag() , что сделало текущую информацию о контексте JSP доступной через getJspContext() . Мы использовали этот метод, чтобы получить доступ к выходному потоку для JSP.

Отображение тегов на классы

Предполагая, что этот класс установлен в / WEB-INF / classes, следующим шагом будет написание файла TLD. TLD (дескриптор библиотеки тегов) — это файл XML, который контейнер использует для сопоставления пользовательских тегов в ваших JSP с соответствующими классами реализации простого обработчика тегов. Ниже мы видим demo.tld , простой файл TLD, который при установке в каталоге / WEB-INF / tlds сопоставляет пользовательский тег с именем 'greeter' с классом 'demo.tags.Greeter' .

 <?xml version="1.0" encoding="UTF-8"?>  <taglib version="2.0" 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">   <tlib-version>1.0</tlib-version>  <short-name>demo</short-name>  <uri>DemoTags</uri>   <tag>    <name>greeter</name>    <tag-class>demo.tags.Greeter</tag-class>    <body-content>empty</body-content>  </tag>   </taglib> 

'tlib-version' и 'short-name' достаточно просты и соотносятся с версией библиотеки тегов и префиксом тега по умолчанию соответственно. Элемент 'uri' , однако, заслуживает некоторого обсуждения. Когда он запускается, контейнер использует функцию автообнаружения для сопоставления всех значений элемента uri с соответствующим TLD; следовательно, эта строка должна быть уникальной в приложении. Начиная с JSP 1.2 нам больше не нужно вносить изменения в файл web.xml, чтобы развернуть пользовательский тег — функция автоматического обнаружения избавляет нас от этого неудобства.

Действительно интересная часть — это содержимое элемента <tag> . Именно здесь, помимо прочего, мы можем дать нашему тегу имя, определить связанный с ним класс tag handler и определить, следует ли разрешить нашему тегу иметь содержимое тела.

Приведенный ниже код показывает, как мы можем использовать наш пример тега greeter в JSP.

 <%@taglib prefix="t" uri="DemoTags"  %>   <html>    <head><title>JSP Page</title></head>    <body>   <!-- prints Hello World. -->          <t:greeter />   </body>  </html> 

Благодаря taglib 'uri' директивы taglib мы сообщили JSP, где находится наш файл TLD и, следовательно, где находится класс реализации нашего простого tag handler . Атрибут 'uri' отображается непосредственно на одно из отображений, созданных контейнером при запуске; отображение контейнера, в свою очередь, указывает на информацию о TLD.

Как правило, вам не нужно заботиться об этом, но если атрибут uri не разрешается в сопоставление контейнера, предполагается, что это путь к файлу. Это полезно, только когда идентичные значения элементов uri встречаются в разных файлах TLD. Если соблюдаются такие соглашения, как использование доменного имени или какой-либо другой уникальной строки, этого никогда не должно произойти.

Обработка атрибутов тега

Пользовательские теги становятся более интересными, когда вы настраиваете их для использования атрибутов. Для этого мы добавляем переменные экземпляра и соответствующие методы установки свойств в класс tag handler . Контейнер вызывает для нас эти методы установки, передавая значения атрибута пользовательского тега в качестве аргументов.

Предположим, мы хотим разрешить нашему тегу greeter принимать атрибут, который будет определять, кого он должен приветствовать. Мы могли бы переписать класс tag handler для размещения атрибута 'name' , как показано здесь:

 public class Greeter extends SimpleTagSupport {     private String name = "World";     public void setName(String name){this.name = name;}      public void doTag() throws JspException {         PageContext pageContext = (PageContext) getJspContext();        JspWriter out = pageContext.getOut();         try {             out.println("Hello " + name);         } catch (Exception e) {            // Ignore.        }     }  } 

Нам также необходимо обновить файл TLD для обработки нашего вновь определенного атрибута. В приведенном ниже коде показана соответствующая часть обновленного TLD:

<tag> <name>greeter</name> <tag-class>demo.tags.Greeter</tag-class> <body-content>empty</body-content> <attribute> <name>name</name> <rtexprvalue>true</rtexprvalue> <required>false</required> </attribute> </tag>

Элемент attribute устанавливает атрибут с именем 'name' , заявляет, что он не является обязательным, и далее заявляет, что он будет принимать 'runtime expression values' . Затем мы можем использовать тег любым из следующих способов:

 <t:greeter name="Andy"/>  <t:greeter name="${param['name']}"/>  <t:greeter /> 

Первый вызов просто выводит «Hello Andy». Как и следовало ожидать, метод setName() был вызван со строковым литералом 'Andy' качестве аргумента.

Второй вызов аналогичен, но получает свое значение из входящего запроса через выражение EL . Вы можете отключить эту функцию, установив элемент 'rtexprvalue' в 'false' или, поскольку false является значением по умолчанию, вообще пропустив этот элемент.

Последний вызов не использует атрибут; вместо этого он использует значение по умолчанию переменной экземпляра 'name' . Вы можете сделать атрибут обязательным, выбрав значение 'true' для элемента 'required' (по умолчанию это false ), или вы можете программно проверить его существование, выполнив проверку на null — в зависимости от того, что имеет смысл для вашего приложения.

Обработка содержимого тела

Пользовательским тегам часто требуется доступ к содержимому их тела, а простые обработчики тегов предоставляют элегантный способ справиться с этим требованием. Во-первых, требуется простая поправка к TLD — элемент body-content должен иметь значение 'scriptless' . При использовании 'scriptless' вам разрешено помещать текст шаблона, стандартные действия и пользовательские действия в тело вашего тега, но не код Java.

Другой важный метод, который вызывается контейнером, это setJspBody() . Этот метод делает содержимое тела тега доступным в виде исполняемого фрагмента любых выражений EL , пользовательских действий и текста шаблона. Вы getJspBody() доступ к этому фрагменту с помощью getJspBody() и можете выполнить его с помощью JspFragment invoke() объекта JspFragment . Ниже код это в действии:

 public void doTag() throws JspException {         JspFragment body = getJspBody();        PageContext pageContext = (PageContext) getJspContext();        JspWriter out = pageContext.getOut();         try {             StringWriter stringWriter = new StringWriter();            StringBuffer buff = stringWriter.getBuffer();            buff.append("<h1>");            body.invoke(stringWriter);            buff.append("</h1>");            out.println(stringWriter);         } catch (Exception e) {            // Ignore.        }     } 

Давайте разберем этот код. Метод doTag() вызовом getJspBody() , и полученный JspFragment сохраняется в переменной с именем 'body' . JspFragment имеет интересный метод invoke() , который принимает java.io.Writer в качестве аргумента. Вот действительно важный бит: когда invoke() , фрагмент выполняется, а затем записывается в этот объект Writer .

В простых случаях вы можете предоставить invoke() аргумент null, заставляющий его использовать текущий Writer JSP и, следовательно, печатать исполняемый фрагмент непосредственно на странице. Во многих случаях, однако, вы захотите сначала обработать содержимое тела, прежде чем отправлять его в выходной поток.

Один из способов сделать эту обработку — использовать StringWriter и работать с лежащим в его основе StringBuffer . Как вы можете видеть в листинге 6, я добавил тег H1 в буфер, использовал invoke для выполнения и записи содержимого тела в устройство записи (и, следовательно, в его основной буфер), а затем завершил добавление закрывающего тега H1 . Простой вызов out.println() отправку обработанного содержимого тела в выходной поток.

Как только вы получаете суть использования исполняемого JspFragment , манипулирование содержимым тела становится JspFragment . Имейте в виду, что у вас есть доступ к объекту JSP pageContext (через getJspContext() ), поэтому вы можете использовать его setAttribute() и getAttribute() чтобы возвращать значения в JSP и иным образом координировать функциональность вашего тега с JSP, в котором он проживает.

Делать правильный выбор

Надеюсь, у вас теперь есть общее представление о том, как писать собственные теги с помощью простых обработчиков тегов. Итак, пора ли отказаться от файлов тегов и API классического обработчика тегов? Вот несколько указателей.

Если ваш тег обязательно должен использовать элементы сценариев (скриптлеты), вам нужно будет использовать классический подход тегов. Это редко должно быть проблемой, поскольку мы в значительной степени заменили скриптлеты JSTL , пользовательскими действиями и выражениями EL .

Одним из очевидных преимуществ, которые имеют классические теги, является то, что контейнеры могут быть оптимизированы для предоставления им функций пула; простые обработчики тегов, с другой стороны, создаются для каждого вхождения на странице JSP. Часто случается, что объединение требует больше затрат, чем оно того стоит. Таким образом, если у вас нет тега, который создает много дорогих ресурсов и используется многократно, я бы не позволил этому отказаться от использования простого метода обработки тегов. На самом деле, даже когда множественные вызовы оказываются дорогими, вы часто можете придерживаться простых обработчиков тегов и быть немного креативными со PageContext объекта PageContext удерживать ссылки.

Файлы тегов следует использовать, когда вам нужно сгенерировать много разметки, например HTML. Они также удобны, когда достаточно более RAD-подхода к разработке. Однако имейте в виду, что они обычно немного медленнее, чем теги в скомпилированной форме.

Когда у вас много Java-кода и важна производительность, простые обработчики тегов — отличный выбор. Их легко написать, и у них более чем достаточно силы, чтобы добиться цели.

Резюме

Мы только поверхностно рассмотрели возможные варианты использования простых обработчиков тегов. Я предлагаю вам обратиться к спецификации JSP для получения дополнительной информации. Вы также можете посмотреть, что может предложить ваша IDE в плане поддержки пользовательских тегов. Например, Netbeans 4 позаботится о том, чтобы написать для вас файл TLD, обеспечит завершение кода для пользовательского тега и имеет ряд других функций, ускоряющих разработку тегов.