Страницы JSP
Как видно из приведенных в предыдущей главе листингов, большую часть сервлета занимают операторы вывода в выходной поток тегов HTML, формирующих результат — страницу HTML. Эти операторы почти без изменений повторяются из сервлета в сервлет. Возникла идея не записывать теги HTML в операторах Java, а, наоборот, записывать операторы Java в коде HTML с помощью тегов специального вида. Затем обработать полученную страницу препроцессором, распознающим все теги и преобразующим их в код сервлета Java.
Так получился язык разметок JSP (JavaServer Pages), расширяющий язык HTML тегами вида <% имя_тега атрибуты %>. С помощью тегов можно не только записать описания, выражения и операторы Java, но и вставить в страницу файл с текстом или изображением, вызвать объект Java, компонент JavaBean или компонент EJB.
Программист может даже расширить язык JSP своими собственными, как говорят, пользовательскими тегами (custom tags), которые сейчас чаще называют расширениями тегов (tag extensions).
В листинге 27.1 приведен пример JSP-страницы "Hello, World!" с текущей датой.
Листинг 27.1. Простейшая страница JSP
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ page contentType=Mtext/html;charset=windows-1251M %>
<%@ page import="java.util.*, java.text.*" %> <html><head><title> Простейшая страница JSP </title>
<META http-equiv=Content-Type
content="text/html; charset=windows-1251">
</head><body>
Hello, World!<p>
Сегодня <%= getFormattedDate() %>
</body></html>
String getFormattedDate(){
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm"); return sdf.format(new Date());
}
%>
Контейнер сервлетов расширили препроцессором, переводящим запись, подобную листингу 27.1, в сервлет. В контейнере сервлетов Tomcat такой препроцессор называется Jasper. Препроцессор срабатывает автоматически при первом обращении к странице JSP. Полученный в результате его работы сервлет тут же компилируется и выполняется. Откомпилированный сервлет затем хранится в контейнере, так же как и все сервлеты, и выполняется при следующих вызовах страницы JSP.
Для сервлетов и страниц JSP придумано общее название — Web-компоненты (Web Components). Контейнер сервлетов, расширенный средствами работы с JSP, называется Web-контейнером (Web Container или JSP Container). Приложение, составленное из сервлетов, страниц JSP, апплетов, документов HTML и XML, изображений и прочих документов, относящихся к приложению, называется Web-приложением (Web Application).
Весь статический текст HTML, называемый в документации JSP шаблоном HTML (template HTML), сразу направляется в выходной поток. Выходной поток страницы буферизуется. Буферизацию обеспечивает класс JspWriter, расширяющий класс Writer. Размер буфера по умолчанию 8 Кбайт, его можно изменить атрибутом buffer тега <%@ page>. Наличие буфера позволяет заносить заголовки ответа в выходной поток вперемешку с выводимым текстом. В буфере заголовки будут размещены перед текстом.
Таким образом, программисту достаточно написать страницу JSP, записать ее в файл с расширением jsp и установить файл в контейнер, подобно странице HTML, не заботясь о компиляции. При установке можно задать начальные параметры страницы JSP так же, как и начальные параметры сервлета.
В то время как создавался и развивался язык JSP, широкое распространение получил язык XML, который мы рассмотрим в главе 28. Для совместимости с ним все теги JSP продублированы элементами XML с именами из пространства имен http://java.sun.com/JSP/Page, получающего, как правило, префикс jsp:. Теги JSP, записанные в форме XML, сейчас называют действиями (actions) JSP.
Например, страницу JSP, представленную в листинге 27.1, можно написать в форме XML так, как это сделано в листинге 27.2.
Листинг 27.2. Простейшая страница JSP в форме XML
<j sp:root xmlns:j sp="http://java.sun.com/JSP/Page" version="2.0"> <j sp:directive.page
contentType="text/html;charset=windows-1251" />
<j sp:directive.page
import="j ava.util.Date, j ava.text.SimpleDateFormat" />
<j sp:text>
<![CDATA[
<html><head><title> Простейшая страница JSP </title>
<META http-equiv=Content-Type
content="text/html; charset=windows-1251">
</he ad><body>
Hello, World!<p>
Сегодня ]]>
</j sp:text> <j sp:expression>getFormattedDate()</j sp:expression>
<jsp:text>
<![CDATA[
</body></html> ]]>
</j sp:text>
<jsp:declaration>
String getFormattedDate(){
SimpleDateFormat sdf = new SimpleDateFormat("dd-MMMM-yyyy hh:mm"); return sdf.format(new Date());
}
</j sp:declaration>
</jsp:root>
Файл со страницей JSP, записанной в форме XML, обычно получает расширение jspx.
Некоторые теги — <jsp:forward>, <jsp:include>, <jsp:plugin>, <jsp:useBean>, <jsp:getProperty>, <j sp: setProperty> — всегда записывались в форме XML.
Вы уже знаете, что язык XML различает регистр букв. Теги XML записываются, как правило, строчными буквами. Значения атрибутов тегов обязательно записываются в кавычках или апострофах. У большинства элементов XML есть открывающий тег (start tag) с необязательными атрибутами, тело (body) и закрывающий тег (end tag), они имеют вид:
<jsp: тег атрибуты_тега >
Тело элемента или вложенные элементы XML </jsp:тег>
Тело элемента может быть пустым, тогда элемент выглядит так:
<j sp:тег атрибуты_тега ></j sp:тег>
Внимание!
Если между открывающим и закрывающим тегом есть хотя бы один пробел, то тело элемента уже не пусто.
Наконец, тело может отсутствовать, тогда закрывающий тег не пишется, а в открывающем теге перед закрывающей угловой скобкой ставится наклонная черта:
<jsp: тег атрибуты_тега />
Если у XML-элемента есть тело, то его открывающий тег заканчивается просто угловой скобкой, без наклонной черты.
Спецификация "JavaServer Pages Specification" не рекомендует смешивать в одном документе теги вида JSP с элементами XML. Страницу HTML, в которую вставлены теги вида <%...%>, она официально называет страницей JSP (JSP Page), а документ XML с тегами вида <jsp:.../> называет документом JSP (JSP Document). Файл со страницей JSP обычно получает имя с расширением jsp, а файл с документом JSP — имя с расширением jspx.
Документ JSP проходит еще одну стадию предварительной обработки, на которой он приводится в полное соответствие с синтаксисом XML. Это приведение включает в себя запись корневого элемента с пространством имен http://java.sun.com/JSP/Page и вставку элементов cdata. После приведения получается документ XML, официально называемый представлением XML (XML View).
Стандартные действия (теги) JSP
Набор стандартных тегов JSP довольно прост. При их написании следует помнить три правила:
? язык JSP различает регистр букв, как и язык Java;
? при записи атрибутов, после знака равенства, отделяющего имя атрибута от его значения, нельзя оставлять пробелы;
? значения атрибутов можно заносить не только в кавычки, но и в апострофы.
Будем записывать теги и в старой форме JSP, и в новой форме элементов XML. Комментарий на страницах JSP отмечается тегом
<%— Комментарий —%>
или тегом
<!-- Комментарий -->
Комментарий первого вида не передается клиенту. Все, что написано внутри него, не обрабатывается препроцессором. Комментарий второго вида переносится в формируемую HTML-страницу. Все JSP-теги, записанные внутри такого комментария, интерпретируются.
Объявления полей и методов Java записываются в теге
<%! Объявления %>
<jsp:declaration> Объявления </jsp:declaration>
После обработки препроцессором они будут полями и методами сервлета.
Выражение Java записывается в теге
<%= Выражение %>
<jsp:expression> Выражение </jsp:expression>
Выражение вычисляется, результат подставляется на место тега. Учтите, что в конце выражения не надо ставить точку с запятой, поскольку выражение, завершающееся точкой с запятой, — это уже оператор.
Фрагмент кода Java, называемый в JSP скриптлетом (scriptlet), который может включать в себя не только операторы, но и определения, записывается в теге
<% Скриптлет %>
<jsp:scriplet> Скриптлет </jsp:scriptlet>
Такой фрагмент после обработки препроцессором попадет в метод _jspService () создаваемого сервлета, являющийся оболочкой метода service ().
Включение файла во время компиляции производится тегом
<%@ include file="URL файла относительно контекста" %>
<jsp:directive.include file="URL файла относительно контекста" />
Общие свойства страницы JSP задаются тегом
<%@ page Атрибуты %>
<jsp:directive.page Атрибуты />
Все атрибуты здесь необязательны. В листингах 27.1 и 27.2 уже использованы атрибуты contentType и import этого тега. Другие атрибуты:
? pageEncoding=" Кодировка" — по умолчанию "ISO 8859-1";
? extends="Долное имя расширяемого класса" — суперкласс того класса, в который компилируется страница JSP;
? session=" true или false" — создавать ли сеанс связи с клиентом, по умолчанию
"true";
? buffer="Nkb или none " — размер выходного буфера, по умолчанию "8kb";
? autoFlush=" true или false" — автоматическая очистка буфера по его заполнении, по умолчанию "true"; если значение этого атрибута равно "false", то при переполнении буфера выбрасывается исключение;
? isThreadSafe=" true или false" одновременный доступ к странице нескольких клиентов, по умолчанию "true"; если этот атрибут равен "false", то полученный после компиляции сервлет реализует устаревший и не используемый сейчас интерфейс
SingleThreadModel;
? info="какой-то текст" — сообщение, которое можно будет прочитать методом
getServletInfo();
? errorPage=" URL относительно контекста" — адрес страницы JSP, которой посылается исключение и которая показывает сообщения, посылаемые исключением;
? isErrorPage=" true или false" — может ли данная страница JSP использовать объектисключение exception, или он будет переслан другой странице, по умолчанию
"false".
Два последних атрибута требуют разъяснения. Дело в том, что если страница JSP не обрабатывает исключение, а выбрасывает его дальше, то Web-контейнер формирует страницу HTML c сообщениями об исключении и посылает ее клиенту. Это мешает работе клиента. Атрибут errorPage позволяет вместо страницы HTML с сообщениями передать встроенный объект-исключение exception для обработки странице JSP, которую предварительно создал разработчик. Атрибут isErrorPage страницы-обработчика исключения должен равняться "true".
Пусть, например, имеется страница JSP:
<html><body>
<%@ page errorPage="myerrpage.jsp" %>
<%
String str = <%= request.getParameter("name") %>; int n = str.length();
%>
</body></html>
Ссылка str может оказаться равной null, тогда метод length() выбросит исключение. Контейнер создаст встроенный объект exception и передаст его странице myerrpage.jsp. Она может выглядеть так:
<html><body>
<%@ page isErrorPage="true" %>
При выполнении страницы JSP выброшено исключение:
<%= exception %>
</body></html>
На этом набор тегов вида <%...%> заканчивается. Остальные теги записываются только в форме элементов XML.
Включение файла на этапе выполнения или включение результата выполнения сервлета, если этот результат представлен страницей JSP, выполняется элементом
<jsp:include Атрибуты />
В этом теге может быть один или два атрибута. Обязательный атрибут
page="относительный URL или выражение JSP"
задает адрес включаемого ресурса. Здесь "выражение JSP" записывается в форме JSP
<%= выражение %> или в форме XML
<jsp:expression> выражение </jsp:expression>
В результате выражения должен получиться адрес URL. Чаще всего в качестве выражения используется обращение к методу getParameter(String).
Второй, необязательный, атрибут
flush="true или false"
указывает, очищать ли выходной буфер перед включением ресурса. Его значение по умолчанию "false".
Вторая форма элемента, include, содержит теги <jsp:param> в теле элемента (обратите внимание на отсутствие наклонной черты в конце открывающего тега):
<jsp:include Атрибуты >
Здесь записываются теги вида <jsp:param>
</jsp:include>
В теле элемента можно записывать произвольные параметры. Они имеют вид
<jsp:param name="имя? параметра"
value="значение параметра или выражение JSP" />
"Выражение JSP" записывается так же, как и в заголовке тега. Параметры передаются включаемому ресурсу как его начальные параметры, и их имена, разумеется, должны совпадать с именами начальных параметров ресурса.
Включаемый JSP-файл может быть оформлен не полностью, а содержать только отдельный фрагмент кода JSP. В таком случае его имя записывают обычно с расширением jspf (JSP Fragment).
Следующий стандартный тег <jsp:element> позволяет динамически определить какой-нибудь элемент XML. У него один обязательный атрибут name — имя создаваемого элемента XML. В его теле можно определить атрибуты создаваемого элемента XML с помощью элемента <jsp:attribute> и тело создаваемого элемента XML с помощью элемента <j sp:body>. Например:
<jsp:element name="${myElem}"
xmlns:j sp="http://java.sun.com/JSP/Page">
<j sp:attribute name="lang">${content.lang}</j sp:attribute>
<j sp:body>${content.body}</jsp:body>
</jsp:element>
Еще один простой стандартный тег <jsp:text> не содержит атрибутов. Его тело без всяких изменений передается в выходной поток. Например:
<j sp:text>
while(k < 10) {a[k]++; b[k++] = $1;}
</jsp:text>
Вы, наверное, заметили, что теги JSP не создают никакого кода инициализации сервлета, того, что обычно записывается в метод init() сервлета. Такой код при необходимости как-то инициализировать полученный после компиляции сервлет надо записать в метод j spInit () по следующей схеме:
<j sp:declaration>
public void jspInit(){
// Записываем код инициализации
}
</jsp:declaration>
Аналогично, завершающие действия сервлета можно записать в метод jspDestroy() по такой схеме:
<j sp:declaration>
public void jspDestroy(){
// Записываем код завершения
}
</jsp:declaration>
Язык записи выражений EL
Хотя на странице JSP можно записать любое выражение языка Java в теге <%=.. .%> или в элементе XML <jsp:expression>...</jsp:expression>, в JSP, начиная с версии 2.0, введен язык EL (Expression Language), предназначенный для записи выражений. Язык JSP EL появился в русле тенденции к изгнанию со страниц JSP чужеродного кода и замены его своими "родными" конструкциями.
В языке JSP EL выражения окружаются символами ${...}, например ${2 + 2}. Это окружение дает сигнал к вычислению заключенного в них выражения. Выражение, записанное без этих символов, не будет вычисляться и воспримется как простой набор символов.
В выражениях можно использовать данные типов boolean, long, float и строковые константы, заключенные в кавычки или апострофы. Значение null считается отдельным типом.
С данными этих типов можно выполнять арифметические операции +, -, *, /, %, сравнения ==, !=, <, >, <=, >=, логические операции !, &&, || и условную операцию ?:. Интересно, что сравнения "равно", "не равно", "меньше", "больше", "меньше или равно", "больше или равно" можно записать не только специальными символами, но и сокращениями eq, ne, lt, gt, le, ge слов "equal", "not equal", "less than", "greater", "less than or equal", "greater or equal". Это позволяет оставить знаки "больше" и "меньше" только для записи тегов XML. По аналогии операцию деления можно записать словом div, операцию взятия остатка от деления — словом mod, а логические операции — словами not, and и or.
В выражениях можно обращаться к переменным, например ${name}, полям и методам объектов, например ${pageContext.request.requestURI}. В выражениях языка JSP EL можно использовать следующие предопределенные объекты:
? pageContext — объект типа PageContext;
? pageScope — объект типа Map, содержащий атрибуты страницы и их значения;
? requestScope — объект типа Map, содержащий атрибуты запроса и их значения;
? sessionScope — объект типа Map, содержащий атрибуты сеанса и их значения;
? applicationScope — объект типа Map, содержащий атрибуты приложения и их значения;
? param — объект типа Map, содержащий параметры запроса, получаемые в сервлетах методом ServletRequest. getParameter (String name);
? paramValues — объект типа Map, содержащий параметры запроса, получаемые в сервлетах методом ServletRequest.getParameterValues(String name);
? header — объект типа Map, содержащий заголовки запроса, получаемые в сервлетах методом ServletRequest. getHeader (String name);
? headerValues — объект типа Map, содержащий заголовки запроса, получаемые в сервлетах методом ServletRequest.getHeaders(String name);
? initParam — объект типа Map, содержащий параметры инициализации контекста, получаемые в сервлетах методом ServletContext.getInitParameter(String name);
? cookie — объект типа Map, содержащий имена и объекты типа Cookie.
Наконец, в выражениях языка JSP EL можно записывать вызовы функций.
Встроенные объекты JSP
Каждая страница JSP может содержать в выражениях и скриптлетах девять готовых встроенных объектов, создаваемых контейнером JSP при выполнении сервлета, полученного после компиляции страницы JSP. Мы уже использовали объекты request и
exception. У этих объектов заданы определенные имена и типы. В большинстве случаев заданы не точные типы объектов, а их суперклассы и интерфейсы:
? request — объект типа ServletRequest, чаще всего это объект типа HttpServletRequest;
? response — объект типа ServletResponse, обычно это объект типа HttpServletResponse;
? config — объект типа ServletConfig;
? application — объект типа ServletContext;
? session — объект типа HttpSession;
? pageContext — объект типа PageContext;
? out — выходной поток типа JspWriter;
? page — произвольный объект класса Object;
? exception — исключение класса Throwable.
В листинге 27.3 приведена страница JSP, использующая скриптлеты и встроенные объекты для организации запроса к базе СДО, описанной в предыдущей главе.
Листинг 27.3. Страница JSP, использующая скриптлеты
<%@ page import="java.sql.*"
contentType="text/html;charset=windows-1251" %>
<html><head><title>Запрос к базе СДО</title></head>
<body>
<h2> Здравствуйте
<%= (request.getRemoteUser() != null ? ", " + request.getRemoteUser() : "") %>!
</h2><hr><p>
<%
try{
Connection conn =
DriverManager.getConnection(
(String)session.getValue("connStr"), "sdoadmin", "sdoadmin"); Statement st = conn.createStatement ();
ResultSet rs = st.executeQuery ("SELECT name, mark " +
"FROM students ORDER BY name");
if (rs.next()){
%>
<table border=1>
<tr>
<th width=200> <1>Ученик</1> </th>
<th width=100> <Й>Оценка</С> </th>
</tr>
<tr>
<td> <%= rs.getString(1) %> </td> <td> <%= rs.getInt(2) %> </td>
</tr>
<%
while (rs.next()){
%>
<tr>
<td> <%= rs.getString(1) %> </td>
<td> <%= rs.getInt(2) %> </td>
</tr>
<%
}
%>
</table>
<%
}else{
%>
<p> Извините, но сведений нет! </p>
<%
}
rs.close(); st.close();
}catch(SQLException e){
out.println(,,<p>Cшибка при выполнении запроса:"); out.println ("<pre>" + e + "</pre>");
}
%>
</body></html>
Обращение к компоненту JavaBean
Из страницы JSP можно обращаться к объектам Java, оформленным как компоненты JavaBeans. Это выполняется тегом
<jsp:useBean id="HM# экземпляра компонента"
[ scope="page или request или session или application" ]
Класс компонента
/>
"Имя экземпляра компонента" id определяет имя JavaBean, уникальное в заданной атрибутом scope области. По умолчанию принимается область page — текущая страница JSP и включенные в нее страницы.
Компонент хранится как атрибут контекста указанной атрибутом scope области и вызывается методом getAttribute () соответствующего контекста.
? Если атрибут scope равен "page", то компонент хранится как один из атрибутов объекта класса PageContext.
? Если атрибут scope равен "request", то компонент хранится как атрибут объекта типа
ServletRequest.
? Если атрибут scope равен "session", то компонент будет атрибутом объекта типа
HttpSession.
? Наконец, если атрибут scope равен "application", то компонент станет атрибутом типа ServletContext.
Определенное в атрибуте id имя используется при обращении к свойствам и методам компонента JavaBean. Обязательный атрибут "класс компонента" описывается одним из трех способов:
? class="полное имя класса" [ type="полное имя суперкласса" ]
? beanName="полное имя класса или выражение JSP"
type="полное имя суперкласса"
? type="полное имя суперкласса"
При обращении к компоненту JavaBean в теле элемента можно задавать и другие элементы.
Свойство (property) уже вызванного тегом <jsp:useBean> компонента JavaBean с именем id="myBean" устанавливается тегом
<jsp:setProperty name="myBean"
property="имя" value="строка или выражение JSP" />
или тегом
<jsp:setProperty name="myBean"
property="имя" param^'n^ параметра запроса" />
Во втором случае свойству компонента JavaBean дается значение, определенное параметром запроса, имя которого указано атрибутом param.
Третья форма этого тега
<jsp:setProperty name="myBean" property="*" />
применяется в тех случаях, когда имена всех свойств компонента JavaBean совпадают с именами параметров запроса вплоть до совпадения регистров букв.
Для получения свойств уже вызванного компонента JavaBean с именем "myBean" существует тег
<jsp:getProperty name="myBean" property^n^ свойства" />
В его атрибуте property уже нельзя записывать звездочку.
Выполнение апплета в браузере клиента
Если в браузере клиента установлен Java Plug-in, то в нем можно организовать выполнение апплета или компонента с помощью элемента <jsp:plugin type="bean или applet"
[code="имя класса апплета"]
[codebase= "каталог апплета"]
Прочие параметры заголовка тега
>
Здесь записываются необязательные параметры </jsp:plugin>
Как видно из этого описания, элемент <jsp:plugin> очень похож на тег <applet> языка HTML. Похожи и его атрибуты code и codebase, только в имени класса апплета или компонента надо обязательно указывать его расширение .class. Если атрибут codebase не указан, то по умолчанию понимается каталог, в котором лежит страница JSP. Прочие атрибуты заголовка тоже подобны параметрам тега <applet>:
? name="имя экземпляра";
? archive="список адресов URL архивов апплета";
? align="bottom или top, или middle, или left, или right";
? height="высота в пикселах или выражение JSP";
? width="ширина в пикселах или выражение JSP";
? hspace="горизонтальные поля в пикселах";
? vspace="вертикальные поля в пикселах";
? jreversion="версия JRE, по умолчанию 1.2";
? nspluginurl=,,полный адрес URL, с которого можно загрузить Java Plug-in для Netscape Communicator";
? iepluginurl=,,полный адрес URL, с которого можно загрузить Java Plug-in для Internet Explorer".
В теле элемента можно поместить любые параметры вида
<j sp:params>
<jsp:param name="имя?" value="значение или выражение JSP" />
</jsp:params>
которые будут переданы апплету или компоненту. Кроме того, в теле элемента допустимо указывать сообщение, которое появится в окне браузера, если апплет или компонент не удалось загрузить. Для этого используется элемент
<jsp:fallback> Текст сообщения </jsp:fallback>
Передача управления
Страница JSP имеет возможность передать управление другому ресурсу: странице JSP, сервлету или странице HTML. Это выполняется тегом
<jsp:forward page="адрес URL относительно контекста" />
содержащим адрес объекта, которому передается управление. Адрес может быть получен как результат вычисления выражения JSP. Управление не возвращается, и строки, следующие за тегом <j sp:forward>, не будут выполняться.
Ресурсу можно передать один или несколько параметров в теле элемента:
<jsp:forward page="адрес URL относительно контекста" >
<jsp:param name="имя?" value="значение или выражение JSP" />
</jsp:forward>
Пользовательские теги
Разработчик страниц JSP может расширить набор стандартных действий (тегов) JSP, создав свои собственные, как говорят, пользовательские теги (custom tags). Пользовательские теги организуются в виде целой библиотеки, даже если в нее входит только один тег. Описание каждой библиотеки хранится в отдельном XML-файле с расширением tld, называемым описателем библиотеки тегов TLD (Tag Library Descriptor). Этот файл хранится в каталоге WEB-INF данного Web-приложения или в его подкаталоге. Если Web-приложение упаковано в JAR-архив, то TLD-файлы, описывающие библиотеки пользовательских тегов этого приложения, находятся в каталоге META-INF архива.
В листинге 27.4 приведен пример простого TLD-файла, описывающего библиотеку пользовательских тегов с одним тегом head, реализуемым классом HeadTag. Полное объяснение использованных в нем элементов XML вы получите в следующей главе.
Листинг 27.4. Описатель TLD библиотеки тегов
<taglib xmlns="http://java.sun.com/xml/ns/j 2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee web-jsptaglibrary 2 0.xsd" version="2.0">
<tlib-version>1.0</tlib-version>
<short-name></short-name>
<uri>/sdo</uri>
<tag>
<name>head</name>
<tag-class>sdotags.HeadTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>size</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
На странице JSP перед применением пользовательских тегов следует сослаться на библиотеку тегом
<%@ taglib uri="адрес URI библиотеки"
prefix="префикс тегов библиотеки" %>
Например, если на странице JSP написан тег
<%@ taglib uri="/WEB-INF/sdotaglib.tld" prefix="sdo" %>
то на ней можно использовать теги вида <sdo: head />.
У этого тега нет прямого XML-эквивалента. Префикс тегов библиотеки и ее адрес определяются в форме XML как пространство имен атрибутом xmlns в элементе <j sp: root>. Например, библиотека с префиксом тегов sdo и адресом /WEB-INF/sdotaglib.tld описывается так:
<j sp:root
xmlns:j sp="http://java.sun.com/JSP/Page" xmlns:sdo="/WEB-INF/sdotaglib.tld" version="2.0"
>
Страница JSP </jsp:root>
Необязательный элемент <jsp:root>, если он присутствует, должен быть корневым элементом документа XML.
Как видите, к каждой странице JSP всегда подключается библиотека с префиксом тегов jsp. Стандартные (core) действия JSP входят в эту библиотеку. Префиксы jsp, jspx, java, j avax, servlet, sun, sunw зарезервированы корпорацией Sun Microsystems, их нельзя употреблять в библиотеках пользовательских тегов.
В конфигурационном файле web.xml можно создать псевдонимы адреса URI библиотеки с помощью элемента <taglib>, например:
<taglib>
<taglib-uri>/sdo</taglib-uri>
<taglib-location>/WEB-INF/sdotaglib.tld</taglib-location>
</taglib>
После этого на странице JSP можно написать ссылку на библиотеку так:
<%@ taglib uri="/sdo" prefix="sdo" %>
Псевдоним может быть указан и в TLD-файле, в элементе <uri>, как показано в листинге 27.4. В этом случае контейнер, обнаружив на странице JSP тег <%@ taglib %> с псевдонимом, просматривает TLD-файлы в поисках этого псевдонима в их элементах <uri>. Найдя псевдоним, контейнер связывает его с путем к TLD-файлу, содержащему псевдоним. Поиск TLD-файлов происходит только в подкаталогах каталога WEB-INF и во всех JAR-архивах, а именно в каталогах META-INF, находящихся в JAR-архивах.
Каждый тег создаваемой библиотеки реализуется классом Java, называемым в документации обработчиком тега (tag handler). Обработчик тега должен реализовать интерфейс Tag, а если у тега есть тело, которое надо выполнить несколько раз, то нужно реализовать его расширение — интерфейс IterationTag. Если же тело пользовательского тега требует предварительной обработки, то следует использовать расширение интерфейса IterationTag — интерфейс BodyTag. Эти интерфейсы собраны в пакет j avax. servlet. j sp.tagext. В нем есть и готовые реализации указанных интерфейсов — класс TagSupport, реализующий интерфейс IterationTag, и его расширение — класс BodyTagSupport, реализующий интерфейс BodyTag.
Итак, для создания пользовательского тега без тела или с телом, но не требующим предварительной обработки, удобно расширить класс TagSupport, а для создания тега с телом, которое надо предварительно преобразовать, — класс BodyTagSupport.
Классы, реализующие библиотеку тегов, хранятся в каталоге WEB-INF/classes, а если они упакованы в JAR-архив — то в каталоге WEB-INF/lib своего Web-приложения. Если библиотека разделяется несколькими Web-приложениями, то она хранится в каком-нибудь общем каталоге, например common/lib. Соответствие между именами тегов и классами, реализующими их, указывается в TLD-файле, описывающем библиотеку, в XML-элементе <tag>. В этом же элементе, во вложенном XML-элементе <attribute>, описываются атрибуты открывающего тега. Например:
<tag>
<name>reg</name>
<tag-class>sdotags.RegTag</tag-class>
<body-content>EMPTY</body-content>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>j ava.lang.String</type>
</attribute>
</tag>
Если после этого на странице JSP написать пользовательский тег <sdo:reg>, например:
<sdo:reg name="%=request.getParameter("name") %" />
или с помощью языка JSP EL:
<sdo:reg name="${param.name}" />
то для обработки данного тега будет создан объект класса RegTag из пакета sdotags.
Класс-обработчик пользовательского тега
Как уже было сказано ранее, класс-обработчик элемента, не имеющего тела, или тело которого не требует преобразований, должен реализовать интерфейс Tag или расширить класс TagSupport. Если тело элемента перед посылкой клиенту надо преобразовать, то классу-обработчику следует реализовать интерфейс BodyTag или расширить класс BodyTagSupport. В случае простых преобразований можно реализовать интерфейс SimpleTag или расширить класс SimpleTagSupport.
Основной метод интерфейса Tag
public int doStartTag();
выполняет действия, предписанные открывающим тегом. К нему сервлет обращается автоматически, начиная обработку элемента. Метод должен вернуть одну из двух констант интерфейса Tag, указывающих на дальнейшие действия:
? eval_body_include — обрабатывать тело элемента;
? skip_body — не обрабатывать тело элемента.
После завершения этого метода сервлет обращается к методу
public int doEndTag();
который выполняет действия, завершающие обработку пользовательского тега. Метод возвращает одну из двух констант интерфейса Tag:
? eval_page — продолжать обработку страницы JSP;
? ski p_page — завершить на этом обработку страницы JSP.
Интерфейс IterationTag добавляет метод
public int doAfterTag();
позволяющий обработать повторно тело пользовательского тега. Он будет выполняться перед методом doEndTag (). Если метод doAfterTag () возвращает константу EVAL_BODY_AGAI N интерфейса IterationTag, то тело элемента будет обработано еще раз, если константу ski p_body — обработка тела не станет повторяться.
Интерфейс BodyTag позволяет буферизовать выполнение тела элемента. Буферизация производится, если метод doStartTag() возвращает константу EVAL_BODY_BUFFERED интерфейса BodyTag. В таком случае перед обработкой тела тега контейнер обращается к методу
public void doInitBody();
который может выполнить различные предварительные действия.
У этих методов нет аргументов, они получают информацию из объекта класса PageContext, который всегда создается Web-контейнером для выполнения любой страницы JSP. При реализации интерфейса Tag или BodyTag данный объект можно получить методом getPageContext ( ) класса JspFactory, предварительно получив объект класса JspFactory его собственным статическим методом getDefaultFactory(). Сложность таких манипуляций — еще один аргумент для того, чтобы не реализовывать интерфейсы, а расширять класс TagSupport или класс BodyTagSupport.
Итак, для создания пользовательского тега без тела или с телом, не требующим обработки, удобнее всего расширить класс TagSupport, а для создания пользовательского тега с обработкой тела — расширить класс BodyTagSupport. При этом разработчик получает в свое распоряжение объект класса PageContext просто в виде защищенного поля
pageContext.
Описанные ранее методы реализованы в классе TagSupport очень просто:
public int doStartTag() throws JspException{ return SKIP_BODY;
}
public int doEndTag() throws JspException{ return EVAL_PAGE;
}
public int doAfterBody() throws JspException{ return SKIP BODY;
В подклассе BodyTagSupport реализация метода dostartTag () немного изменена:
public int doStartTag() throws JspException{ return EVAL_BODY_BU FFERED;
}
Метод doinitBody () оставлен пустым.
Итак, в самом простом случае достаточно расширить класс TagSupport, переопределив метод doStartTag (). Пусть, например, определен пользовательский тег без аргументов и без тела, всего лишь отправляющий клиенту сообщение:
<sdo:info />
Реализующий его класс может выглядеть так, как показано в листинге 27.5.
Листинг 27.5. Класс простейшего пользовательского тега
package sdotags;
import javax.servlet.jsp.*;
import j avax.servlet.jsp.tagext.*;
public class InfoTag extends TagSupport{
public int doStartTag() throws JspException{
pageContext.getOut().print(,,Библиотека тегов СДО."); return SKIP_BODY;
}
}
Исходный текст листинга 27.5 надо откомпилировать обычным образом и установить в контейнер так же, как устанавливается сервлет. Проследите за правильным соответствием пакетов Java и каталогов файловой системы: в каталоге WEB-INF/classes должен быть подкаталог sdotags с файлом InfoTag.class.
Еще проще эти действия выполняются с помощью метода doTag( ) интерфейса SimpleTag, реализованного в классе SimpleTagSupport. У данного метода нет аргументов и возвращаемого значения, он объединяет действия, обычно выполняемые методами
doStartTag() и doEndTag().
Пользовательский тег с атрибутами
Для каждого атрибута открывающего тега надо определить свойство JavaBean, т. е. поле с именем, совпадающим с именем атрибута, и методы доступа getXxx() и setXxx(). Например, немного ранее (см. разд. "Пользовательские теги” данной главы) мы определили пользовательский тег
<sdo:reg name="имя" />
с одним атрибутом name. Класс RegTag, содержащийся в листинге 27.6, реализует этот тег.
Листинг 27.6. Пользовательский тег с атрибутом
package sdotags;
import javax.servlet.jsp.*;import javax.servlet.jsp.tagext.*;
public class RegTag extends TagSupport{
private String name;
public String getName(){ return name;
}
public void setName(String name){ this.name = name;
}
public int doStartTag() throws JspException{ if (name == null)
name = pageContext.getRequest().getParameter("name"); registrate(name); return SKIP_BODY;
}
}
Пользовательский тег с телом
Если у пользовательского тега есть тело, то при описании тега в TLD-файле в элементе <body-content> вместо слова empty следует написать слово jsp или вообще не писать этот элемент, поскольку его значение jsp принимается по умолчанию.
У тела элемента <body-content> могут быть еще два значения. Значение tagdependent применяется, если содержимое тела тега написано не на языке JSP, а на каком-то другом языке, например это запрос на языке SQL. Значение scriptless показывает, что в теге нет скриптлетов.
Если содержимое тела тега не нужно обрабатывать, а надо только отправить клиенту, то при создании его обработчика достаточно реализовать интерфейс Tag или расширить класс TagSupport. Если метод dostartTag() обработчика вернет значение eval_body_include, то все тело тега будет автоматически отправлено в выходной поток.
Пусть, например, в файле sdotaglib.tld определен пользовательский тег head:
<tag>
<name>head</name>
<tag-class>sdotags.HeadTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>size</name>
<required>false</required> <rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
Этот тег реализован классом HeadTag, описанным в листинге 27.7.
Листинг 27.7. Пользовательский тег с простым телом
package sdotags;
import javax.servlet.jsp.*; import j avax.servlet.jsp.tagext.*;
public class HeadTag extends TagSupport{
private String size = "4";
public String getSize(){ return size;
}
public void setSize(String size){ this.size = size;
}
public int doStartTag(){ try{
JspWriter out = pageContext.getOut(); out.print("<font size="" + size + "">"); }catch(Exception e){
System.err.println(e);
}
return EVAL_BODY_INCLUDE;
}
public int doEndTag(){ try{
JspWriter out = pageContext.getOut(); out.print("</font>");
}catch(Exception e){
System.err.println(e);
}
return EVAL_PAGE;
}
}
После этого на странице JSP можно писать пользовательский тег
<sdo:head size = "2" >
Сегодня <jsp:expression>new java.util.Date()</jsp:expression>
</sdo:head>
Текст, написанный в его теле, будет выведен у клиента шрифтом указанного размера.
Обработка тела пользовательского тега
Если тело пользовательского тега требует обработки, то его класс-обработчик должен реализовать интерфейс BodyTag или расширить класс BodyTagSupport. Метод dostartTag ( ) должен вернуть значение eval_body_buffered. После завершения метода dostartTag(), если тело тега не пусто, контейнер вызовет метод doInitBody(), который может выполнить предварительные действия перед обработкой содержимого тела пользовательского тега. Далее контейнер обратится к методу doAfterBody(), в котором и надо проделать обработку тела тега, поскольку к этому моменту тело тега будет прочитано и занесено в объект класса BodyContent.
Класс BodyContent расширяет класс JspWriter, значит, формально является выходным потоком. Однако его удобнее рассматривать как хранилище информации, полученной из тела тега.
Объект класса BodyContent создается после каждой итерации метода doAfterBody( ), и все эти объекты хранятся в стеке.
Ссылку на объект класса BodyContent можно получить двумя способами: методом
public BodyContent getBodyContent();
класса BodyTagSupport или, используя объект pageContext, следующим образом:
BodyContent bc = pageContext.pushBody();
Содержимое тела тега можно прочитать из объекта класса BodyContent тоже двумя способами: или получить ссылку на символьный входной поток методом
public Reader getReader();
или представить содержимое объекта в виде строки методом
public String getString();
После обработки прочитанного содержимого его надо отправить в выходной поток out методом
public void writeOut(Writer out);
Выходной поток out, аргумент этого метода, выводит информацию в стек объектов класса BodyContent. Поэтому его можно получить двумя способами: методом
public JspWriter getPreviousOut();
класса BodyTagSupport или методом
public JspWriter getEnclosingWriter();
класса BodyContent.
Приведем пример. Пусть тег query, описанный в TLD-файле sdotaglib.tld следующим образом:
<tag>
<name>query</name>
<tag-class>sdotags.QueryTag</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>size</name> <required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
содержит в своем теле SQL-запросы, например:
<sdo: query>
SELECT * FROM students </sdo:query>
В листинге 27.8 приведен фрагмент обработчика этого тега.
Листинг 27.8. Пользовательский тег обработки SQL-запросов
package sdotags;
import java.sql.*;
import javax.servlet.jsp.*;
import j avax.servlet.jsp.tagext.*;
public class QueryTag extends BodyTagSupport{
private Connection conn; private ResultSet rs;
public int doStartTag(){
// . . .
return EVAL_BODY_BU FFERED;
}
public void doInitBody(){
conn = DriverManager.getConnection(. . .); // Проверка соединения
}
public int doAfterBody(){
BodyContent bc = getBodyContent(); if (bc == null) return SKIP BODY;
String query = bc.getString();
try{
Statement st = conn.createStatement(); rs = st.executeQuery(query);
// Обработка результата запроса
JspWriter out = bc.getEnclosingWriter();
out.print("Вывод результатов"); }catch(Exception e){
System.err.println(e);
return SKIP_BODY;
}
public int doEndTag(){ conn = null; return EVAL_PAGE;
}
}
Обработка взаимодействующих тегов
Часто пользовательские теги, расположенные на одной странице JSP, должны взаимодействовать друг с другом. Например, тело тега может содержать другие, вложенные, теги JSP. Они могут быть самостоятельными по отношению к внешнему тегу или зависеть от него. Так, например, в языке HTML тег <tr> может появиться только внутри тега <table>. При этом атрибуты тега <table> могут использоваться в теге <tr>, а также могут быть переопределены внутри него. Таких примеров много в языке XML.
В языке JSP тоже могут появиться теги, зависящие друг от друга. Например, мы можем определить тег
<sdo:connection source="источник данных" >
устанавливающий соединение с базой данных, указанной в атрибуте source. Внутри этого тега допустимо обращение к базе данных, например:
<sdo:connection source="SDO" >
<sdo:query >
SELECT * FROM students </sdo:query>
<sdo:insert>
INSERT INTO students (name) VALUES ('Иванов')
</sdo:insert>
</sdo:connection>
Конечно, вложенные теги можно реализовать вложенными классами-обработчиками или расширениями внешних классов. Но в классах-обработчиках пользовательских тегов есть свои средства. Внешний и вложенный теги реализуются отдельными классами, расширяющими классы TagSupport или BodyTagSupport. Описания тегов в TLD-файле тоже не вложены, они записываются независимо друг от друга, например:
<tag>
<name>connection</name>
<tag-class>sdotags.ConnectionTag</tag-class>
</tag>
<tag>
<name>query</name>
<tag-class>sdotags.QueryTag</tag-class>
</tag>
<tag>
<name>insert</name>
<tag-class>sdotags.InsertTag</tag-class>
</tag>
Для связи обработчиков тегов используются их методы. Сначала обработчик вложенного тега задает себе внешний, "родительский" тег parent методом
public void setParent(Tag parent);
Затем обработчик вложенного тега может обратиться к обработчику внешнего тега методом
public Tag getParent();
Более общий метод
public static final Tag findAncestorWithClass(Tag from, Class class);
позволяет обратиться к обработчику тега, не обязательно непосредственно окружающего данный тег. Первый аргумент этого метода чаще всего просто this, а второй аргумент должен реализовать интерфейс Tag или его расширения.
Итак, все вложенные теги могут обратиться к полям и методам внешних тегов и взаимодействовать с их помощью.
Допустим, класс ConnectionTag, реализующий пользовательский тег connection, устанавливает соединение с источником данных, как показано в листинге 27.9.
Листинг 27.9. Реализация тега соединения с базой данных
public class ConnectionTag extends TagSupport{
private Connection conn;
public Connection getConnection(){ return conn;
}
public int doStartTag(){
Connection conn = DriverManager.getConnection(url, user, password);
if (conn == null) return SKIP_BODY; return EVAL_BODY_INCLUDE;
}
public int doEndTag(){ conn = null; return EVAL_BODY;
}
}
Класс QueryTag, реализующий тег query, может воспользоваться объектом conn, как показано в листинге 27.10.
Листинг 27.10. Реализация связанного тега
public class QueryTag extends BodyTagSupport{
private ConnectionTag parent;
public int doStartTag(){
parent = (ConnectionTag)findAncestorWithClass(this, ConnectionTag.class);
if (parent == null) return SKIP BODY; return EVAL_BODY_INCLUDE;
}
public int doAfterBody(){
Connection conn = parent.getConnection();
// Прочие действия
return SKIP_BODY;
}
}
Другой способ сделать объект obj доступным для нескольких тегов — указать его в виде атрибута какого-нибудь контекста. Это выполняется методом
public void setAttribute(String name, Object obj);
класса PageContext. Контекст этого атрибута — страница, на которой он определен. Если область действия атрибута надо расширить, то используется метод
public void setAttribute(String name, Object obj, int scope);
того же класса. Третий аргумент данного метода задает область действия атрибута — это одна из констант: page_scope, application_scope, request_scope, session_scope.
Значение атрибута всегда можно получить методами
public Object getAttribute(String name, int scope); public Object getAttribute(String name);
класса PageContext.
Для удобства работы с атрибутом обработчик тега может создать переменную, известную в области действия атрибута и доступную для других тегов в этой области (scripting variable). Она будет содержать ссылку на созданный атрибут. Переменную можно определить не только по атрибуту контекста, но и по атрибуту открывающего тега. Для определения переменной есть два способа.
Первый способ — указать в TLD-файле элемент <variable>, описывающий переменную. В этот элемент вкладывается хотя бы один из двух элементов:
? <name-given> — постоянное имя переменной;
? <name-from-attribute> — имя атрибута, значение которого будет именем переменной.
Остальные вложенные элементы необязательны:
? <variable-class> класс переменной, по умолчанию String;
? <declare> — создавать ли новый объект при обращении к переменной, по умолчанию
true;
? <scope> — область действия переменной, одна из трех констант:
• nested — между открывающим и закрывающим тегами, т. е. в методах
doStartTag (), dolnitBody (), doEndBody (); принимается по умолчанию;
• at_begin — от открывающего тега до конца страницы;
• at_end — после закрывающего тега до конца страницы.
Например:
<tag>
<name>tagname</name>
<tag-class>sdotags.SomeTag</tag-class>
<variable>
<name-given>varname</name-given>
<variable-class>j ava.lang.String</variable-class>
<declare>true</declare>
<scope>AT_END</scope>
</variable>
</tag>
Второй способ — определить объект, содержащий ту же самую информацию, что и элемент <variable>. Данный объект создается как расширение класса TagExtraInfo.
Вот как выглядит это расширение для примера, приведенного ранее:
public class MyTei extends TagExtraInfo{
public VariableInfo[] getVariableInfo(TagData data){ return new VariableInfo[]{
new VariableInfo(data.getAttributeString("id"),
"java.lang.String", true, VariableInfo.AT END)
};
}
}
Класс MyTei описывается в TLD-файле в элементе <tei-class> следующим образом:
<tag>
<name>tagname</name>
<tag-class>sdotags.SomeTag</tag-class>
<tei-class>sdotags.MyTei</tei-class>
<bodycontent>JSP</bodycontent>
</tag>
У этого способа больше возможностей, чем у элемента <variable>. Класс TagExtraInfo имеет в своем распоряжении экземпляр класса TagData, содержащий сведения о теге, собранные из TLD-файла. Для удобства использования этого экземпляра в классе TagExtraInfo есть логический метод
public boolean isValid(TagData info);
Его можно применять для проверки атрибутов тега на этапе компиляции страницы JSP.
Обработка исключений в пользовательских тегах
Ранее уже говорилось о том, что обработку исключения, возникшего на странице JSP, можно перенести на другую страницу, указанную атрибутом errorPage тега <%@ page %>. В пользовательских тегах можно облегчить обработку исключения, реализовав интерфейс TryCatchFinally.
Интерфейс TryCatchFinally описывает всего два метода:
public void doCatch(Throwable thr); public void doFinally();
Метод doCatch () вызывается автоматически контейнером при возникновении исключения в одном из методов doStartTag(), doInitBody(), doAfterTag(), doEndTag() обработчика, реализующего интерфейс TryCatchFinally. Методу doCatch () передается созданный объект-исключение. Метод doCatch () сам может выбросить исключение.
Метод doFinally ( ) выполняется во всех случаях после метода doEndTag( ). Он уже не может выбрасывать исключения.
Например, при реализации тега <sdo:connection> допускается использование методов интерфейса TryCatchFinally для отката транзакции следующим образом:
public class ConnectionTag extends TagSupport implements TryCatchFinally{
private Connection conn;
// Прочие поля и методы класса
public void doCatch(Throwable t) throws Throwable{ conn.rollback();
throw t;
}
public void doFinally(){ conn.close();
}
}
Обработка тегов средствами JSP
Уже упоминавшаяся тенденция к изгнанию со страниц JSP всякого чужеродного кода, даже кода Java, привела к тому, что для обработки пользовательского тега вы можете вместо класса Java написать страницу JSP, описав на ней действия тега. Имя файла с такой страницей-обработчиком JSP получает расширение tag, а если она оформлена как документ XML, то расширение tagx. Файл, содержащий отдельный фрагмент страницы-обработчика, получает имя с расширением tagf.
Например, тег <sdo:info />, действие которого мы показали в листинге 27.5, можно обработать следующей, очень простой, страницей JSP, записанной в tag-файл с именем info.tag:
<j sp:root
xmlns:j sp="http://j ava.sun.com/JSP/Page"
version="2.0" >
Библиотека тегов СДО.
</jsp:root>
Поскольку элемент <jsp:root> необязателен, весь файл info.tag может состоять только из одной строчки:
Библиотека тегов СДО.
На странице-обработчике пользовательских тегов можно записывать любые теги JSP, кроме директивы page. Вместо нее указывается специально введенная для этой цели в язык JSP директива tag. Например:
<%@ tag body-content="scriptless">
Второе отличие заключается в том, что в директиве <%@ taglib %> следует записывать не атрибут uri, а атрибут tagdir, в котором указывается каталог с tag-файлами, например:
<%@ taglib tagdir="/WEB-INF/tags" prefix="sdo" %>
Атрибуты обрабатываемого пользовательского тега описываются еще одной специально введенной директивой attribute, аналогичной элементу <attribute> TLD-файла. Пусть, например, на странице JSP применяется пользовательский тег с двумя атрибутами:
<sdo:reg name="Иванов" age="25"/>
В файле reg.tag, обрабатывающем этот тег, можно написать
<%@ attribute name="name" required="true" fragment="false" rtexprvalue="false"
%>
<%@ attribute name="age" type="j ava.lang.Integer"
%>
Если тип type атрибута не указан, то по умолчанию он считается строкой класса
j ava.lang.String.
Типом атрибута может быть фрагмент JSP, т. е. объект класса javax.servlet.jsp.tagext. JspFragment. В таком случае атрибуты type и rtexprvalue не указываются, а атрибут
fragment="true".
После этого описания значения атрибутов, введенные в обрабатываемый тег, в примере они равны "Иванов" и "25", можно использовать в файле reg.tag как ${name} и $ {age}.
Третья, специально введенная директива variable, аналогичная элементу <variable> TLD-файла, позволяет описать переменную на странице-обработчике. Например:
<%@ variable name-given="x"
variable-class="j ava.lang.Integer"
scope="NESTED"
declare="true"
%>
Область действия переменной определяется атрибутом scope, таким же, как описанный в разд. "Обработка взаимодействующих тегов” данной главы одноименный элемент TLD-файла.
Значения атрибутов, являющихся фрагментами страниц JSP, и тело пользовательского тега не вычисляются Web-контейнером, а передаются tag-файлу.
Для того чтобы заставить Web-контейнер выполнить значение атрибута, являющегося фрагментом страницы JSP, в tag-файле нужно использовать стандартный тег <jsp:invoke>. Пусть, например, атрибут price описан следующим образом:
<%@ attribute name="price"
fragment="true"
%>
Для вычисления его значения Web-контейнером в tag-файле следует написать:
<jsp:invoke fragment="price"/>
Если же надо заставить Web-контейнер выполнить не фрагмент, а все тело пользовательского тега, то в tag-файле записывается стандартный тег <j sp:doBody/>.
Tag-файлы не требуют никакого TLD-описания, если файл, содержащий страницу-обработчик пользовательского тега, хранится в каталоге WEB-INF/tags/ или в его подкаталогах. Другое место хранения tag-файлов должно быть описано в TLD-файле элементом <tag-file>. Например:
<taglib>
<tag-file>
<name>info</name>
<path>/WEB-INF/sdo/tags/info.tag</path>
</tag-file>
</taglib>
Если Web-приложение упаковано в JAR-архив, то tag-файлы должны храниться в каталоге META_INF/tags/ архива или в его подкаталогах.
Стандартные библиотеки тегов JSTL
Несмотря на недолгую историю языка JSP, его возможность создания пользовательских тегов была использована многими фирмами и отдельными разработчиками. Уже создано множество библиотек пользовательских тегов. Их обзор можно посмотреть, например, на сайте http://www.servletsuite.com/jsp.htm. В рамках уже не раз упоминавшегося проекта Jakarta создана мощная и широко применяемая библиотека пользовательских тегов Struts, которую можно скопировать с сайта http://struts.apache.org/. К сожалению, рамки нашей книги не позволяют дать ее описание.
Корпорация Sun Microsystems создала стандартные библиотеки пользовательских тегов, носящие общее название JSTL (JSP Standard Tag Library). Их реализации составляют пакет javax.servlet.jsp.jstl и его подпакеты. Справку о том, где найти самую последнюю реализацию, можно получить по адресу http://www.oracle.com/technetwork/ java/index-jsp-135995.html. Впрочем, библиотеки JSTL входят в стандартную поставку
Java EE SDK. Всю библиотеку JSTL составляют два JAR-архива: jstl-1.2.jar и standard. j ar. Номер версии JSTL, конечно, может быть другим.
В пакет JSTL входят пять библиотек пользовательских тегов: core, xml, fmt, sql и fn.
Библиотека core
Библиотека core описывается на странице JSP тегом
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
В нее входят теги, создающие подобие языка программирования. Они призваны заменить операторы Java, тем самым устраняя скриптлеты со страниц JSP.
Тег <c:out> выводит значение своего обязательного атрибута value в выходной поток out. Если оно пусто, то будет выведено значение атрибута default, если, конечно, этот атрибут написан.
Например:
Здравствуйте, уважаемый <c:out value="${name}" default=,,господин,, />!
Тег <c:set> устанавливает значение переменной, имя которой задается атрибутом var, а область действия — атрибутом scope. Например:
<c:set var="name" scope="session" value="${param.name}"/>
или, что то же самое:
<c:set var="name" scope="session">
${param.name}
</c:set>
Тег <c:remove> удаляет ранее определенные переменные, например:
<c:remove var="name" scope="session"/>
Тег <c:url> тоже устанавливает переменную, но ее значение должно быть адресом, записанным в форме URL. Например:
<c:url var="bhv" value="http://www.bhv.ru"/>
или, что то же самое:
<c:url var="name"> http://www.bhv.ru </c:url>
После этого переменную bhv можно использовать для создания сеанса связи с пользователем, как это делалось в предыдущей главе, или записывать в гиперссылках HTML:
<a href="${bhv}">Издательство</a>
К создаваемому адресу URL можно добавить параметры вложенным тегом <c:param>, например:
<c:url var="bhv" value="http://www.bhv.ru"> <c:param name="id" value="${book.id}"/>
<c:param name="author" value="${book.author}"/>
</c:url>
Тег <c:if> проверяет условие, записанное в его атрибуте test логическим выражением. Если условие истинно, то выполняется тело тега. Например:
<c:if test="${age <= 0}">
Уважаемый ${name}! Укажите, пожалуйста, свой возраст.
</c:if>
Как видите, этот тег не реализует полностью условие "if-then-else". Такое разветвление можно организовать тегом <c:choose>.
Тег <c:choose> со вложенными в него элементами <c:when> и <c:otherwise> реализует выбор одного из вариантов.
Например:
<c:choose>
<c:when test="${balance < 0}" >
На вашем счету отрицательный остаток.
</c:when>
<c:when test="${balance == 0}" >
На вашем счету нулевой остаток.
</c:when>
<c:when test="${balance > 0}" >
На вашем счету положительный остаток.
</c:when>
<c:otherwise>
Нет сведений о вашем остатке.
</c:otherwise>
</c:choose>
В примере листинга 27.11 показано, как можно организовать разветвление с помощью тега <c:choose>. В нем проверяется, известны ли уже сведения о клиенте. Если они известны, клиенту посылается приветствие, если нет — форма для ввода имени и пароля.
Листинг 27.11. Ответ, сформированный с помощью JSTL
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head><title>Проверка ввода имени</title></head>
<body>
<c:choose>
<c:when test="${user != null}">
Здравствуйте, ${user.name}!
</c:when>
<c:otherwise>
form method="POST" action="/sdo/jsp/user.jsp">
Имя: <input type="text" name="name">
Пароль: <input type="password" name="pswd">
<input type="submit" name="ok" value="Отправить">
</form>
</c:otherwise>
</c:choose>
</body>
</html>
У тега <c:forEach> две формы. Первая создает цикл, пробегающий коллекцию типа Collection или Map, указанную атрибутом items. Ссылка на текущий элемент коллекции заносится в переменную, определенную атрибутом var. Ее можно использовать в теле тега.
Например:
<c:forEach var="item" items="${sessionScope.cart.items}">
<td>
${item.quantity}
</ td>
</c:forEach>
Вторая форма тега <c:forEach> создает цикл с перечислением, в котором переменная цикла, определяемая атрибутом var, пробегает от начального значения, задаваемого значением атрибута begin, до конечного значения, задаваемого значением атрибута end, с шагом — значением атрибута step. Например:
<c:forEach var="k" begin="0" step="1" end="${n}" >
<td>
${^-й столбец
</ td>
</c:forEach>
Тег <c:forTokens> разбивает строку символов, заданную атрибутом items, на слова подобно классу StringTokenizer, рассмотренному в главе 5. Разделители слов перечисляются в атрибуте delims. Текущее слово заносится в переменную, определенную атрибутом var. Например:
<c:forTokens var="word" items="${text}" delims=" :;,.?!">
<c:out value="${word}"/>
</c:forTokens>
Тег <c:import> включает на страницу JSP ресурсы по их адресу URL. Например:
<c:import url="/html/intro.html" var="intro" scope="session" charEncoding="windows-1251"
/>
Переменную, определенную атрибутом var, можно использовать в своей области действия, определенной атрибутом scope (по умолчанию, page). Атрибут charEncoding показывает кодировку символов включаемого ресурса. По умолчанию это кодировка ISO 8859-1, которая плохо подходит для кириллицы.
Тег <c:redirect> прекращает обработку страницы и посылает HTTP-ответ redirect клиенту с указанием адреса, записанного в атрибуте url. Браузер клиента сделает новый запрос по этому адресу. В соответствующем сервлете метод doEndTag() возвращает константу SKIP_PAGE. Например:
<c:redirect url="/books/list.html" context="/lib" />
Необязательный атрибут context устанавливает контекст для нового запроса.
В тег <c:redirect>, как и в тег <c:url>, можно вложить теги <c:param>, задающие параметры нового запроса.
Библиотека xml
Библиотека xml, описываемая тегом
<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %>
содержит теги <x:out>, <x:set>, <x:forEach>, <x:if>, <x:choose>, <x:when>, <x:otherwise>, аналогичные соответствующим тегам библиотеки core, но выбирающие нужный элемент XML из интерпретируемого документа атрибутом select, а также теги <x:parse> и <x: trans form>, интерпретирующие и преобразующие документ XML.
Работа с библиотекой xml основана на адресации элементов документа XML средствами языка XPath, что выходит за рамки нашей книги.
Библиотека fmt
Библиотека fmt содержит теги, помогающие в интернационализации страниц JSP. Она описывается так:
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
В нее входят теги <fmt:setLocale>, <fmt:timeZone>, <fmt:formatDate>, <fmt:parseDate>, <fmt: formatNumber>, <fmt:parseNumber> и другие теги, делающие локальные установки.
Например, тег
<fmt:formatNumber var="formattedAmount" pattern="0.00" value="${amount}" />
запишет в переменную formattedAmount типа String количество amount с двумя цифрами в дробной части, а тег
<fmt:formatDate var="ruDate" pattern="dd.MM.yyyy" value="${today}" />
запишет в переменную ruDate типа String дату today в виде 08.12.2007.
Теги
<fmt:parseNumber var="n" pattern="0.00" value="${amount}" />
<fmt:formatDate var="d" pattern="dd.MM.yyyy" value="${today}" />
выполняют обратное преобразование строки символов, заданной атрибутом value, в объекты типа Number и Date соответственно, записанные в переменные n и d по шаблону pattern.
Эти теги реализованы классами DecimalFormat и SimpleDateFormat из пакета java.text и преобразуют данные по правилам этих классов, которые можно посмотреть в документации Java SE.
Библиотека sql
Четвертая библиотека, sql, описываемая тегом
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
содержит теги связи и работы с базами данных: <sql:setDataSource>, <sql:query>, <sql: update> и <sql:transaction>. Их действие построено на JDBC и работа с ними очень похожа на работу с базами данных через JDBC, что можно понять из следующего примера:
<sql:setDataSource driver="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@localhost:1521:ORCL" user="scott" password="tiger"
/>
<sql:query var="depts" > select * from DEPT </sql:query>
<sql:transaction>
<sql:update>
insert into DEPT values(50, 'XXX', 'YYY')
</sql:update>
</sql:transaction>
Библиотека fn
Пятая библиотека, fn, содержит функции для обработки строк. Она описывается тегом
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
и содержит теги <fn:substring>, <fn:substringBefore>, <fn:substringAfter>, <fn:indexOf>, <fn:toUpperCase> <fn:toLowerCase>, <fn:startsWith>, <fn:endsWith>, <fn:contains>,
<fn:containsIgnoreCase>, <fn:trim>, <fn:replace>, <fn:split>, <fn:join>, <fn:escapeXml>. Вы,
несомненно, узнаете в названиях этих тегов привычные методы обработки строк, которые мы изучили в главе 5.
Кроме тегов обработки строк, библиотека fn содержит функцию <fn:length>, вычисляющую как длину строки, так и длину коллекции, переданной ей в качестве аргумента. Например:
<c:if test="${fn:length(param.username) > 0}" >
<%@include file="response.jsp" %>
</c:if>
Frameworks
Приступая к разработке Web-приложения, каждая команда решает вопрос о его архитектуре. Чаще всего ответ находится в схеме MVC (Model-View-Controller) (см. главу 3). Один или несколько сервлетов, принимающих и обрабатывающих запросы клиента, составляют Контроллер. Подготовка ответа, связь с базой данных, отбор информации, все то, что называется бизнес-логикой или бизнес-процессами, выполняется классами Java, образующими Модель. Страницы HTML и JSP, заполненные информацией, полученной от Модели, составляют Вид.
В ходе обсуждения и реализации проекта каждая из трех частей схемы MVC конкретизируется, описывается интерфейсами и абстрактными классами. Иногда на этом этапе получается наполовину реализованная конструктивная схема, подходящая для выполнения целого класса типовых проектов. Для завершения проекта Web-приложения остается реализовать несколько интерфейсов, дописать при необходимости свои собственные классы и определить под свой проект параметры приложения, записав их в конфигурационные файлы.
Наиболее удачные из таких шаблонов готового приложения становятся достоянием всего сообщества программистов, активно обсуждаются, улучшаются, модернизируются и получают широкое распространение. Такие общепризнанные шаблоны, или, по-другому, каркасы программного продукта получили название Frameworks.
В технологии Java уже есть десятки таких каркасов, в их числе JSF, Seam, Struts, Facelets, Tales, Shale, Spring, Velocity, Tapestry, Jena, Stripes, Trails, RIFE, WebWork и множество других. В состав Java EE SDK входит каркас JSF. Познакомимся с ним подробнее.
JavaServer Faces
Framework под названием JSF (JavaServer Faces) вырос из простой библиотеки тегов, расширяющей возможности тегов HTML. Он входит в стандартную поставку Java EE, хотя всеми возможностями JSF можно воспользоваться и не имея у себя на компьютере Java EE. Есть много других реализаций интерфейсов JSF.
Эталонную реализацию, которую предоставляет проект GlassFish, можно загрузить с сайта http://javaserverfaces.dev.java.net/. Из других реализаций следует выделить очень популярный продукт Apache MyFaces, http://myfaces.apache.org/.
Все необходимое для работы с JSF собрано в одном архивном файле. Достаточно распаковать его в какой-нибудь каталог, и JSF готов к работе. В этом каталоге вы найдете подкаталоги с документацией и подкаталог с подробными примерами. Основу реализации JSF составляют два JAR-файла jsf-api.jar и jsf-impl.jar из подкаталога lib, которые надо скопировать в ваше Web-приложение или в ваш сервер приложений или записать пути к ним в переменную classpath. Для работы JSF понадобится библиотека JSTL, проследите за тем, чтобы у вашего Web-приложения был доступ к ее JAR-архиву, например файлу jstl-1.2.j ar.
Каркас JSF построен по схеме MVC (Model-View-Controller), которую мы обсуждали в главе 3.
Роль Контроллера в JSF играет сервлет под названием FacesServlet. Как и все сервлеты, он должен быть описан в конфигурационном файле web.xml. Это описание выглядит так:
<web-app>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet> <servlet-mapping>
<servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern>
</servlet-mapping>
</web-app>
После этого описания мы можем обращаться к JSF c запросами вида
http://localhost:8080/MyAppl/index.faces, http://localhost:8080/MyAppl/login.faces
Часто элемент <servlet-mapping> записывают по-другому:
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
После такого описания запросы к JSF должны выглядеть так:
http://localhost:8080/MyAppl/faces/index.j sp, http://localhost:8080/MyAppl/faces/login.j sp
И в первом, и во втором случае запрашиваются файлы index.jsp и login.jsp. Расширение имени файла faces в запросах первого вида сделано только для JSF, на сервере файлов с такими именами нет. Увидев в запросе имя index.faces, JSF будет искать файл index.jsp.
Для организации Вида в JSF разрабатываются библиотеки тегов. В состав JSF сейчас входят две библиотеки: core и html. Они обычно описываются на странице JSP директивами
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
Все теги JSF вкладываются в элемент <f:view>. Компилятор JSF просматривает и компилирует только теги, вложенные в <f:view>. Теги включаемого файла должны быть вложены в элемент <f: subview>. Это можно сделать так, как показано в листинге 27.12.
Чаще всего на странице JSP формируется пользовательский интерфейс, который будет отображен браузером клиента. В нем размещаются формы с полями ввода, списками выбора, кнопками и прочими компонентами, текст с гиперссылками, панели, вкладки, таблицы.
Создание классической страницы HTML уже не удовлетворяет ни разработчиков, ни клиентов. Набор тегов HTML невелик и фиксирован, их возможности весьма ограниченны. Уже давно придумываются разные способы оживления страниц HTML: таблицы стилей CSS, динамический HTML, апплеты. Библиотека тегов html в первую очередь призвана усилить возможности тегов HTML.
Для каждого тега HTML в JSF есть соответствующий тег, обладающий дополнительными возможностями. В листинге 27.12 показана форма с полями ввода имени и пароля и кнопкой типа Submit.
Листинг 27.12. Страница JSP с тегами библиотеки JSF
<html><head>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
</head><body>
<f:view>
<f:subview id="myNestedPage">
<j sp:include page="theNestedPage.jsp" />
</f:subview>
<h:form>
<h:inputText id="name" si ze="50"
value="#{cashier.name}" required="true">
<f:valueChangeListener type="listeners.NameChanged" /> </h:inputText>
<h:inputSecret id="pswd" size="50"
value="#{cashier.pswd}"
required="true">
<f:valueChangeListener type="listeners.NameChanged" /> </h:inputSecret>
<h:commandButton type="submit" value="Отправить" action="#{cashier.submit}"/>
</h:form>
</f:view>
</body></html>
В библиотеке html есть более двух десятков тегов, соответствующих графическим компонентам пользовательского интерфейса. Кроме того, разработчик может легко создать свои, пользовательские, компоненты (custom components), расширив классы JSF. Уже создано много библиотек тегов для JSF, свободно распространяемых или коммерческих.
Главная особенность библиотеки html заключается в том, что ее составляют не просто теги, а целые компоненты. Компоненты JSF реагируют на события мыши и клавиатуры, которые могут быть обработаны обычными средствами JavaScript или специальными средствами JSF. Для этого введены теги-обработчики событий. Один такой тег, <v:valueChangeListener>, показан в листинге 27.12. Надо сказать, что разработчики тегов библиотеки html сознательно ориентировались на компоненты Swing, стараясь, по мере возможности, наделить теги такими же свойствами. В частности, компоненты JSF можно размещать на форме, пользуясь подходящим IDE. Это "умеет" делать, например, Java Studio Creator.
Форма, записанная в листинге 27.12, посылает на сервер имя и пароль, связанные с полями cashier.name и cashier.pswd атрибутом value. Что это за поля и какому объекту cashier они принадлежат?
Данные, полученные от HTML-формы, будут храниться в объекте, класс которого должен написать разработчик Web-приложения. Этот класс оформляется как JavaBean, чтобы JSF мог заполнять и читать его поля методами доступа. Он будет частью Модели в схеме MVC. Класс для хранения имени и пароля, полученных от формы листинга 27.12, показан в листинге 27.13.
Листинг 27.13. Класс с данными HTML-формы j
package myjsf;
import javax.faces.bean.*;
@ManagedBean(name="cashier")
@RequestScoped public class Cashier{
private String name; private String pswd;
public String getName(){ return name; }
public void setName(String name){ this.name = name; }
public String getPswd(){ return pswd; }
public void setPswd(String pswd){ this.pswd = pswd; }
public String submit(){
if ("Cashier".equalsIgnoreCase(name) && "rT34?x D".equals(pswd))
return "success"; else
return "failure";
}
}
Как видите, в этом же классе записан метод обработки щелчка по кнопке Отправить.
Теперь надо каким-то образом указать JSF этот класс. Сведения о нем записываются в аннотациях или в конфигурационном XML-файле faces-config.xml, который должен храниться в каталоге WEB-INF вместе с файлом web.xml. Этот файл для нашего примера записан в листинге 27.14.
Листинг 27.14. Конфигурационный файл JSF
<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/j avaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig 1 2.xsd" version="1.2">
<managed-bean>
<description>
Класс-обработчик регистрации кассира
</description>
<managed-bean-name>cashier</managed-bean-name>
<managed-bean-class>myjsf.Cashier</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<from-view-id>/index.j sp</from-view-id>
<navigation-case>
<description>
После удачной проверки переход на страницу welcome.jsp
</description>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.j sp</to-view-id>
</navigation-case>
<navigation-case>
<description>
После неудачной проверки возврат на страницу index.jsp
</description>
<from-outcome>failure</from-outcome>
<to-view-id>/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Элемент <managed-bean> связывает имя класса cashier, используемое в листинге 27.12, с полным именем класса myjsf.Cashier. Элемент <navigation-rule> показывает, откуда (<from-view-id>) и куда (<to-view-id>) следует перейти после обработки данных методом submit () объекта cashier, а также при каком событии (<from-outcome>) сделать тот или иной переход.
Для полноты осталось написать файл welcome.jsp. Он может выглядеть так, как показано в листинге 27.15.
Листинг 27.15. Страница приветствия с тегами библиотеки JSF
<html><head>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
</head><body>
<f:view>
Здравствуйте, ${cashier.name}!
</f:view>
</body></html>
Обзор всех возможностей JSF выходит за рамки нашей книги. Вы можете ознакомиться с ними на сайте разработчиков JSF http://jsfcentral.com/. Множество статей и учебников по JSF собрано на сайте http://www.jsftutorials.net/. Там вы можете найти дальнейшие ссылки.
Вопросы для самопроверки
1. Для чего придуман язык JSP?
2. Можно ли смешивать код JSP и код HTML на одной странице?
3. Можно ли записывать код Java на страницах JSP?
4. Можно ли включать в страницу JSP другие файлы?
5. Можно ли передавать управление из страницы JSP другим ресурсам?
6. Можно ли расширить набор стандартных тегов JSP?
7. Можно ли написать несколько классов Java, по-разному обрабатывающих один и тот же пользовательский тег?
8. Можно ли обработать пользовательский тег не классом Java, а страницей JSP?
9. Как подключить библиотеку пользовательских тегов к странице JSP?
ГЛАВА 28