Произвольные Тэги (Custom Tags) предоставляют вам всю мощь языка Java наряду с почти совершенной интеграцией в ваш уровень представления. [6]
Произвольные теги - это JSP теги самостоятельного производства. Для полного понимания смысла, вам необходимо, прежде всего, четко представлять себе, что такое тэги.
Есть шансы, что вы активно использовали теги и до прочтения этой главы. Очень мало разработчиков в наши дни работают в средах, которые полностью отделены от Internet, а это значит, что вы, вероятно, имеете как минимум основы знаний HTML или XML.
В языке разметки, таком как HTML или XML, структура встраивается в данные. Это позволяет синтаксическому анализатору, такому как браузер, интерпретировать инструкции для отображения или хранить информацию.
Основным механизмом для этого являются тэги.
Тэги - это очень просто, как для инструкции <b>, которая просто говорит читателю (в данном случае это web браузер), что необходимо применить некоторое форматирование к данным. Этот конкретный тэг предназначен для отображения данных (которые являются обычным текстом) жирным шрифтом. Каждый тэг должен иметь начальную и конечную точку, то есть в этом случае </b>, что позволяет синтаксическому анализатору языка разметки идентифицировать порцию содержимого путем определения того, какие данные лежат между начальным и конечным тегами. Это позволяет применить к этим данным некоторое структурирование или формат. В этом случаю любой текст между начальным и конечным тегами будет отображаться жирным шрифтом.
Другой важный механизм для применения этой структуры к данным - это атрибуты, которые делают тэги немного более сложными, такие как, например, тэг "table" в HTML. Тэг таблицы может иметь атрибут бордюра, то есть <table border="3">, это значение атрибута border, применяется к данным, которые появятся после тэга <table> и перед тэгом </table>. Эти данные, находящиеся между двумя тэгами, являются телом тэга. Все данные в теле этого тэга будут отображаться в таблице, а бордюр вокруг каждого элемента таблицы будет толщиной "3".
В случае тэга таблицы в HTML все более усложняется, поскольку тело тега может содержать другие тэги наряду с данными, позволяя вложение. Другими словами, вы можете определить строку в таблице и данные таблицы (или колонку) в строке. Это позволяет создать хорошее структурирование данных.
Пример:
<table border="3"> <tr> <td>black</td> <td>white</td> </tr> </table>
Здесь определена таблица с размером бордюра равным трем, одной строкой и двумя колонками, содержащая данные black и white. Слова black и white являются элементами данных, которые составляют тело тэга <td> или тэга данных таблицы (table data). Тэг <tr>, или тэг строка таблицы (table row), со своим встроенным тэгом <td> и данными составляет тело тега таблицы. Вы можете четко видеть, что взаимосвязь между этими тегами важна. Тэг строки таблицы <tr>, имеет мало смысла вне пределов тэга таблицы, аналогично, тэг данных таблицы <td> не имеет смысла вне пределов тега строки таблицы. Мы ссылаемся на тэг таблицы, как на родительский для тэга строки таблицы, а на тэг строки таблицы, как на родительский для тега данных таблицы.
XML вводит идею пространства имен, которое дает вам немного больше гибкости при именовании тэгов. (Более детально смотрите главу об XML.) Если вы хотите создать свой собственный язык разметки, аналогичный HTML, вы можете определить пространство имен, называемое mytags, и определить ваш собственный тэг в нем, таким образом получиться тэг <mytag:table> и </mytag:table>. Смысл прост: это должно значить, что в данном случае не будет путаницы между оригинальным HTML тэгом table и вашим собственным, заново определенным тэгом table. Ваш тэг table находится в собственном пространстве имен. Это значит, что вы можете использовать последовательную конвенцию имен, то есть вызов "table" предпочтительнее "myspecialtable", когда не нужно беспокоиться о коллизии с определением в HTML стандарте.
Произвольные теги имеют все приведенные выше свойства. Они имеют тела, которые либо могут содержать данные, либо могут быть пустыми, они имеют свойства, которые могут быть применены к телу тэга. Они также используют пространство имен, чтобы допустить последовательную конвенцию имен. Вы будете использовать некоторые или все эти возможности, в зависимости от того, что подходит для вашего приложения.
Что нам дают произвольные теги?
Мощь: Так как с помощью JavaBeans реальные проблемы программирования, такие как опрос баз данных или создание сложных вычислений и т. п., могут быть выполнены в Java коде со всей мощью библиотек Java за вашими плечами. Произвольные тэги JSP также позволяют вам создавать объекты, доступные автору web страницы посредством фрагментов Java кода, или "скриплеты", так что при аккуратном дизайне вы можете создать огромную функциональность, доступную через простой и ясный интерфейс.
Последовательность: В отличие от JavaBean, представленный код четко отделяется от кода бизнес логики, потребность в скриплетах снижается и большая часть разработки выполняется с помощью HTML при использовании тэгов. Поэтому это легче понимать автору web страницы и намного легче читать и отлаживать.
Инкапсуляция: Бизнес логика определяется в Java классах, которые могут решать сложные проблемы программирования, такие как сетевое взаимодействие или опрос базы данных. Этот код может быть протестирован изолировано и, наконец, представлен в виде простого тега, что идеально для web автора при использовании во время создания web приложения.
Повторное использование: Тэги хранятся в библиотеках, которые в точности являются сущностями повторного использования во время web разработки. Библиотека тэгов обычно содержит набор тэгов сходной или соединенной функциональности. Библиотека может быть легко включена в web страницу автором, которому необходима эта функциональность. Тэг намного легче использовать, чем обыкновенный метод Java!
Здесь, если вы помните раннее обсуждение использования Java bean из JSP в главе, посвященной JSP, вы можете спросить себя: почему это необходимо. JavaBeans в JSP также помогает нам отделить уровень представления от бизнес логики. Beans тоже могут храниться в библиотеке, они могут повторно использоваться, имеют последовательный интерфейс и могут даже взаимодействовать со скриплетами, так почему нам необходимы теги?
Ответ достаточно хитрый. Beans необходимы для разработчика приложений и очень полезны, если используются в правильном месте. Фактически, как мы увидим, beans и произвольные теги очень хорошо дополняют друг друга. Хотя различия состоят в том, что bean являются компонентами, а произвольные теги являются, в сущности, действием или обработчиком. Это необходимо понять и хорошо взвешивать в вашем дизайне, не будет ли некорректным использование бина в этом месте.
Beans, как и любые другие компоненты, имеют свойства, методы и внутреннее состояние. Что делает использование beans в JSP легким, это взаимодействие между JSP страницами и Java beans, предназначенными специально для получения и установки свойств bean. Если вы именуете методы вашего bean в соответствии с соглашением о JavaBeans, вы можете получить очень простой доступ к этим свойствам через JSP.
Давайте вспомним JavaBeans. Рассмотрим bena персоны с двумя свойствами: имя и возраст. Чтобы получить доступ к ним через JSP теги, вам необходимо использовать
<jsp:usebean …> действие и
<jsp:getProperty name=”beanName” property=”requiredProperty”> действие для каждого из этих двух значений. Если вы хотите поместить их в строки HTML таблицы, вы должны указать
.. <table> <tr> <td><jsp:getProperty name="personbean" property="name"></td> <td><jsp:getProperty name="personbean" property="age"></td> </tr> </table> ..
Это хороший на вид, последовательный HTML код. Он легок в уходе в настоящее время, но станет более ужасным и "раздутым", как только вы станете использовать beans с большим числом свойств. Вы можете иметь пять bean'ов у каждого из которых будет пять свойств и вы будете постоянно вынуждены набирать двадцать пять строк. В реальном приложении вы, к сожалению, не будете иметь столько мало, например два, параметров, как в приведенном выше примере. Что нужно здесь, это очевидно немного программирования, так чтобы нам не нужно было много набирать и создавать много исходного кода.
Одна из великих сил при использовании JavaBean совместно с JSP является ограниченной. Поскольку JSP теги getProperty и setProperty легко использовать для установки и чтения свойств bean'а. Помните, что это ссылки для использования методов установки и получения. Но если вы попробуете вызвать другие методы для bean'а, вы должны сделать это напрямую и вы ограничены кодом скриплета или кодом выражения, используя тэги <% %>. При этом код начинает выглядеть немного ужасающе и непоследовательно. Рассмотрим фрагмент.
<ol> <% while (list.hasNextElement()) { list.nextElement(); %> <li><jsp:getProperty name="list" property="currentElement"></li> <% } %> </ol>
В приведенном выше примере обратите внимание, что для выполнения перебора в простом цикле и отображения элементов из списка (используя воображаемый bean списка, который перебирает свои данные) первые две строки кода Java скриплета помещены в скриплетные скобки. Тэг jsp:getProperty позволяет использовать HTML'ные скобки, что дает нам последовательное представление показанного кода, но затем закрывающая скобка цикла while снова обрамляется скобками скриплета <%}%>. Теперь два тэга обрамляют единственную закрывающую скобку и, как вы можете видеть, это снова выглядит хорошо. Если вы попробуете отладить такой код, может быть очень затруднительно работать в том месте, где один цикл заканчивается, а другой начинается. Инструменты отладки JSP улучшают свое качество, но все еще не покрывают большинство желаний. Добавьте к этому тот факт, что почти невозможно достигнуть последовательных соглашений относительно ваших скобок и вы можете видеть, что это потенциальный беспорядок.
Некоторый код скриплетов неизбежен, но большинство его нежелательна. Как только возрастает сложность кода, то комбинация HTML тэгов, JSP директив, действий и кода скриплета, каждый из которых в своей нотации скобок, будет увеличивать сложность кода, делая его сложным для чтения и ухода.
Как это предотвратить? Очень заманчиво написать bean, который возвращает значения, которые уже заключены в теги. Таким образом, это, на первый взгляд, удаляет большую часть HTML кода и делает документ короче и проще. Подобная реализация в виде bean может просто возвращать строковое значение из методов get, которые уже имеют форматирующие теги в себе и выходящие данные должны отображать себя лучше на экране.
Как заманчиво это звучит, но это оборачивается очень плохой идеей, как вы вскоре увидите.
Относительно bean, который имеет строковый метод, возвращающий таблицу строк данных, включая тэги. Это может выглядеть примерно так...
public class PersonDataBean {
private int age;
private String name;
String getCurrentRow() {
return "<tr><td>" + name + "</td><td>" + age + "</td></tr>";
}
}
Теперь метод getCurrentRow( ) возвращает строку, которая отображает строку таблицы, а HTML, отображающий персону в таблице из двух приведенных выше примеров, становится проще:
.. <table> <jsp:getProperty name="personbean" property="currentRow" /> </table> ..
Таким образом, мы сокращаем HTML кодирование, но это плохой подход к дизайну по нескольким причинам. Первое, здесь создается очень плотная связь между данными и их представлением. Это имеет тенденцию покрывать туманом представление о bean'е, как об объекте, и загромождает дизайн.
С точки зрения программирования дела обстоят еще хуже. Программист, используя этот bean, должен получить назад результат в таблице, не зависимо от того подходит это ему или нет. Расположение и порядок полей не может быть изменен программно и, хотя можно использовать страницу стилей для изменения стиля вывода, будет невозможно сделать что-либо специфичное (например, показать одну колонку жирным шрифтом).
Это значит, что автор web страницы, использующий этот bean, теперь ограничен представляемыми данными в формате, определенном разработчиком bean'а. Разработчик bean'а может быть великолепным разработчиком баз данных, но может не быть тем человеком, который отвечает за финальное представление. Фактически, очень желательно, чтобы требования к расположению не были даже известны во время разработки bena'а. Bean должен быть написан с возможностью повторного использования в уме, но ни один дизайнер web страницы не одобрит работу с предварительно определенным расположением.
Таким образом, хоть и кажется, что проблема увеличения сложности кода решена, фактически, мы потеряли возможность повторного использования кода.
Вы всегда будете наталкиваться на эти проблемы расположения при динамическом web контенте. Когда разрабатывается страница, вам постоянно необходима разметка или представление, которое зависит от состояний определенных программ (таких как переменная длина таблицы, различные цвета для значений внутри определенных рамок и т. п.). Такое динамическое поведения в точности является тем, что мы можем ожидать от web страницы, но это поведение делает код тяжелым для чтения, поскольку требует скриплетного программирования с забавными скобками, встроенными в код.
Понятно, что код скриплета не может быть удален их JSP страницы полностью, и это нам не нужно. Фактически, написание скриплетов с использованием JavaBeans (или использование любого другого скриптового языка с компонентами любого рода) это великолепный способ построить приложение до тех пор, пока не появится злоупотребление. До сих пор мы обнаружили две проблемы: необходимость отделить код представления от бизнес логики; и необходимость предотвратить сложности любого рода, которые вкрадываются в наш код представления при смешивании кода HTML со скриплетами. Мы также можем видеть, что не смотря на всю полезность JavaBeans, они не всегда решают эти проблемы и могут приводить к плохому дизайну. Таким образом, в игру вступают произвольные теги.
О JSP тэге можно думать, как о действии или об обработчике для некоторого количества событий. Эти события реально запускаются JSP контейнером путем чтения JSP исходников и разбора каждого тэга. Различные методы, соответствующие этим событиям, это ничто иное, как doStartTag( ) и doEndTag( ) - точки, в которых они вызываются, должны быть понятны из названий.
Конечно же, есть больше чем 2 метода, которые вам будут нужны для создания произвольных тегов, и когда мы пишем свои собственные тэги, мы имеем разные классы и интерфейсы, которые мы можем использовать, чтобы получить больше или меньше функциональности, по необходимости.
Давайте взглянем на простейший тэг, тэг "Hello World!" будет выполнен славно.
Было бы идеально, если бы тэг должен был отображать Hello World. Такой тэг должен выглядеть примерно так:
Обратите внимание, что здесь употребляется пространство имен tijtags со своим именем helloworldtag. Также обратите внимание, что есть прямой слеш в конце имени тэга. Это значит, что тэг начала и тэг конца собраны в одном тэге. Таким образом, использована стенографическая нотация, определенная в XML. Это также может быть записано следующим образом:
В любом случае, очевидно, что тэг не имеет тела, и как вы можете предположить, необходимо минимум программирования, чтобы заставить его работать. Вам необходимо сказать JSP контейнеру, каким образом искать ваши Java классы, тем не менее, чтобы сделать это, должен быть выполнен минимальный набор задач:
- Написать класс, который реализует интерфейс Tag, находящийся в пакете javax.servlet.jsp.tagext.Tag.
- Реализовать методы "setPageContext(), doStartTag() и doEndTag()"
- Создать TLD файл или дескриптор библиотеки тэга для тэга.
Давайте позаботимся о первых двух задачах:
package cx2.tags;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.JspException;
import java.io.IOException;
public class HelloWorldTag implements Tag {
PageContext pageContext;
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag parent) {
}
public Tag getParent() {
return null;
}
public int doStartTag() throws JspException {
try {
pageContext.getOut().println("Hello World!");
}
catch (IOException ioe) {
ioe.printStackTrace();
}
return Tag.SKIP_BODY;
}
public int doEndTag() throws JspException {
return Tag.EVAL_PAGE;
}
public void release() {
}
}
Вы заметите, что конструктор не определен, это означает, что этот класс имеет конструктор по умолчанию без параметров. Это одно из требований при написании класса, обрабатывающего тэги.
Вы заметите, что определено свойство, называемое pageContext, имеющее тип PageContext, находящийся в пакете javax.servlet.jsp. Оно инициализируется в методе setPageContext( ). Контейнер вызывает его автоматически и передает в него ссылку на PageContext при первом чтении тэга. Эта ссылка должна сохраняться, потому что она понадобится позже. Спецификация JSP Tag Extension гарантирует, что метод setPageContext всегда будет вызываться до метода doStartTag( ).
Дале реализован метод doStartTag( ). Этот метод выполняет всю работу, которую должен делать этот тэг.
С помощью объекта pageContext, который был ранее проинициализирован, запрашивается выходной поток JSP. Это выходной поток, который отправляется назад браузеру, читающему JSP страницу, и используется метод println( ) этого потока точно так же, как если бы вы использовали System.out.
Обратите внимание, что метод doStartTag( ) возвращает значение Tag.SKIP_BODY. Таким образом сообщается контейнеру, что он должен игнорировать все, что находится между начальным и конечным тэгом. Так как этот тэг не имеет тела, это все, что нам нужно в настоящий момент.
Реализация метода doEndTag( ) просто возвращает значение Tag.EVAL_PAGE. Это говорит контейнеру, что нужно продолжить процесс обработки JSP страницы, как это было до того, как нашли наш тэг. Опять таки, поскольку наш тэг очень прост, это все, что нужно.
Есть несколько других методов в интерфейсе Tag, которые должны быть реализованы, но только три упомянутых выше метода важны в настоящий момент.
Все, что теперь осталось сделать для тэга HelloWorld - это описать для контейнера, что ваш новый тэг вызывает и в каком классе он реализован. Чтобы сделать это, вам необходимо написать дескриптор библиотеки тэга - это XML файл, который используется контейнером для получения информации о тэге или тэгах, определенных вами. Этот файл сохраняется с расширением .tld и помещается на ваш сервер с вашими JSP файлами.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <shortname>htl</shortname> <uri>http://www.eckelobjects.com/tagtut/tags1.0</uri> <info> Library of tags on increasing complexity to demonstrate the use of tag libraries. </info> <tag> <name>helloworldtag</name> <tagclass>cx2.tags.HelloWorldTag</tagclass> <bodycontent>empty</bodycontent> <info> echo's hello world to the browser. </info> </tag> </taglib>
Не пугайтесь увиденного. Тэги в этом файле менее загадочны, чем это выглядит. Они будут представлены более детально позже, а пока все, о чем вам нужно побеспокоиться - это тэг tag. Этот тэг, что не удивительно, сообщает контейнеру вещи, которые ему необходимо знать о новом тэге, который вы только что создали. Есть четыре пункта, которые важны для каждого тэга: его имя, какой класс содержит реализацию тэга, какого рода содержимое должно находиться в его теле и, наконец, описание того, что делает этот тэг. Описание должно быть коротким и ясным, по типу того, которое вы можете ожидать в контекстной подсказке панели инструментов графического редактора, дающего вам возможность использовать этот тэг в вашем приложении.
Чтобы использовать новый тэг в JSP странице вы должны указать что-то наподобие этого:
<%@ taglib uri="/tijtags.tld" prefix="tijtags"%> <b> <tijtags:helloworldtag /> </b>
Вот и все, что вам нужно сделать, чтобы послать "Hello World" назад в браузер!
В первой строке помещена директива JSP taglib. Атрибут uri говорит контейнеру, что ваш дескриптор библиотеки тэгов может быть найден в файле, называемом "tijtags.tld" в текущем каталоге. Атрибут prefix говорит контейнеру, какое пространство имен вы используете, чтобы ссылаться на ваши тэги.
Тэг <b> - это просто декорация, чтобы особо указать на то, что вы можете свободно использовать HTML вместе с JSP тэгами. Такая комбинация становится очень мощной, когда вы пишите тэги итераций.
Использование Тэгов и JavaBeans
Тэг "hello world" не очень мощный. Определенно, он не сэкономит чьих-то усилий. В этом случае, конечно же, было бы гораздо проще напечатать слова "Hello World" самостоятельно. Давайте посмотрим на пример, который немного более продвинутый с точки зрения программирования и, в то же время, иллюстрирует, как тэги могут создавать объекты, доступные для JSP страницы.
В этот раз мы намереваемся создать приветственный тэг, который немного более полезен, чем "hello world", и поэтому немного более общий. Представьте тэг приветствия, который при использовании будет создавать объект приветствия и делать его доступным для JSP страницы. Если этот объект, JavaBean, имеет свойство, которое производит случайное приветствие, такое как "Hi there!" или "Hello", вы можете его использовать, например, так...
<tijtags:greetingtag id="randomgreeting"/> <jsp:getProperty name="randomgreeting" property="greeting"/>
Это немного более запутано, чем последний тэг. Давайте взглянем более пристально на различия.
Во-первых, хотя это другой тэг без тела, он имеет атрибут, называемый id. Указанный выше тэг передает строку "randomgreeting" в тэг.
Далее, есть JSP действие, getProperty, которое используется для запроса значения параметра "greeting" из JavaBean'а, называемого "randomgreeting". Это не случайное совпадение, что имя этого bean'а то же самое, что и значение атрибута "id", вы можете предположить, что здесь этот тэг позволяет выполнить getProperty, используя JavaBean с этим именем. Обычно вы будете ожидать увидеть JSP действие <jsp:useBean..., объявленное в верхней части JSP файла до того, как вы сможете использовать действие getProperty, чтобы получить доступ к bean'у, но в этом случае этот тэг действительно определяет bean и делает его доступным из этой страницы.
Чтобы заставить такой тэг работать, нужно выполнить следующие шаги:
- Создайте Javabean со свойством greeting и получите метод, который возвращает случайную строку приветствия, такую как "Hi there!" или "Hello".
- Напишите класс, который реализует интерфейс Tag.
- Дайте классу метод setId( ), который позволит контейнеру установить атрибут id.
- Создайте экземпляр JavaBean'а приветствия и сохраните его в контексте страницы.
- Создайте TLD или файл дескриптора библиотеки тэга для тэга.
В этот раз нужно сделать несколько больше вещей, но они все еще достаточно очевидны. Вы уже знаете, как писать JavaBean, но здесь есть минимальное описание того, что будет необходимо.
package cx2.tags;
public class GreetingBean {
String greetings[] = { "Hello!", "Howdy!", "Hi There!", "How do you do?" };
public String getGreeting() {
return greetings[(int) (Math.random() * greetings.length)];
}
}
Идея состоит в том, что класс тэга будет создавать экземпляр этого JavaBean'а, чтобы автор JSP страницы мог использовать действие getProperty на этой страницы.
Вот сам класс тэга.
package cx2.tags;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.JspException;
public class GreetingTag implements Tag {
private String id;
private PageContext pageContext;
public GreetingTag() {
}
public void setId(String id) {
this.id = id;
}
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
public void setParent(Tag parent) {
}
public Tag getParent() {
return null;
}
public int doStartTag() throws JspException {
pageContext
.setAttribute(id, new GreetingBean(), PageContext.PAGE_SCOPE);
return Tag.SKIP_BODY;
}
public int doEndTag() throws JspException {
return Tag.EVAL_PAGE;
}
public void release() {
}
}
Вы заметите, что здесь есть метод setId( ). Это не метод интерфейса Tag, но контейнер все равно будет вызывать этот метод. Через него будет передаваться значение атрибута id, как это указано в JSP файле. Это высвечивает другую интересную взаимосвязь между JavaBean'ом и тэгом. Тэг, который имеет атрибуты, должен иметь параметры в стиле JavaBean'а, которые подчиняются корректному соглашению имен и должны соответствовать именам атрибутов. Эти параметры будут устанавливаться автоматически контейнером с помощью механизма JavaBean, встроенного в Java. Два дополнительных правила обычно не ассоциируются с JavaBean'ами, но здесь они очень важны, а именно: вы можете иметь только один метод установки для атрибута, и вы не должны вызывать метод установки самостоятельно, т.е. откуда угодно из кода тэга.
Как и прежде, большая часть работы для этого тега делается в методе doStartTag( ). В этот раз его следует рассмотреть немного более внимательно, а именно тот факт, что каждый из методов doStartTag( ) и doEndTag( ) могут выбрасывать JspException. Это можно проигнорировать в настоящее время, но если возникает исключение в вашей реализации тэга, будет выброшено JspException, которое может завершиться тем, что оно отобразится в клиентском окне браузера, что редко бывает нужно. Техника обработки этого будет объяснена позднее.
Здесь метод doStartTag( ) использует метод setAttribute( ) объекта pageContext для создания экземпляра bean'а приветствия и хранения его в pageContext, указывая его имя и его область видимости. Имя берется их атрибута id, а область видимости - это хорошо знакомая константа PAGE_SCOPE, final параметр класса PageContext. Если это звучит запутанно, подумайте об этом как о двух частях. В класс тэга уже было передано имя через метод setId( ) и сохранено в переменной id. Теперь создается объект GreetingBean и добавляется в pageContext, ему передается имя, хранимое в переменной id. С точки зрения программирования это эквивалентно JSP акции: <jsp:useBean...>. Теперь JavaBean доступен для использования в действии <jsp:getProperty...> на JSP странице.
Все, что теперь осталось - это создать файл TLD. Так как такой уже существует, все, что вам нужно, это добавить другой тэг tag перед последним </taglib>.
.. <tag> <name>greetingtag</name> <tagclass>cx2.tags.GreetingTag</tagclass> <bodycontent>empty</bodycontent> <info>Adds a HelloWorld Bean to the page context.</info> <attribute> <name>id</name> <required>true</required> </attribute> </tag> ..
Обратите внимание, что в этом тэге tag есть вложенный тэг, называемый attribute. Имя вложенного тэга attribute говорит контейнеру о том, как называется атрибут, а вложенный тэг required говорит контейнеру о том, является ли этот атрибут обязательным или нет. Обязательные вложенные тэги должны быть указаны, т.е. в данном случае
<tijtags:greetingtag id="randomgreeting"/>
в противном случае контейнер выбросит исключение, когда попытается загрузить JSP страницу.
Для того чтобы ваш тэг был правильно декларирован и был правильно инициализирован атрибут, имя этого атрибута, имя в файле дескриптора библиотеки тэга и имя параметра JavaBean (и методов установки) все должны быть одними и теми же.
Для использования тэга случайного приветствия и объекта bean'а, который он создает, вы должны будете указать тэг:
который создает JavaBean в контексте страницы, чтобы вы могли его использовать.
Затем вам нужно получить свойство из bean'а:
которое посылает в браузер приветствие. Эта последняя строка может затем быть использована столько раз, сколько вам нужно на JSP странице после приведенной выше декларации. При каждом появлении она будет производить разные приветствия. Обновление этой страницы в вашем браузере также станет причиной появления другого приветствия, поскольку каждый раз контейнер обрабатывает действие getProperty, в котором вызывается метод getGreeting( ) класса GreetingBean, который возвращает новое, случайное приветствие. (Обратите внимание, что использование метода get для JavaBean'а, возвращающего случайное значение, часто может грешить пуризмом. Здесь он использован, как хорошая демонстрация работы с bean'ом.)
Тэги, которые управляют содержимым своего тела
До этого момента те два тэга, которые мы рассматривали, игнорировали содержимое своего тела или, что более точно, они были определены, как не имеющие никакого содержимого в теле. Тэги такого рода очень просты и часто ограничены в своей пригодности, хотя, как вы можете видеть, создание bean'а, доступного автору страницы, может быть достаточно полезным. Тэги, которые манипулируют содержимым своего тела, обычно намного более полезны, хотя немного более трудны в программировании.
Давайте взглянем на тэг, который реально интересуется содержимым своего тела.
В этот раз предположим, что вы хотите сгенерировать тэг, который дает вам хорошо разнесенные буквы для вашего текста. Например...
Это была темная и штормовая ночь...
Это может быть выполнено в HTML достаточно примитивным образом, с помощью использования текста заголовка и таблиц, которые служат для наших целей, но прежде, чем писать таблицу каждый раз, когда вы хотите разбить символы, почему бы не создать тэг, которые делает это за вас? В идеале он должен делать что-то типа такого:
это была темная и штормовая ночь, дождь падал стремительным потоком - за исключением тех случайных интервалов, когда он останавливался...
Атрибут цвета должен быть хорошей идеей, так чтобы большие буквы могли отображаться указанным цветом.
Чтобы создать этот тэг, нам необходимо выполнить следующие важные задачи:
Написать класс, который реализует интерфейс Tag.
Дать классу метод setColor( ), который позволит контейнеру устанавливать атрибут цвета.
Написать код, который читает содержимое тела и вставляет соответствующие тэги HTML, чтобы первый символ отображался, как отдельный символ.
Создать файл дескриптора библиотеки тэга для тэга.
Давайте взглянем на код этого тэга:
package cx2.tags;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.TryCatchFinally;
import javax.servlet.jsp.JspException;
import java.util.StringTokenizer;
public class DropCapitalTag extends BodyTagSupport implements TryCatchFinally {
String color = "black";
public void setColor(String color) {
this.color = color;
}
public int doAfterBody() throws JspException {
int period;
StringBuffer result = new StringBuffer();
String body = bodyContent.getString().trim();
result.append("<table><tr><td><h1><font color="" + color + "">");
result.append(Character.toUpperCase(body.charAt(0)));
result.append("</font></h1></td><td>");
period = body.indexOf('.');
result.append(body.substring(1, period + 1));
result.append("</td></tr></table>");
result.append(body.substring(period + 1, body.length()));
try {
if (result.length() > 0) {
bodyContent.clearBody();
bodyContent.println(result.toString());
}
bodyContent.writeOut(bodyContent.getEnclosingWriter());
}
catch (Exception e) {
e.printStackTrace();
}
return SKIP_BODY;
}
public void doCatch(Throwable t) {
System.out.println("An error occurred with the message"
+ t.getMessage());
}
public void doFinally() {
}
public void release() {
color = "black";
}
}
Первое, что вы заметите, это то, что в отличие от предыдущего тэга, этот тэг не реализует интерфейс Tag. Это происходит потому, что когда вам необходимо управлять содержимым тела, более полезно расширять класс BodyTagSupport, который берет на себя заботу о сохранении объекта pageContext и предоставлении реализации по умолчанию для методов doStartTag( ) и doEndTag( ). Этот класс реализует интерфейс Tag, так что вам не нужно делать это.
Метод doAfterBody( ) этого тэга является тем методом, где все происходит. Вы увидите, что этот класс ссылается на обработчик, называемый bodyContent. Это ссылка на объект BodyContent, который находится в пакете javax.servlet.jsp.tagext. Он предоставляется контейнером через метод setBodyContent( ) и является ничем иным, что мы свободно получаем во время наследования класса BodyTagSupport. Этот контейнер будет всегда предоставлять этот объект до того, как он вызовет метод doInitBody( ), так что вы всегда гарантированы в том, что у вас есть действительный объект BodyContent в ваших методах doInitBody(), doAfterBody(), doStartTag и doEndTag().
Объект BodyContent является инкапсуляцией содержимого тела этого конкретного тэга, он содержит весь текст из JSP страницы, который находится между начальным и конечным тэгом. Это очень сильно похоже на выходной поток, но с некоторой дополнительной функциональностью. Этот объект дает вам доступ к JSP тексту. Вот что делает тэг отделения заглавной буквы и вот что вам необходимо сделать, если вы хотите манипулировать содержимым тела. Вы можете получить тело целиком в виде строки, посредством вызова getString( ), или в виде потока, используя метод getReader( ), если это более подходит вашим нуждам.
В этом примере использован простейший алгоритм для удаления любого свободного пространства, которое поступает из JSP источника, изменение первой буквы на прописную и добавления некоторого количества HTML тэгов, чтобы отформатировать и предать эффект отделенной большой буквы.
После того, как были добавлены HTML тэги (с помощью объекта StringBuffer для добавления строк), появляется длинная строка, которой необходимо заменить старый текст. Чтобы сделать это, сначала вызывается метод clearBody( ) из BodyContent. Если вы не сделаете этого, содержимое тела появится в браузере дважды. Далее, мы используем метод println( ), чтобы добавить новую строку в BodyContent, и, наконец, вызывается метод writeOut( ). Этот метод сообщает объекту BodyContent о перенаправлении его вывода в браузер, но вы заметите, что этот метод принимает JspWriter в качестве параметра, а в этом случае writer получается из вызова getEnclosingWriter( ). Вам нужно сделать это, поскольку объект содержимого тела включает только тело вашего определенного тэга и вам необходимо получить доступ к выходному потоку, который направляет вывод клиентскому браузеру.
Если вы рассмотрите более подробно, вы увидите, что метод release( ) не делает ничего. Этот метод будет вызван контейнером для высвобождения своего состояния, так что его лучше использовать для высвобождения ресурсов, которые вы можете использовать в тэге. Имейте в виду, что может быть несколько вызовов doStartTag( ) и doEndTag( ) и между каждым из них вызывается release, так что это не тот метод, который нужно использовать для выполнения операций, выполняющихся каждый раз, при завершении обработки тэга. Если вы хотите сделать это, вам лучше всего использовать doFinally( ), описанный в следующем параграфе.
Вы можете заметить, что этот класс реализует интерфейс TryCatchFinally, находящийся в пакете javax.servlet.jsp.tagext. Когда контейнер видит, что ваш тэг реализует этот интерфейс, он знает, что нужно поместить блок try/catch вокруг обработчика тэга и, в случае возникновения исключения, передать обработку вам. Это значит, что вы получаете максимальный контроль над кодом обработки исключений и, если захотите, вы можете предотвратить отображение текста возникшего исключения в окне браузера.
Методы интерфейса TryCatchFinally - это doCatch( ) и doFinally( ) и они соответствуют ключевым словам catch и finally в Java. Метод doCatch( ) вызывается, если возникает исключение, исключение передается в него в качестве параметра, а doFinally( ) вызывается всегда, независимо от того, что случилось в других методах обработчика тэга. В этом примере, если возникнет исключение, на консоли сервера, а не в клиентском браузере, просто возникнет сообщение. Это немного ленивый подход к дизайну, использующийся для демонстрации интерфейса. Алгоритм, использованный здесь, не очень устойчивый, так что легко может возникнуть исключение. В приведенном выше методе doFinally( ) устанавливается черный цвет, так как это всегда значение по умолчанию.
Используйтесь этот интерфейс с осторожностью. Его основное назначение состоит в том, чтобы дать вам больший контроль, когда обрабатываете ресурсы такие, как потоки и т.п. Помните, что исключения относятся к восстановлению и может быть более подходящим обработать исключения в ваших методах doAfterBody( ) или doInitBody( ) и произвести некоторый имеющий смысл вывод для отображения в клиентском браузере, например страницы с ошибкой, которая попросит пользователя ввести повторно некоторые детали. Вы можете захотет просто перенаправить браузеру общую страницу сообщения об ошибке, если это больше подходит.
Теперь, когда вы уже увидели, как реализован класс, давайте взглянем на запись о тэге в файле дескриптора библиотеки тэга. Она выглядит так:
.. <tag> <name>dropcapitaltag</name> <tagclass>cx2.tags.DropCapitalTag</tagclass> <bodycontent>JSP</bodycontent> <info> Transforms text to have a drop capital. </info> <attribute> <name>color</name> <required>false</required> </attribute> </tag> ..
Обратите внимание, что атрибут цвета не требуется. Как вы можете видеть в исходном коде, тэг имеет черный цвет по умолчанию, если цвет не указан, так что цвет не обязателен здесь.
Вот и все, что вам нужно сделать для манипуляции телом тэга!
Итеративные тэги
Одна из наиболее мощных особенностей произвольных тэгов в JSP - это то, что они могут итерировать, как цикл for или while в Java и, таким образом, производить динамический вывод.
Давайте рассмотрим более сложный тэг, который реально делает итерации по содержимому своего тела, воспроизводя переменный вывод в зависимости от своих атрибутов.
Предположим, что вы хотите сгенерировать список простых чисел (чисел, делящихся только на себя и на 1) и отобразить его в HTML в виде ненумерованного списка. Что вам действительно желательно - это тэг, который может генерировать все простые числа в пределах указанного диапазона, предоставит вам доступ к каждому значению и поместит его в желаемый вам формат. Так как вы не знаете, сколько чисел будет в заданном диапазоне, а их может быть несколько, вы не захотите набирать HTML тэги для списка <li> и </li> для каждого из них. Допустим, что ваш тэг называется primtag, и вы хотите, чтобы ваш JSP файл выглядел примерно так:
<ul> <tijtags:primetag start="1" end="25"> <li><%=value%></li> </tijtags:primetag> </ul>
Обратите внимание, что есть начальный и конечный HTML тэги <ul>, окружающий весь блок. **(HTML лекция выходит далеко за пределы темы этой главы, но коротко скажу, что они определяют ненумерованный список и внутри тела того тэга все данные между парой <li> и </li> будут отображаться, как помеченный элемент списка.)
Следующее, что вы заметите, это ссылка на хорошо известное пространство имен tijtags, указанное в новом произвольном тэге primetag с двумя атрибутами start и end.
В этом тэге, в отличие от двух предыдущих тэгов, есть не только завершающий тэг, указанный отдельно, </tijtags…, но также некое содержимое тела между начальным и конечным тегами. Вот что делает этот тэг особенно интересным. Тэги <li> являются простым HTML, как сказано выше, но между этим, заключенная в скриплетные скобки JSP, есть ссылка на переменную value.
Есть два очень мощных свойства этого произвольного тэга. Во-первых, он делает доступной переменную value для использования в скриплете или в простом JSP выражении, таком как <%=value%>. Во-вторых, все, что находится в теле нашего произвольного тэга, неоднократно обрабатывается и добавляется в выходной поток, чтобы быть представленным браузеру. Это значит, что не только приведенный выше тэг будет зациклен до тех пор, пока не найдет все примитивные числа внутри указанного диапазона, но также будут отображаться тэги <li> и </li> наряду со значением переменной value для каждого числа! Другими словами, приведенный выше JSP код - это все, что вам необходимо для отображения значений простых чисел в чистом списке. И вам даже не нужно знать, сколько чисел должно получиться.
Случайно это стало очень полезно. Это тот тэг, который помогает снизить количество встроенного кода скриплета в нашем хорошо сделанном тэге, но дает нам полную программную функциональность, такую, как цикл.
Чтобы реализовать этот тэг вам необходимо выполнить следующие задачи:
- Создать (или найти) класс с подходящей логикой для генерации простых чисел.
- Написать класс тэга, который реализует подходящий интерфейс тэга.
- Написать методы установки для параметров start и end.
- Запрограммировать механизм цикла для обработки содержимого тела и для каждой итерации сделать доступной для JSP страницы переменную со значением текущего простого числа.
- Создать для тэга TLD или файл дескриптора библиотеки тэга.
Вы можете найти класс PrimeUtilities на CD (??) или в приложении (??). Этот пример использует метод sievePrimes( ), который находит простые числа. Есть много ресурсов в Интернете для нахождения такого алгоритма, если вы интересуетесь подобного рода вещами.
Давайте взглянем на реальный класс тэга.
package cx2.tags;
import java.util.ArrayList;
import java.util.Iterator;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import com.bruceeckel.PrimeUtilities;
public class PrimeNumTag extends BodyTagSupport {
private int start = 0;
private int end = 0;
ArrayList primeList;
Iterator primeIterator;
public void setStart(int start) {
this.start = start;
}
public void setEnd(int end) {
this.end = end;
}
public int doStartTag() throws JspException {
try {
primeList = PrimeUtilities.sievePrimes(start, end);
primeIterator = primeList.iterator();
}
catch (Exception e) {
e.printStackTrace();
return SKIP_BODY;
}
return EVAL_BODY_BUFFERED;
}
public void doInitBody() throws JspException {
try {
if (primeIterator.hasNext()) {
pageContext.setAttribute("value", primeIterator.next());
}
}
catch (Exception e) {
e.printStackTrace();
}
}
public int doAfterBody() {
try {
if (primeIterator.hasNext()) {
pageContext.setAttribute("value", primeIterator.next());
return EVAL_BODY_BUFFERED;
}
else {
bodyContent.writeOut(bodyContent.getEnclosingWriter());
return SKIP_BODY;
}
}
catch (Exception e) {
e.printStackTrace();
return SKIP_BODY;
}
}
public void release() {
primeList = null;
start = 0;
}
}
Первое, что вы должны заметить, это то, что класс тэга больше не реализует интерфейс Tag напрямую. Это происходит потому, что тэг имеет немного больше функциональности и поэтому более полезно наследовать класс BodyTagSupport. Этот класс реализует интерфейс Tag и предоставляет некоторую дополнительную функциональность. Варианты классов и интерфейсов Tag будут обсуждаться более детально позднее.
Этот класс, опять таки, использует объект PageContext, предоставляемый контейнером посредством метода setPageContext( ). Так как этот класс тэга наследуется от BodyTagSupport, больше нет необходимости хранить объект pageContext самостоятельно. Об этом заботится родительский класс. Это хороший пример повторного использования кода посредством наследования.
Метод doStartTag( ) становиться первым методом, о котором вам необходимо позаботиться. В приведенной выше реализации используется класс PrimeUtilities, использующийся для получения и хранения объекта типа Iterator, который содержит простые числа. Обратите внимание, что если не возникнет исключения, этот метод возвращает значение EVAL_BODY_BUFFERED, в отличие от двух предыдущих примеров, в которых возвращалось значение SKIP_BODY во всех случаях. Как вы можете предположить, это ключевое значение для реализации тэга, который обрабатывает содержимое своего тела.
Далее вы заметите, что реализован метод doInitBody( ). Этот метод вызывается JSP контейнером прежде, чем тело вашего тэга будет обработано, и это тот метод, в котором вы будете выполнять код любой инициализации, требующейся для обработки содержимого тела. В предыдущем примере в этом не было необходимости, так как действительно нечего было делать. Однако в приведенном выше примере этот метод опрашивает итератор простых чисел, чтобы убедиться, что в списке есть доступные простые числа, и если они есть, используется метод setAttribute( ) для добавления значения первого имеющегося простого числа из списка в контекст страницы. Как вы можете видеть, значение ассоциируется с именем value.
Мы употребляли аналогичную форму setAttribute( ) ранее, но в этот раз вы заметите, что метод имеет только два параметра, поскольку этот класс использует область видимости по умолчанию PAGE_SCOPE. Действия, делающие этот объект доступным для использования в JSP странице, заключены в части ответственности специального класса, называемого TagExtraInfo - класс, который будет рассмотрен немного позднее. Конструкция цикла теперь инициализирована и, если не произошло никаких исключений, в этом месте тэг будет читать текст тела.
Метод doAfterBody( ) не отличается от метода doInitBody( ). Он проверяет, чтобы убедиться, что больше нет простых чисел в итераторе, а если они есть, метод добавляет значение имеющегося простого числа в контекст страницы. Если больше нет имеющихся простых чисел, метод пишет содержимое тела тэга в выходной поток, который перенаправляется в браузер.
Если вы посмотрите внимательнее, вы увидите, что все это не достаточно очевидно. Объект, на который ссылается bodyContent, фактически является экземпляром класса BodyContent, который находится в пакете javax.servlet.jsp.tagext. Он инкапсулирует содержимое тела этого конкретного тэга, а это немного похоже на выходной поток, но с некоторой дополнительной функциональностью. Если бы объект PrimeNumTag не наследовался бы от класса BodyTagSupport, то необходимо было бы реализовать метод setBodyContent( ) и сохранять объект bodyContent "вручную", аналогично тому, как это мы делали с объектом pageContext в предыдущих примерах.
Поскольку объект содержимого тела включает только тело вашего конкретного тэга, вам будет необходимо вызывать метод getEnclosingWriter(), чтобы получить верный выходной поток.
Что является реальной причиной возникновения цикла - это возвращаемое значение. Если метод doAfterBody( ) возвращает константу EVAL_BODY_BUFFERED, то контейнер будет обрабатывать тело тэга и вызовет метод снова. Если метод возвращает SKIP_BODY, то цикл прекращается и вам предоставляется возможность послать содержимое тела в прилагающийся поток writer'а, ваши значения будут отображены на экране браузера.
Теперь осталось только создать запись о тэге в TLD файле.
<tag> <name>primetag</name> <tagclass>cx2.tags.PrimeNumTag</tagclass> <bodycontent>JSP</bodycontent> <info>Generates Prime Number sequences</info> <attribute> <name>start</name> <required>false</required> </attribute> <attribute> <name>end</name> <required>true</required> </attribute> <variable> <name-given>value</name-given> <variable-class>Integer</variable-class> <declare>true</declare> <scope>AT_BEGIN</scope> </variable> </tag>
Если вы посмотрите на атрибуты тэга, вы увидите, что атрибут start не является обязательным, а атрибут end - является. Это происходит просто из-за выбранного дизайна класса. Здесь имеется в виду, что если start не указывается, используется значение по умолчанию 0, но так как (по всей видимости) существует бесконечное количество простых чисел, то хорошо бы ограничить конечное число. Вы должны помнить, что если автор JSP страницы не согласен с этим ограничением при написании JSP страницы и не обратит внимания на значение end, контейнер выбросит исключение в окно web браузера.
После тэга атрибута есть новый тэг, с которым вы еще не знакомы, это тэг <variable> со своими вложенными тэгами <name-given>, <variable-class> и <declare>. Они, как вы можете ожидать, сообщают JSP контейнеру о переменной, которая добавляется в контекст страницы вашим тэгом. Так как эта переменная является объектом, а не JavaBean'ом, нет автоматического механизма, чтобы сообщить контейнеру тип переменной, так что вам необходимо сделать это с помощью такого тэга. Значения этих тэгов перечисляется в конце этой главы, но давайте взглянем на некоторые из них сейчас.
Тэг <name-given> сообщает контейнеру, какое имя дается используемой переменной. Вы можете использовать другой тэг, <name-from-attribute> в этом месте, который позволит вам указать имя атрибута, значение времени трансляции которого будет содержать имя вашей переменной. Таким образом, вы можете сделать имя в зависимости от некоторых других значений времени трансляции.
Тэг <variable-class> сообщает контейнеру переменную какого типа необходимо создать, класс, с указанным именем, должен содержаться в classpath во время трансляции, а именно в то время, когда JSP страница компилируется в первый раз на сервере. Чаще всего вы будете использовать стандартные типы, находящиеся в java.lang.
Тэг <declare> сообщает контейнеру нужно или нет декларировать эту переменную. Это предназначено для дальнейших реализаций JSP. Пока же вы всегда будете желать, чтобы переменная декларировалась, что является значением по умолчанию.
Значение <scope> сообщает контейнеру в каком месте вашего скрипта переменная должны быть доступна автору JSP страницы. Возможные значения: NESTED - что делает переменную доступной только между начальным и конечным тегами на JSP странице; AT_BEGIN - что делает переменную доступной между начальным тэгом и концом страницы; или AT_END - что делает переменную доступной после конечного тэга до конца страницы.
Во время написания наблюдались некоторые проблемы в текущей реализации Tomcat (4.0.4) при чтении тэга <attribute>. По этой причине вы можете использовать более продвинутую и мощную технику класса TagExtraInfo. Это влечет за собой замену тэга <variable> на тэг, называемый <teiclass> или тэг класса дополнительной информации. Более детально это описано ниже в этой главе.
Тэги внутри Тэгов
Тэги также могут помещаться внутри других тэгов, что дает нам мощную структурную функциональность, а также возможность создавать вывод при условии, когда дочерние тэги вычисляются только при определенных условиях, устанавливаемых в их родительских тэгах. JSTL, Java Standard Tag Libraries, в полной мере использует это, но давайте взглянем на то, как мы можем создать подобный тэг самостоятельно.
Теперь, когда вы чувствуете себя немного более комфортно с исключениями в тэгах с помощью API JSP, нам необходимо только рассмотреть ключевые моменты следующего тэга. Предположим, что вы используете тэг генерации простых чисел, и вы хотите проверить каждое простое число, чтобы убедиться, что это простое число типа "Ферма" и показать его в браузере. Числа Ферма - это такие числа, которые могут быть представлены, как "2 в степени 2 в степени n плюс 1", где n - целое число. Если ваши глаза тускнеют от математических описаний, не беспокойтесь. Достаточно сказать, что некоторые простые числа являются простыми числами Ферма, а другие нет. Фактически, только числа 3, 5, 17, 257, 65537 являются известными простыми числами Ферма.
Так как тэг проверки простых чисел Ферма предназначен, чтобы выполнять проверку каждого числа, сгенерированного тэгом простых чисел, очевидно, что нет смысла, чтобы тэг fermatprimetag существовал вне тэга primenumtag. Это значит, что он должен быть определен, как дочерний тэг, что помешает пользователям библиотеки тэгов, содержащей этот тэг, совершить ошибку и означает, что вы можете сделать определенные допущения. Это можно проиллюстрировать в следующем коде. Вот класс тэга:
public class FermatPrimeTag extends BodyTagSupport {
public int doStartTag() throws JspException {
PrimeNumTag parentTag =(PrimeNumTag)findAncestorWithClass(this,
PrimeNumTag.class);
if (parentTag == null) {
throw new JspException("Tag should be nested in "primenumtag" Tag");
}
return EVAL_BODY_TAG;
}
public int doAfterBody() throws JspException {
Integer prime = (Integer) pageContext.getAttribute("value");
String s = bodyContent.getString();
try {
if (PrimeUtilities.isFermatPrime(prime.intValue())) {
bodyContent.clearBody();
bodyContent.println(s + " (is a Fermat Prime Number) ");
}
bodyContent.writeOut(bodyContent.getEnclosingWriter());
}
catch (IOException ioe) {
throw new JspException(ioe.getMessage());
}
return SKIP_BODY;
}
}
Обратите внимание на метод findAncestorWithClass( ). Он рекурсивно проходит всю иерархию тэгов в поисках родителей до тех пор, пока не найдет того, кто соответствует классу, который вы ищите. Этот метод работает посредством рекурсивного вызова метода getParent( ). Это еще одна часть управления тэгом, которую мы наследуем от класса BodyTagSupport. Если вы не будете наследовать от этого класса, вам необходимо реализовать методы setParent( ) и getParent( ) самостоятельно. Формирователь вызывается контейнером и передает вам ссылку на родительский тэг. Вы должны были бы сохранять эту ссылку для обработки запроса метода getParent( ).
Возможны случаи, когда все это необходимо выполнять самостоятельно, к счастью они бывают редко. Теперь же вы можете быть уверены в мудрости при создании своих собственных тэгов посредством наследования от BodyTagSupport.
Если метод findAncestorWithClass( ) не найдет родительский тэг подходящего типа, будет выброшено исключение, которое появится в окне клиентского браузера. Это скажет пользователю JSP библиотеки, что он использует тэг неправильно.
Другая важная вещь, которую необходимо заметить, это то, что этот тэг вызывает метод getAttribute( ) для объекта pageContext, чтобы получить значение атрибута с именем value. Это тот атрибут, который добавляется в контекст страницы родительским тэгом primenumtag. Мы получаем доступ к нему по той причине, что наш дочерний тэг fermatprimetag и наш родительский тэг primenumtag находятся на одной странице и поэтому разделяют один и тот же объект pageContext. Помните, что вы все еще должны очищать содержимое тела и получать вложенный writer. Реально здесь вы заменяете существующее простое число на строку, отображающую простое число и сообщение, гласящее ".. is a Fermat Prime Number.".
Этот тэг намного проще, чем его родитель по той причине, что ему нет необходимости определять какой-либо механизм цикла. Этот тэг будет трактоваться, как совершенно новый тэг каждый раз, когда родительский тэг будет совершать итерацию, так что метод doAfterBody( ) и другие методы будут вызываться повторно.
Классы Тэгов
Определить, что лучше использовать для реализации вашего обработчика тэга - интерфейс или класс, может быть достаточно сложно. Помочь вам может диаграмма классов и интерфейсов, которая есть в вашем руководстве.
Приведенная ниже таблица описывает некоторые полезные интерфейсы в API обработчика тэга:
Интерфейс | Описание | Когда использовать |
Tag |
Основной обработчик Тэга, от которого должны наследоваться все другие классы и интерфейсы |
Используйте это, если хотите реализовать специфическое поведение для каждого метода, или если ваш обработчик тэга должен наследоваться от другого класса. В последнем случае, этот класс наследуется не напрямую, а через один из классов, которые его реализуют, например TagSupport.. |
IterationTag |
Определяет механизм цикла для тэга путем добавления метода doAfterBody() к интерфейсу Tag. |
Используйте это, если не хотите использовать интерфейс Tag, но вам требуется, чтобы ваш тэг был итеративный. В противном случае используйте его через один из классов-реализаций, например BodyTagSupport. |
BodyTag |
Определяет механизм для обработки содержимого тела тэга путем добавления методов setBodyContent( ) и doInitBody( ) к интерфейсу Tag. |
Используйте это, если вы хотите использовать интерфейс Tag, но вашему тэгу необходимо обрабатывать каким-либо образом содержимое своего тела. В противном случае используйте его через класс BodyTagSupport. |
TryCatchFinally |
Позволяет реализатору создать код обработки исключения, который перехватывает любое исключение, выбрасываемое в обработчике. |
Используйте это, если вам необходимо специфическое поведение (особенно при использовании finally) или если вам нужно, чтобы исключения не отображались в окне клиентского web браузера. |
Эта таблица описывает некоторые полезные классы в API обработчика тэгов:
Класс | Описание | Когда использовать |
TagSupport |
Простой класс, который реализует интерфейсы Tag и IterationTag и предоставляет полезное поведение по умолчанию для создания основных или итеративных тэгов. |
Используйте это, если хотите реализовать минимальный тэг или итеративный тэг, не делая много работы и вам не нужен класс обработчика для реализации других интерфейсов. |
BodyTagSupport |
Простой класс, который расширяет класс TagSupport и определяет поведение для обработки содержимого тела, реализуя интерфейс BodyTag. |
Используйте это, если хотите реализовать тэг или итеративный тэг, который управляет содержимым своего тела, и вам не нужен обработчик тэга, реализующий никакой другой интерфейс. |
Как только вы выбрали подходящий класс, с помощью которого будете реализовывать свой обработчик тэга, вам необходимо будет работать с классами-компаньонами из API.
Эта таблица описывает два класса-компаньона наиболее часто используемых обработчиками тэга.
Класс | Описание | Когда использовать |
BodyContent |
Класс, который инкапсулирует содержимое тела тэга и позволяет управлять им. |
Вам будет необходимо использовать этот класс, когда вы будете манипулировать содержимым тела вашего тэга. |
PageContext |
Класс, который позволяет хранить и получать доступ к атрибутам JSP страницы. Экземпляр этого класса, к которому вы получаете доступ в обработчике тэга, является тем же самым, который доступен через JSP страницу. |
Используйте этот класс для создания атрибутов и bean'ов, доступных в JSP странице. |
Некоторые Дополнительные Особенности Tag'а
Использование классов TagExtraInfo
Иногда переменные, которые вы хотите сделать доступными из вашего тэга для автора JSP страницы, могут быть немного сложны в определении в статическом TLD файле. Имена переменных могут зависеть от некоторых условий состояния, или вы можете пожелать подтвердить действительность JSP страницы. Например, вы можете пожелать убедиться, что автор JSP страницы не указал некорректных атрибутов или не поместил дочерний тэг в неверный родительский тэг. Для функциональности такого рода применение тэга <variable> в файле дескриптора библиотеки тэга более не адекватно.
В приведенном выше primenumbertag вместо применения тэга <variable> мы могли бы использовать следующее:
Этот тэг говорит контейнеру, что у вас есть полностью отдельный класс, описывающий ваши атрибуты за вас.
На самом деле все не так плохо, как это звучит, необходимый вам класс очень прост и вам, чаще всего, не нужно перекрывать более одного метода. В случае класса PrimeNumTag вы можете указать класс, называемый PrimeNumTagExtraInfo, в соответствии с приведенной ниже конвенцией и вот такой реализацией:
package cx2.tags;
import javax.servlet.jsp.tagext.TagExtraInfo;
import javax.servlet.jsp.tagext.TagData;
import javax.servlet.jsp.tagext.VariableInfo;
public class PrimeNumTagExtraInfo extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData data) {
VariableInfo[] vi = new VariableInfo[1];
vi[0] = new VariableInfo("value", "Integer", true,
VariableInfo.AT_BEGIN);
return vi;
}
}
Обратите внимание, что класс расширяет класс TagExtraInfo, находящийся в пакете javax.servlet.jsp.tagext. Метод getVariableInfo( ) создает массив объектов VariableInfo, описывающих имя, тип, должна или нет переменная быть декларирована и область видимости переменной. Это все соответствует элементам, перечисленным в тэге <variable>. Массив объектов VariableInfo имеет размер 1 потому, что есть только одна переменная.
Объект TagData используется в качестве параметра для метода getVariableInfo( ), но он разработан для использования в следующих реализациях. Некоторая часть функциональности предназначена для поддержки того, что пока не поддерживается в JSP.
Повторный Обзор Дескриптора Библиотеки
Если вы посмотрите еще раз на дескриптор библиотеки тэга, который мы использовали для приведенных выше тэгов, вы обнаружите, что вы знакомы с первыми двумя строками после прочтения главы об XML из этой книги. Тэг <taglib> понятен сам по себе, он определяет начало библиотеки тэга. Обычно у вас будет один такой тэг на каждый TLD дескриптор. Он имеет несколько вложенных элементов:
- tlibversion - определяет номер версии, который может помочь программисту применять некоторую проверку во время трансляции, получая возможность читать номер версии.
- jspversion - определяет номер версии, который помогает контейнеру определить, JSP какой версии предполагается использовать, так что если необходимо, можно отключить большинство дополнительных особенностей или выполнить специальную операцию, которую необходимо выполнить для обратной совместимости.
- shortname - определяет легкое в использование имя, которое может использоваться в GUI средах в качестве префикса к любому генерируемому тэгу или директиве...
- uri - определяет уникальный идентификатор для библиотеки тэга. Обычно это полезно определять в форме HTTP URL, как в примере этой главы, но это не значит, что будет найдена соответствующая web страница, не смотря на то что это уникально.
- displayname - определяет имя, читабельное для человека, которое будет полезно при отображении в панели инструментов в GUI среде разработки.
- small-icon - определяет не обязательный файл изображения, подходящий для среды разработки при отображении в панели инструментов.
- large-icon - определяет необязательный файл изображения, подходящий для среды разработки при отображении в панели меню.
- description - определяет описание библиотеки тэга, которое может использовать GUI среда разработки для описания этой библиотеки в панели инструментов или в меню.
- validator - определяет не обязательный класс, который может быть использован для проверки истинности тэга путем проверки его параметров и даже других тэгов на этой же JSP странице, чтобы убедиться, что автор JSP использует их корректно.
- listener - определяет не обязательный класс, который реализует класс слушателя для сервлета, созданного из JSP страницы. Класс должен реализовывать один из интерфейсов для слушателей сервлета, например javax.servlet.http.HttpSessionListener.
Тэг <taglib> должен иметь, как минимум, один вложенный элемент <tag>. Смысл в том, что вы вряд ли захотите иметь библиотеку тэга без тэгов. Она не будет полезной.
Сам элемент tag имеет несколько влеженных элементов, некоторые из них вы уже видели раньше в этой главе:
- name - определяет уникальное имя для этого тэга или действия.
- tagclass - определяет полностью квалифицированное имя класса, который реализует интерфейс обработчика тэга javax.servlet.jsp.tagext.Tag.
- teiclass - определяет не обязательный класс дополнительной информации, который содержит информацию для проверки действительности переменных этого тэга. Класс обязан реализовывать интерфейс javax.servlet.jsp.tagext.TagExtraInfo.
- bodycontent - Тип содержимого, находящегося в теле тэга, допустимы: пустое значение, JSP и tagdependant. Елси указан tagdependant, то содержимое будет передаваться без интерпретаций в обработчик тэга. Это может быть полезно при встраивании других языков, например SQL запросов.
- displayname - Короткое, понятное человеку имя, которое может быть отображено в GUI среде разработки.
- small-icon - определяет графический файл, подходящий для среды разработки при оторбажении в панели инструментов.
- large-icon - определяет графический файл, подходящий для среды разработки при отображении в панели меню.
- description - определяет дополнительную, не обязательную информацию, специфичную для тэга, и как он может быть использован с другими тэгами. Это выполняется путем предоставления класса, который может применять ограничения. Класс должен быть подклассом одной из реализаций тэга.
- example - определяет не обязательный пример использования тэга, подходящий для среды разработки при отображении в меню помощи.
Последние два субэлемента тэга <tag> могут сами иметь вложенные элементы.
variable - определяет информацию для переменной скрипта, определенной в этом тэге. Может быть заменено тэгом teiclass и предоставлено классом дополнительной информации тэга.
Вложенные элементы для variable:
- name-given - определяет имя переменной, как константу
- name-from-attribute - определяет имя атрибута, который даст имя переменной во время трансляции.
- variable-class - определяет тип переменной. Значение по умолчанию java.lang.String.
- declare - должна быть переменная задекларирована или нет. Значение по умолчанию True.
- scope - область видимости переменной. NESTED, AT_BEGIN или AT_END.
- attribute - определяет информацию для атрибута, используемого этим произвольным тэгом.
Вложенными элементами для attribute являются:
- name - определяет имя атрибута.
- required - определяет является ли атрибут обязательным или нет.
- rtexprevalue - определяет может или нет атрибут передаваться, как выражение, которое будет вычисляться во время выполнения.
- type - определяет тип атрибута. Значение по умолчанию - String, что наиболее полезно.
- description - не обязательное описание атрибута.
Развертывание Библиотеки Тэга
Развертывание библиотеки тэга является задачей администрирования, но, как и любая задача администрирования, чем больше вы понимаете требования для каждого приложения, тем легче она становится. В случае тэгов, есть несколько способов для развертывания библиотеки тэга и вы должны сделать выбор, какой из них больше всего подходит для вашего приложения. Это также зависит от того, будете ли вы использовать свою собственную библиотеку тэга или использовать библиотеку от третьих поставщиков.
Вы уже видели, что файл дескриптора библиотеки необходимо создавать для каждой библиотеки, и очевидно, что вы захотите поместить его куда-нибудь, где сервер сможет найти его. Вам также необходимо поместить Java классы где-нибудь, где контейнер сможет найти их.
Наиболее простой способ сделать это такой:
Поместите файл дескриптора библиотеки, или .tld файл, на сервер в директорий WEB-INF, в каталог вашего web приложения, например, ../webroot/tijtags/WEB-INF, используя символы-разделители пути, наиболее подходящие для вашей операционной системы.
Укажите расположение вашего .tld файла в вашей JSP странице, используя директиву taglib, например, <%@ taglib uri="/WEB-INF/tijtags.tld" prefix="tijtags" %>. Атрибут uri указывает относительный путь к файлу дескриптора библиотеки тэга.
Поместите ваш JSP файл, где он должен быть, а именно в jsp директорий.
Поместите ваши классы в директорий classes вашего каталога WEB-INF, например, ../webroot/tijtags/WEB-INF/classes/, убедившисть, что вы используете подкаталог с именем пакетов, как это делается для любого Java приложения.
Вот и все, что вам нужно сделать. Когда вы перезапустите ваш сервер, и обратитесь к JSP странице, например, набрав http://localhost/tijtags/index.jsp, здесь предполагается, что вы запускаете сервер локально, страница будет способна получить доступ к дескриптору библиотеки тэга и классам.
Это хорошо для тестирования, но более типичным случаем является хранения ваших классов в jar файле в директории /WEB-INF/lib. Если ваш файл дескриптора библиотеки находится в jar файле вместе с классами-обработчиками, то контейнер способен найти его, и будет различать тэги во время трансляции и работы.
Если вы развернули вашу библиотеку тэга, как релиз, или если вы используете библиотеку тэга сторонних производителей, вы захотите использовать более последовательную структуру директорий и вы можете пожелать держать все ваши библиотеки тэгов в одном месте. Это позволит вам разделять библиотеки тэга между различными web приложениями.
Чтобы сделать это, вы можете использовать второй подход, который реализуется посредством использования элемента <taglib> в файле web.xml вашего web приложения путем добавления строк:
<taglib> <taglib-uri>http://www.eckelobjects.com/tagtut/tags1.0</taglib-uri> <taglib-location>tijtags.tld</taglib-location> </taglib>
Здесь создается связка между URI и расположением файла дескриптора библиотеки тэга. Если такая связка существует, ваша JSP страница может ссылаться на библиотеку тэга по ее URI, а не через файл Дескриптора Библиотеки Тэга:
Это позволяет web администратору перемещать библиотеки тэга на сервере, хотя это и не обязательно, если не применяются web приложения. Если на сервере создана такая связка, любое web приложение будет способно создавать ссылку на библиотеку тэга посредством URI. Это имеет больше смысла, когда происходит разворачивание библиотек от третьих производителей, которые необходимо помещать в разделяемые каталоги, где они могут быть доступны любому web приложению.
Помните, для URI нет необходимости использовать форму HTTP URL, как это сделано выше, но это хороший способ гарантировать уникальность.
В приведенном выше примере вы заметите, что отображение между .tld файлом и URI указано в файле web.xml. Если ваши классы библиотеки тэга находятся в jar файле, это означает, что существует дескриптор библиотеки, который располагается вне jar файла. Это может быть неудобно, поэтому спецификация контейнера для тэгов определяет автоматическое связывание напрямую с jar файлом, содержащим дескриптор библиотеки тэга.
Чтобы использовать это, вам необходимо убедиться, что ваш дескриптор библиотеки тэга есть в файле, имеет название taglib.tld, и что он находится в jar файле библиотеки тега в директории META-INF. Если это так, то вы можете определить связывание в вашем web.xml файле следующим образом:
<taglib> <taglib-uri> http://www.eckelobjects.com/tagtut/tags1.0 </taglib-uri> <taglib-location>tijtags.jar</taglib-location> </taglib>
Обратите внимание, что jar файл указывается в качестве места расположения библиотеки тэга. Это, вероятно, лучший способ разворачивания библиотек тэга, он позволит вам разделять библиотеки между приложениями, обращаясь к библиотеке тэга по уникальному URI и храня файл дескриптора библиотеки тэга аккуратно упакованным вместе с классами.
На самом деле спецификация того, как JSP контейнер должен обрабатывать связку с библиотекой тэга, невероятно гибка. Контейнер будет пробовать и строить неявное связывание, если он найдет дескриптор вашей библиотеки тэга и не найдет явного связывания в вашем файле web.xml. Хотя, не рекомендуется, чтобы вы оставляли очень много работы контейнеру. Чем больше вы оставляете серверу, тем больший риск получаете в виде потери портируемости, когда, скажем, попробуете перенести ваше приложение на другой сервер приложений, вы также можете быть озабочены преодолением различия в поведении на разных платформах.
Использование Библиотек Тэга от Третьих Производителей
Теперь, когда вы понимаете как создавать и разворачивать библиотеки тэга, вы можете быть счастливы, обнаружив, что вы не можете создавать сои собственные библиотеки. Как и при разработки библиотек любого другого типа, всегда будет время, когда вы будете вынуждены писать свой собственный код, но уже существуют огромные и очень мощные библиотеки, готовые для использования, и вы всегда должны сначала убедиться, что вы не изобретаете колесо, когда погружаетесь в новую разработку.
Существующий JCP (Java Community Process) во время создания опубликовал первое приближение публичной Стандартной Библиотеки Тэгов JSP, JSTL, в которой определено огромное количество мощных тэгов различного назначения. Эта библиотека предоставляет стандартизированное решение для некоторых, наиболее часто встречающихся проблем с точки зрения разработчика, и будут рассмотрены обзорно.
Группа разработчиков Apache Jakarta имеет другую огромную коллекцию очень полезных тэгов, распространяемых свободно для разработчиков, которые сгруппированы под одним проектом "taglibs" и имеют то же самое назначение, что и JSTL. Хотя существует некоторая общая функциональность между двумя библиотеками, эта группа имеет более близкие отношения с JCP и, соответственно, производит превосходный Java продукт с открытым исходным кодом уже несколько лет. За проектами Jakarta всегда стоит следить.
Детальное рассмотрение этой библиотеки выходит за пределы интересов этой главы, но давайте взглянем на некоторые избранные популярные тэги, имеющиеся в библиотеке.
Вот несколько библиотечных тэгов, доступных в проекте Jakarta "taglibs", которые должны вам дать идею о том, какого рода вещи вы можете ожидать от библиотеки тэга.
Имя из TagLibrary | Функция | Пример Тэга |
Application |
Позволяет пользователю получать доступ к информации о JSP приложении |
existsAttribute - проверяет, существует ли атрибут |
DateTime |
Позволяет пользователю обрабатывать дату и время, используя локализацию и временную зону |
currentTime - текущее время в миллисекундах. |
DBTags |
Позволяет пользователю выполнять SQL запросы |
query - SQL запрос, встроенный в выражение для тэга. |
I18N |
Предоставляет функциональность для интернационализации |
bundle - определяет ресурс пакета |
Input |
Предоставляет утилиты для приема ввода от пользователя |
textarea - отображает многострочную область текста |
IO |
Предоставляет различные операции ввода и вывода |
request - позволяет вам вставлять результат в HTTP запрос |
JNDI |
Предоставляет тэги для соединения с Java Naming и Directory interface |
useDirContext - - создает объект DirContextObject |
Log |
Предоставляет тэги для облегчения логирования с помощью библиотеки log4j |
debug - отображает сообщения уровня отладки |
Mailer |
Предоставляет тэги для использования библиотеки JavaMail из JSP |
setrecipient - позволяет указание приемщика почтового сообщения |
Page |
Предоставляет тэги для доступа к контексту JSP страницы |
attributes - выполняет циклический просмотр всех атрибутов страницы. |
Regexp |
Предоставляет тэги для Регулярных Выражений |
regexp - создает скриптовую переменную из регулярного выражения. |
Session |
Предоставляет тэги для чтения или изменения информации о клиентской HttpSession |
isNew - булевский тэг, отображает, является ли сессия новой |
String |
Предоставляет тэги для манипулирования строками |
upperCase - конвертирует строку в верхний регистр. |
Xtags |
Предоставляет тэги для работы с XML. |
style - Выполняет XML преобразование данного XML документа. |
← | JavaServer Pages | Enterprise JavaBeans | → |