JSTL (JavaServer Pages Standard Tag Library)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 20:11, 1 июня 2016.
JSTL
Разработчики Oracle
Первый   появившийся 2006
Стабильная версия 1.2.1 / 2015.12 (1.2.1)
OS Кросплатформенный
Расширение файла .jsp .jspf .jsf
Портал: jstl.java.net
Под влиянием
HTML, JSP

JSTL (JavaServer Pages Standard Tag Library - Стандартная библиотека тегов JSP) — расширение спецификации JSP, добавляющее библиотеку JSP тегов для общих нужд и содержащее условная обработка, разбор XML данных, поддержка интернационализации и др.

История

Сервлет - это специальный класс со набором необходимых методов (doGet, doPost и др.), который вызывается из браузера и генерирует HTML-страницу. Сначала, на основании пришедших от клиента данных, (параметры запроса или содержимое полей формы) нужно выполнить расчет какой-то информации, подготовить набор данных и каким-то образом визуализировать их, т.е. перемешать HTML-теги и подготовленные данные. Это выглядело ужасно:

OutputStream out = response.getOutputStream();
out.println("<HTML>");
out.println("<body>");
out.println("<h1> Привет, " +  vasyanoFIO + "</h1>");
out.println("</body>");
out.println("</HTML>");

Потом придумали JSP. Типовая страница характеризуется значительным превышением количества статического HTML-кода над динамической информацией (взятой из файлов, баз данных, вычисленных в веб-приложении), поэтому логично было не встраивать HTML-код внутрь JAVA-кода, а наоборот - встроить JAVA-код внутрь HTML-шаблона.

<%@ page contentType="text/HTML;charset=UTF-8" language="JAVA"%>
<body>
<%
    String vasuanoFIO = "Бла-бла-бла";
    if ( 1 < 2)
        vasuanoFIO = "Бум-бам-тарарам";
%>
<h1><%= vasuanoFIO %></h1>
</body>

Выглядит не очень приятно. Конечно, если нужно быстро сделать одну страничку, то это хороший выход, только пихать логику вычисления данных в HTML не хорошо. С ростом количества расчетов и усложнением верстки код превращается в винегрет. Большой код не читаем. Код, где и HTML и логика, не читаем вдвойне. Очень скоро часть логики будет продублирована и размазана по тысяче файлов, так что понять, что и откуда взялось невозможно. Так же данное решение сильно усложняет жизнь дизайнерам. В стандарте JSP рекомендуемой практикой является вынос логики обработки запроса в bean. Например, задачу с приветствиями можно оформить в виде отдельного класса и добавить к нему поля (управляющие расчетом итогового значения).

public class HelloMachineBean {
    public String fio;
    public String age;
    public String sex;

    public String getFio() {
        return fio;
    }

    public void setFio(String fio) {
        this.fio = fio;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getResult (){
        if ("male".equalsIgnoreCase(sex))
            return "Это дядя " + fio + " его возраст "+age;
        return "Это тетя "+fio+ " ее возраст "+age;
    }
}

Обратите внимание на метод getResult – именно он содержит логику расчета сообщения, которое будет показано пользователю. Теперь нужно создать JSP-файл, в котором создается объект bean, его свойства наполняются значениями и извлекается рассчитанное значение (getResult).

// создаем объект логики, указываем его имя и JAVA-класс 
<jsp:useBean id="helloMachine" class="t
sti.HelloMachineBean" />
// теперь начинаем заполнять значениями поля этого класса-бина, синтаксис
// property=”*” означает, что все пришедшие параметры запроса должны быть помещены внутрь bean-а.
<jsp:setProperty name="helloMachine" property="*" />
// вариант, когда указывается конкретное имя свойства, которое нужно заполнить информацией, и имя поля из HTTP-запроса
<jsp:setProperty name="helloMachine" property="fio" param="fio" />
// можно присвоить атрибуту значение в виде константы
<jsp:setProperty name="helloMachine" property="sex" value="female" />
// использование, как будто бы в составе класса helloMachine есть свойство result.
// для обращения к свойствам используются методы getter-ы,
// поэтому фактическое существование поля класса с именем result не существенно
<h2>
    Hello <jsp:getProperty name="helloMachine" property="result" />
</h2>

Такой подход имеет много подводных камней. Например, если создать JSP-страницу как наследник от некоторого класса сервлета. Основная проблема в том, что программисты могут (а значат и будут) смешивать JAVA-код и HTML:

<%
 if ( bla-bla)
  …
 else
  …
%>

Есть и другая проблема: реальный сайт оперирует не только скалярными данными (переменная со строковым значением fio), но и списками, массивами. Для их обработки нужны циклы, условные конструкции (если сумма денег на счету меньше чем 100$, то вывести ее красной, иначе - зеленой). Можно перенести циклы и условия внутрь bean-ов, но, фактически, это означает, что мы перенесем внутрь bean'ов генерацию HTML-кода (к чему приводит такое решение, мы уже видели на примере сервлетов), поэтому некоторая примитивная обработка условий и циклов на странице необходима, иначе мы получим множество проблем.

<%
    String vasuanoFIO = "Нет. Ты не сможешь победить силы Зла";
    if ( 1 < 2)
        vasuanoFIO = "Твоя борьба против каши в JSP обречена на провал, Ха-ха-ха";
%>

Для упрощения придумали пользовательские теги. Т.е. можно создаеть JAVA-класс, который умеет что-то делать, и внедрить его в страницу, примерно, так:

<showcalendar />
<mailsend to="vasyano@mail.ru" />

Это очень удобно и все стали создавать собственные библиотеки “очень нужных” тегов. Но такой подход не способствовал накоплению знаний, потому что библиотек тегов было слишком много, их делали все, кому не лень, но страдало качество этих библиотек. Поэтому появление стандартов, принятых наборов тегов и их качественная реализация были неизбежны. Так и появилась JSTL – JAVA Servlet Templates Libruary.

Использование

  • Загрузить с сайта архив с библиотекой JSTL (в примерах используется версия 1.2).
  • Добавить эту библиотеку в папку WEB-INF/lib вашего веб-приложения.
  • Подключить в начале JSP-файла с помощью специальных директив taglib библиотеки тегов, которые хотите использовать:

Основные теги позволяющие делать циклы, условия, выводить информацию на экран:

<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/core" %>

Теги для работы с xml-документами:

<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/xml" %>

Теги для работы с базами данных (Ну зачем вы это сюда вставили …):

<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/sql" %>

Теги для форматирования информации перед выводом и работы с i10n и i18n:

<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/fmt" %>

Сначала разберем теги из библиотеки JSTL/core. Начнем с самого простого: вывести на экран браузера некоторый текст.

<%@ page contentType="text/HTML;charset=UTF-8" language="JAVA" %> 
<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/core" %>

<HTML>
  <head><title>Simple JSP page</title></head>
  <body>
    <c:out value="hello, Vasuano" />
  </body>
</HTML>

Cмысла в такой конструкции мало, потому что простой статический текст мы можем выводить и без использования тегов JSTL. c:out нужен для вывода на экран динамически рассчитываемых величины:

<c:out value="${12+56*2}" />

Есть список предопределенных контекстов:

Контекст Комментарий
pageScope Контекст страницы (переменные, объявленные на этой странице и доступные только для этой страницы)
requestScope Доступ к таким переменным имеют все страницы (сервлеты), обслуживающие текущий запрос пользователя
sessionScope Доступ к переменным сохраняется на протяжении всего сеанса пользователя (пока не закроет браузер или не истечет предельное время бездействия)
applicationScope Доступ к переменным сохраняется со всех страниц, размещенных внутри веб-приложения
param В этом контексте находятся все переменные, полученные страницей от пользователя либо как параметры адресной строки, либо как поля
paramValues Список значений тех переменных, которые были переданы в страницу пользователем, в виде HashMap<String, String [] >
header В этом объекте хранится информация об HTTP-заголовках, которые были переданы от веб-серверу клиента
headerValues Список значений HTTP-заголовков
initParam Конфигурационные параметры, указанные для вашей страницы, сервлета в файле web.xml
cookie Список переменных внутри cookie
pageContext Ссылка на объект pageContext

Для того чтобы обратиться к переменной, находящейся внутри одного из контекстов, нужно написать имя контекста, затем точку и имя переменной. Возможен и альтернативный синтаксис:

<c:out value="${param.fio}" />
<c:out value="${param['fio']}" />

У тега c:out есть еще несколько атрибутов:

<c:out value="${param.fio}" default="NO DATA" escapeXml="true" />

В этом примере, если на вход странице не была подана переменная fio, будет выведена фраза “NO DATA”.

Для создания переменных используется тег c:set. В качестве его атрибутов указывается название контекста, куда мы хотим положить переменную, имя переменной и значение:

<c:set var="vasyano" scope="session" value="Вася Тапкин" /> 
<c:set var="petyano" scope="page">
  Петька Козлов на странице
</c:set>
<c:set var="petyano" scope="request">
  Петька Козлов в запросе
</c:set>
<c:set var="petyano" scope="session">
  Петька Козлов в сессии
</c:set>
<c:set var="petyano" scope="application">
  Петька Козлов в приложении
</c:set>
vasyano: <c:out value="${sessionScope.vasyano}" />

petyano: <c:out value="${petyano}" />

Здесь показаны несколько хитрых моментов в работе JSTL. Во-первых, обратите внимание, как отличаются имена контекстов при операции set и операции out. В первом случае нужно писать слово session, а во втором случае – sessionScope. Также Если не указывать имя контекста, то работает правило поиска нужного контекста: ищем переменную внутри pageScope, если не найдено, то внутри requestScope, если опять не найдено, то внутри sessionScope и, наконец, внутри applicationScope.

Есть еще один вариант синтакиса оператора c:set, когда нужно установить значение свойства некоторого JAVA-bean, внедренного на страницу.

<c:set target="${helloMachine}" property="fio" value="Ленка Слонова" />
<c:out value="${helloMachine.fio}" />

Для удаления переменных (присвоения им значения null) используется оператор remove:

<c:set var="fio" scope="session" value="Vasyano Petrovno" />
 fio1 = <c:out value="${fio}" />
<c:remove var="fio" />
 fio2 = <c:out value="${fio}" />

Операторы if и switch

Оператор if не слишком похож на своих больших братьев в JAVA и других языках: нельзя задать ни ветку else, ни ветку elseif (elif), можно только проверить некоторое условие и, если оно истинно, сделать что-то:

<c:if test="${param.age gt 12}">
  Возраст более 12 лет
</c:if><c:if test="${param.age lt 25}">
  Возраст менее 25 лет
</c:if>

Обратите внимание, что в условии используются не знаки “<” или “>”, а слова “gt” и “lt”, это необходимо т.к. в противном случае мы нарушим правила записи xml-документов. Есть и другие слова-операторы:

  • eq – проверка на равенство
  • ne – проверка на неравенство
  • lt – строго менее чем
  • gt – строго более чем
  • le – меньше либо равно чему-то
  • ge – больше или равно чему-то

У тега if есть несколько необязательных атрибутов, которые могут пригодиться, чтобы не записывать повторяющиеся выражения. В атрибут var записывается результат вычисления условия (атрибута test). Контекст, куда будет положена эта переменная, задается атрибутом scope.

<c:if test="${param.age gt 12}" var="if_less_12">
  Возраст более 12 лет
</c:if>

<c:if test="${if_less_12}">
  Еще раз повторяю: Возраст более 12 лет
</c:if>

Если нужно проверить несколько условий, то здесь нужно использовать тег choose -> when -> otherwise:

<c:choose>
  <c:when test="${param.age lt 10}">
      Возраст менее 10 лет
  </c:when>
  <c:when test="${param.age lt 20}">
      Возраст в отрезке от 10 до 20 лет
  </c:when>
  <c:otherwise>
      Срочно пройдите на процедуру усыпления
  </c:otherwise>
</c:choose>

Циклы

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

Первый тег “c:ForEach” имеет 6 атрибутов, управляющих его работой, но среди них нет ни одного обязательного.

Введем в состав описанного выше класса HelloMachineBean несколько новых методов, возвращающих массив элементов и список элементов.

public List< String> getFriendsAsList () {
        return Arrays.asList(getFriendsAsArray ());
}

public String[] getFriendsAsArray(){
    return new String[]{"Васька", "Петька", "Ленка"};
}

Пример использования этих данных внутри цикла:

<c:set var="friends" value="${helloMachine.friendsAsArray}" />
<c:set var="friends2" value="${helloMachine.friendsAsList}" /><c:forEach items="${friends}" var="friend">
    <h2>
        <c:out value="${friend}"/>
    </h2>
</c:forEach><c:forEach items="${friends2}" var="friend">
    <h3>
        <c:out value="${friend}"/>
    </h3>
</c:forEach>

Здесь используются два атрибута тега ForEach – items, играющий роль источника данных, и var – переменная, в которую будет последовательно помещаться элементы массива/списка.

Второй вариант цикла ForEach предназначен для прохода по целым числам в отрезке от X до Y, например:

<c:forEach var="friend_i" begin="0" end="2">
    <h5>
        <c:out value="${friend_i}"/> =  <c:out value="${friends[friend_i]}"/>
    </h5>
    <h4>
        <c:out value="${friend_i}"/> =  <c:out value="${friends2[friend_i]}"/>
    </h4>
</c:forEach>

Обратите внимание, что с помощью индекса (в квадратных скобках) можно обращаться к элементами не только массива, но и списка. Еще один атрибут для тега forEach – это step. Он управляет величиной шага, с которым выполняется проход по элементам массива. В следующем примере атрибут step корректно работает не только, когда цикл перебирает числа в отрезке от X до Y, но и когда перебирается содержимое некоторой коллекции элементов.

<c:forEach items="${friends}" var="friend" step="2">
    <h2>
        <c:out value="${friend}"/>
    </h2>
</c:forEach><c:forEach var="friend_i" begin="0" end="2" step="2">
    <h5>
        <c:out value="${friend_i}"/> =  <c:out value="${friends[friend_i]}"/>
    </h5>
    <h4>
        <c:out value="${friend_i}"/> =  <c:out value="${friends2[friend_i]}"/>
    </h4>
</c:forEach>

Последний атрибут для цикла For – varStatus. В эту переменную помещается информация о выполняемом цикле и его текущем шаге. Значение переменной отличается в зависимости вида цикла используется. В обычном цикле от X до Y значением этой переменной будет текущий индекс:

<c:forEach var="friend_i" begin="0" end="2" step="2" varStatus=”friendStatus”>
  <h5>
      friend_i = <c:out value="${friendStatus}"/>*
      <c:out value="${friend_i}"/> =  <c:out value="${friends[friend_i]}"/>
  </h5>
</c:forEach>

Если же цикл выполняется по элементам коллекции, то varStatus - сложный объект с информацией о начальном и конечном шаге цикла, о том, перебирается ли первый элемент коллекции, например:

<c:forEach items="${friends}" var="friend" step="1" varStatus="friendStatus">
    Status:
    index=<c:out value="${friendStatus.index}"/>
    count=<c:out value="${friendStatus.count}"/>
    first=<c:out value="${friendStatus.first}"/>
    last=<c:out value="${friendStatus.last}"/>
    step=<c:out value="${friendStatus.step}"/>
    <h2>
        <c:out value="${friend}"/>
    </h2>
</c:forEach>

Перейдем ко второму типу циклов – forTokens. Этот цикл похож на ранее приведенный forEach: совпадают многие атрибуты тегов, но ключевое отличие в том, что цикл идет по списку лексем, на которые была разбита строка:

<c:set var="str" value="Гравитон Фотон Бозон Мюон" />
<c:forTokens items="${str}" delims=" " var="token" begin="1" varStatus="tokenStatus" step="1">
    index=<c:out value="${tokenStatus.index}"/>
    count=<c:out value="${tokenStatus.count}"/>
    first=<c:out value="${tokenStatus.first}"/>
    last=<c:out value="${tokenStatus.last}"/>
    step=<c:out value="${tokenStatus.step}"/>
     
    <h2>     
        <c:out value="${token}"/>      
    </h2>
</c:forTokens>

Строка “Гравитон Фотон Бозон Мюон” разбивается на отдельные подстроки с помощью разделителя delims (пробела). Цикл начинается не с первого элемента - “Гравитон”, а со второго - “Фотон”. На каждой итерации цикла выводятся сведения о номере итерации и признак, первая ли это итерация или последняя. Неприятность в том, что значение переменной count использовать нельзя, эта величина не равна “4”, как ожидалось, а растет по мере выполнения цикла. Значением атрибута tokens может быть несколько символов, например:

<c:set var="str" value="Гравитон,Фотон.Бозон Мюон" />
<c:forTokens items="${str}" delims=" ,." var="token" begin="0" varStatus="tokenStatus" step="1">
    <h2>
        <c:out value="${token}"/>
    </h2>
</c:forTokens>

Разделение на модули

Очевидно, что тяжело написать хороший код страницы в одном файле, без разбиения его на части, поэтому в состав JSTL входит тег import, позволяющий включить в один JSP-файл другой JSP-файл. Этот тег очень гибкий, в самом простом случае в страницу можно включить статический кусочек текста в виде HTML/txt блока:

<c:import url="heading.HTML" />

Пример включения в JSP-страницу результата работы другой JSP-страницы: Включаемая страница (footer.JSP):

<%@ page import="JAVA.util.Date" %>
<%@ page contentType="text/HTML;charset=UTF-8" language="JAVA"%>
<%@ taglib prefix="c" uri="HTTP://JAVA.sun.com/JSP/jstl/core" %><%
    out.write("Сложные расчеты по внедрению информации внутрь ... " + new Date());
%>
<h1>
    <c:out value="${externalVar}" />
</h1>

Главная страница:

<c:set var="externalVar" value="Hello from Outer page"  scope="request"/>
<c:import url="footer.JSP" />

Аналогичным образом можно передавать переменные и из вложенной страницы во внешнюю (не забывайте про контекст переменных). На включаемой странице будут доступны и те переменные, которые были переданы главному скрипту из HTML-формы. Например, явное указание кодировки:

<c:import url="heading.HTML" charEncoding="utf-8" />

Содержимое включаемой страницы можно даже не посылать сразу в браузер клиенту, а накопить в какой-то переменной для последующей обработки, например:

<c:import url="heading.HTML" charEncoding="utf-8" var="inner_c" scope="request" />
<c:out value="${requestScope.inner_c}" escapeXml="true" />

В случае, если указать атрибут varReader, результат вставки страницы не будет выведен на экран или помещен в строковую переменную var, а будет доступен только при чтении потока varReader.

Одим из наиболее часто используемым приемов сборки странички из кусочков является перенаправление на другой адрес, для этого используем тег redirect.

<c:redirect url="heading.HTML">
    <c:param name="fio" value="${helloMachine.fio}" />
</c:redirect>

Тег param можно использовать и при внедрении в страницу “подстраницы”:

<c:import url="heading.JSP" charEncoding="utf-8">
    <c:param name="fio" value="${helloMachine.fio}" />
</c:import>

Еще один малополезный тег = c:url, он служит для декорирования некоторого адреса:

<c:url value="/a3.JSP" />

На страницу будет вставлен адрес, включая контекст приложения.

Теги для работы с xml

Прежде всего, мы должны в заголовке jsp страницы подключить следующую библиотеку тегов:

<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>

Во всех последующих примерах будет нужен xml-файл, вот его код:

<?xml version="1.0" encoding="utf-8"?>
<bookshelf>
    <book id="12">
        <title>Книга про зайцев</title>
        <description><![CDATA[Книга &amp;copy;рассказывает про суровую
 судьбу молодой семьи зайцев, живущих в самый разгар ...]]></description>
        <price>99.95</price>
        <authors>
            <author main="true">Тапкин Василий Васильевич</author>
            <author>Пупкина Ленка Ленковна</author>
        </authors>
    </book>
    <book id="13">
        <title>Слоны-людоеды</title>
        <description><![CDATA[Книга создана по материалам расследования серии
 жестоких убийств в местном зоопарке]]></description>
        <price>29.95</price>
        <authors>
            <author main="true">Слоноглазов Глеб Гамбитович</author>
            <author>Слоноухова Виктория Мракобесовна</author>
        </authors>
    </book>
</bookshelf>

До начала работы с xml его нужно распарсить, делается это либо непосредственно внутри jsp-файла, либо подобную работу может выполнить java backend-код. Парсинг выполняется средствами тега x:parse, например:

<x:parse var="bookshelf">
<?xml version="1.0" encoding="utf-8"?>
<bookshelf>
    <book id="12">
        <title>Книга про зайцев</title>
        <description><![CDATA[Книга &amp;copy;рассказывает про суровую судьбу
 молодой семьи зайцев, живущих в самый разгар ...]]></description>
        <price>99.95</price>
        <authors>
            <author main="true">Тапкин Василий Васильевич</author>
            <author>Пупкина Ленка Ленковна</author>
        </authors>
    </book>
    <book id="13">
        <title>Слоны-людоеды</title>
        <description><![CDATA[Книга создана по материалам расследования
 серии жестоких убийств в местном зоопарке]]></description>
        <price>29.95</price>
        <authors>
            <author main="true">Слоноглазов Глеб Гамбитович</author>
            <author>Слоноухова Виктория Мракобесовна</author>
        </authors>
    </book>
</bookshelf> 
</x:parse>
<x:parse var="bookshelf2" xml="${helloMachine.bookshelfAsString}"  />

В первом случае весь код xml находится внутри тега parse. Второй вариант предполагает, что в составе класса HelloMachineBean должен появиться новый метод (для ненастоящего свойства), который читает с диска приведенный ранее xml-документ, и возвращает его в jstl-код в виде обычной строки.

public String getBookshelfAsString() {
   try {
      BufferedReader brin = new BufferedReader(
            new InputStreamReader(getClass().getResourceAsStream("/testi/templates/book.xml")));
      StringBuffer buf = new StringBuffer();
      String line;
      while ((line = brin.readLine()) != null)
         buf.append(line);
      return buf.toString();
   } catch (IOException e) {
      e.printStackTrace();
      return null;
   }
}

Попробуем что-нибудь сделать с этим xml-файлом. В самом простом случае нужно вывести на экран какую-то информацию. Нам понадобится тег, который работает с Xpath - x:out, в качестве параметров для него указывается атрибут select с выражением вида: Где-искать/Что-искать Где искать – это java-переменная ссылающаяся на xml-дерево, ее имя может быть просто bookshelf (см. прошлый пример) или содержать имя контекста, например:

pageScope:bookshelf.

Вот примеры, отбирающие разные части xml-документа:

<x:parse var="bookshelf">
    <c:import url="book.xml" charEncoding="utf-8" />
</x:parse>

all book content:<x:out select="$bookshelf/bookshelf/book[1]" escapeXml="false"/>
id:<x:out select="$bookshelf/bookshelf/book[1]/@id" />
title:<x:out select="$bookshelf/bookshelf/book[1]/title" />
qty authors:<x:out select="count($bookshelf/bookshelf/book[1]/authors/author)" />

Атрибут escapeXML служит для экранирования. Возникает проблема: если к xml-документу добавить пространство имен, например:

<bookshelf xmlns="http://books.com/buki">

То ранее приведенный код не будет работать. Выходом будет записать выражение, основанное на коротких (локальных) именах тегов:

all book content:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]" escapeXml="false"/>

id:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/@id" />
title:<x:out select="$bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='title']" />
qty authors:<x:out select="count($bookshelf/*[name()='bookshelf']/*[name()='book'][1]/*[name()='authors']/*[name()='author'])" />

При записи сложных xpath-выражений можно ссылаться на объявленные в jstl переменные, например, далее я хочу найти в списке книгу, номер которой был передан как входной параметр скрипту:

book title by bid: <x:out select="$bookshelf/bookshelf/book[@id = $param:bid]/title" />

Перейдем к тегу x:set. Он очень похож на x:out, только не выводит на экран некоторую xpath-часть входного документа, а присваивает ее значение jstl-переменной, чтобы позднее еще раз выполнить обработку документа с помощью x:out:

<x:set var="bookTitle" select="$bookshelf/bookshelf/book[1]/title" />
book title = <x:out select="$bookTitle/." />

В стандартной части jstl есть тег forEach, работающий со списками и массивами. В части jstl, посвященной xml, есть одноименный и очень похожий по параметрами тег forEach. Пример использования двойного цикла:

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <ul>
        <x:forEach select="$book/authors/author" var="author">
            <li>
                <x:out select="$author" />
            </li>
        </x:forEach>
    </ul>
</x:forEach>

Для работы с условиями есть два тега: if и choose, аналогичные рассмотренным ранее. Ниже представлен пример, для каждой книги выводится сообщение “два или более автора” если у книги более чем один автор:

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <x:if select="count($book/authors/author) >= 2">
        Два или более автора
    </x:if>
</x:forEach>

У тега x:if есть необязательные атрибуты (var и scope), позволяющие сохранить проверку условия в некоторую переменную для последующего многократного использования. В следующем примере показывается прием, когда в условии мы можем обращаться к переменным, полученным как параметры формы:

<x:if select="$bookshelf/bookshelf/book[@id = $param:bid]" var="xxx" />

Теперь разберем, как работать с двумя или более ветвями вычислений:

<x:forEach select="$bookshelf/bookshelf/book" var="book">
    <h1>
        <x:out select="$book/title" />
    </h1>
    <x:choose>
        <x:when select="count($book/authors/author) = 0">
            У книги нет авторов, вообще.
        </x:when>
        <x:when select="count($book/authors/author) = 1">
            Только один автор
        </x:when>
        <x:otherwise>
            Количество авторов неизвестно, но их точно более чем один
        </x:otherwise>
    </x:choose>
</x:forEach>

Рассмотрим тег x:transform. Он предназначен для выполнения xsl-трансформации некоторого xml-документа с помощью xsl-документа в другой xml или html-документ. Для примера создадим небольшой xsl-файл:

transbook.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>    <xsl:template match="/">
        <xsl:for-each select="bookshelf/book">
            <h1>
                <xsl:value-of select="title" disable-output-escaping="yes" />
            </h1>
            <ul>
                <xsl:for-each select="authors/author">
                    <li>
                        <xsl:value-of select="." disable-output-escaping="yes" />
                    </li>
                </xsl:for-each>
            </ul>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Для дальнейшего примера вам нужно удостовериться, что в папке lib веб-приложения есть библиотека xerces. У тега transform есть два обязательных атрибута: xml – содержит входные данные для трансформации, и xslt - задает правила выполнения трансформации. Входные параметр xslt – это строки текста.

<c:set var="bookshelf">
    <c:import url="book.xml" charEncoding="utf-8" />
</c:set><c:set var="transbook">
    <c:import url="transbook.xsl" charEncoding="utf-8" />
</c:set><x:transform xml="${bookshelf}" xslt="${transbook}" />

SQL в JSTL

Стоит отметить, что вынесение логики работы с базой данных в JSP - очень плохая практика, но эта возможность поддерживается.

Для работы всех показанных далее тегов необходимо подключить к jsp-файлу следующую библиотеку тегов:

<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>

Создадим таблицу в базе данных для следующих примеров:

CREATE TABLE `tm_user` (
  `UserID` int(11) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(100) DEFAULT NULL,
  `BirthDate` datetime DEFAULT NULL,
  `Sex` enum('male','female') DEFAULT NULL,
  `Comment` text,
  PRIMARY KEY (`UserID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Заполним ее:

INSERT INTO `tm_user` (username, birthdate, sex, comment) VALUES
  ('Jim Tapkin', '2006.1.1', 'male', 'bla-bla-bla'),
  ('Ron Baskerville', '2002.7.5', 'male', 'be-be-be'),
  ('Lenka Slonova', '2009.4.1', 'female', 'wow-wow-wow')

Доступ к подключению может быть выполнен либо путем использования JNDI соединения, либо внутри самого jstl-файла нужно объявить объект соединения. В первом случае необходимо наличие файла META-INF/context.xml (для Tomacat):

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/">
    <Resource name="jdbc/VeloDemoDS" auth="Container" type="javax.sql.DataSource"
              username="jim"
              password=""
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://center/obmachine_t?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF8"
            />
</Context>

В JSTL коде нужно обратиться к JNDI и получить подключение к описанному выше ресурсу. Затем нужно положить этот объект в какую-нибудь область видимости (например, pageContext). Теперь можно попробовать выполнить запрос, для этого используются два тега: query или update. Текст запроса можно указать непосредственно как тело тега query, либо значением атрибута sql.

<%
    InitialContext co = new InitialContext();
    pageContext.setAttribute("ds_jndi", co.lookup("java:comp/env/jdbc/VeloDemoDS"));
%>
<body><sql:query var="users" dataSource="${ds_jndi}">
    select * from tm_user
</sql:query><c:forEach items="${users.rows}" var="row">
    <c:out value="${row.UserName}"/>
</c:forEach>

Можно сделать еще короче: здесь, внутри атрибута dataSource тега, посылающего запросы к базе данных, указывается JNDI-адрес:

<sql:query var="users"  dataSource="jdbc/VeloDemoDS">
    select * from tm_user
</sql:query>
Если писать JNDI-адрес в каждом из query не удобно (или не хочется вставлять в начале страницы код, извлекающий из Context JNDI подключение), то можно сделать так:

<syntaxhighlight lang="html5">
<sql:setDataSource dataSource="jdbc/VeloDemoDS" var="velo_simple"/><sql:query var="users" dataSource="${velo_simple}">
    select * from tm_user
</sql:query>

В результате выполнения запроса появилась переменная users. Эта переменная играет роль контейнера для множества переменных (полей), содержащих детальные сведения о том, что было получено с сервера.

  • rows представляет собой список вида HashMap<String, Object>, в котором роль ключа играет имя поля, а значением является значение поля
  • rowsByIndex – список массивов; каждый массив представляет отдельную запись в таблице, но для доступа к полям нужно указывать не имя поля, а порядковый номер (отсчет начинается с 1)
  • columnNames – список имен полей

Например, так можно распечатать все имена отобранных полей:

<c:forEach items="${users.columnNames}" var="col">
    <c:out value="${col}"/>
</c:forEach>
  • rowCount – число отобранных строк.
  • limitedByMaxRows – признак того, что количество отобранных записей было ограничено

У тега query еще есть несколько атрибутов:

  • scope – контекст, куда будет помещена переменная с результатом отбора данных;
  • атрибуты maxRows и startRow служат для ограничения выборки

Есть возможность создавать параметрические запросы. Для этого внутрь тега query кроме текста запроса необходимо поместить еще и элементы param. Каждый из них имеет атрибут value со значением, которое будет подставлено в текст запроса (будьте внимательны с порядком следования параметров).

<sql:query var="users" dataSource="${ds_jndi}">
    select * from tm_user where UserID between ? and ?
    <sql:param value="${param.minID}" />
    <sql:param value="${param.maxID}" />
</sql:query>

Есть подвид тега param – dateParam. Его рекомендуют применять в случаях, когда поле, которое подставляется в текст запроса, имеет тип дата/время:

<sql:query var="users" dataSource="${ds_jndi}">
    select * from tm_user where BirthDate = ?
   <sql:dateParam value="${date}"/>
</sql:query>

Второй вид запросов – на обновление данных - выполняется с помощью тега update, правила его использования полностью идентичны приведенным выше для query (нужно указать источник данных, текст запроса, можно использовать параметры), только в переменную “var” помещается не выборка записей, а количество записей, которые были модифицированы.

<sql:update var="qtyInsterted" dataSource="${ds_jndi}">
    insert into `tm_user` (username, birthdate, sex, comment) values
      ('Jim Tapkin', '2006.1.1', 'male', 'bla-bla-bla'),
      ('Ron Baskerville', '2002.7.5', 'male', 'be-be-be'),
      ('Lenka Slonova', '2009.4.1', 'female', 'wow-wow-wow')
</sql:update>count affected records = <c:out value="${qtyInsterted}" />

В jstl есть поддержка работы с транзакциями. Транзакция представлена тегом “transaction”. Ее атрибутами является источник данных, для которого начата транзакция, и уровень ее изоляции (isolation).

<sql:transaction dataSource="${ds_jndi}">
    <sql:update>
        delete from tm_user
    </sql:update>
    <sql:update>
        delee adfs adasd
    </sql:update>
</sql:transaction>

Вторая команда внутри транзакции, очевидно, должна привести к ошибке – будет выброшено исключение. Визуально на экране браузера выведется текст сообщения об ошибке и выполнение дальнейшего кода jstl прервется. Методы обработки исключений в jstl тривиальны. Если исключение произошло не внутри блока catch, то генерация страницы прерывается. В случае, если потенциально опасные действия были внутри catch, то при генерации исключения описывающий его объект будет помещен внутрь переменной, заданной с помощью атрибута var. Затем нужно проверить, чему равна эта переменная. Если она не null, значит произошла ошибка.

<c:catch var="e_sql">
    <sql:transaction dataSource="${ds_jndi}">
        <sql:update>
            delete from tm_user
        </sql:update>
        <sql:update>
            delee adfs adasd
        </sql:update>    </sql:transaction>
</c:catch><c:if test="${e_sql != null}">
    Произошла ошибка: <c:out value="${e_sql}"/>
</c:if>

Второй способ конфигурирования подключения к СУБД – указать явно в коде jstl драйвер и учетные данные для доступа:

<sql:setDataSource driver="com.mysql.jdbc.Driver" user="jim" password=""
   url="jdbc:mysql://center/obmachine_t?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF8"
   var="velo_local" scope="application"
/>

Теперь у нас есть объект подключения и его можно использовать так же, как и полученный ранее из jndi.

Форматирование и локализация

В jstl входит набор тегов, предназначенный для создания локализованных приложений: представление дат и чисел в соответствии с определенными региональными настройками и средства для загрузки текстовых ресурсов из ResourceBoundle.

Начнем с подключения библиотеки тегов fmt:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

Самый часто используемый в этой библиотеке тег requestEncoding – установить значение кодировки входных данных, чтобы веб-сервер корректно произвел их декодирование:

<fmt:requestEncoding value="utf-8" />

Для работы с датами потребуются два тега: formatDate (выполняет преобразование даты в строку) и parseDate (на основании строки конструируется объект Date). Единственным необходимым атрибутом является value – значение даты, которую необходимо форматировать. Если не указать значение атрибута var, то результат форматирования будет выведен на экран, а не в промежуточную переменную. Атрибут pattern задает строку форматирования.

<jsp:useBean id="beanNow" class="java.util.Date" />
<fmt:formatDate value="${beanNow}" var="s_now" pattern="yyyy.MMM.dd hh:mm:ss" />
<c:out value="${s_now}" />

Ниже пример обратного преобразования из строки в дату:

<fmt:parseDate pattern="dd.MM.yyyy" value="12.04.2001" />

Можно выполнить настройку процесса форматирования/парсинга с помощью атрибута timeZone и parseLocal (для тега parseDate). Можно также поместить операцию форматирования внутрь тега timeZone, тогда форматирование будет выполняться по определенным правилам:

<fmt:timeZone value="Europe/Minsk">
     <fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />
</fmt:timeZone><fmt:timeZone value="Antarctica/Casey">
    <fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />
</fmt:timeZone>

Изменить локаль можно с помощью тега setLocale, например:

<fmt:setLocale value="ja_JP"/>
<fmt:formatDate value="${beanNow}" pattern="yyyy.MMM.dd hh:mm:ss" />

Для работы с числами используются два тега: formatNumber и parseNumber: Сначала из числа в строку:

<fmt:formatNumber pattern="#,##0.0#" value="234234.23423" />

А теперь наоборот:

<fmt:parseNumber value="234,234.23" pattern="#,##0.0#" />

Для работы с локализованными сообщениями используется два тега: setBundle и message. Первый из них загружает Resource Bundle, второй – выводит сообщения на основании Resource Bundle. У тега message обязательно должен быть указан атрибут key – это имя свойства, хранящего сообщение на нужном языке. Если не указан атрибут var, то сообщение будет выведено на экран тут же, в противном случае сообщение будет помещено в промежуточную переменную.

<fmt:setBundle basename="testi.messages" var="loc" /><fmt:message bundle="${loc}" key="MSG_1" /><fmt:message bundle="${loc}" key="MSG_2"  var="m_2"/>
<c:out value="${m_2}" />

Функции

Рассмотрим функции, которые можно использовать при записи jstl-выражений. Подключим:

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

Начнем разбирать функции по порядку. Первая из них “fn:contains” (имена всех функций должны предваряться префиксом “fn:” – именно его мы подключили в начале jsp-файла). contains служит для проверки того, что внутри строки содержится другая подстрока.

<c:set value="boy goes to school" var="boy" /><c:if test="${fn:contains(boy, 'goes')}">
    <h1>
        He goes to School. Realy.
    </h1>
</c:if>

Если проверить такое условие нужно без учета регистра символов, то используйте функцию containsIgnoreCase:

<c:if test="${fn:containsIgnoreCase(boy, 'GOES')}">
    <h1>
        He goes to School. Realy.
    </h1>
</c:if>

Для проверки того, начинается ли некоторая строка с заданной подстроки, используйте функцию startsWith, заканчивается - endsWith:

<c:if test="${fn:startsWith(boy, 'boy')}">
    <h1>
        Starts from BOY
    </h1>
</c:if><c:if test="${fn:endsWith(boy, 'school')}">
    <h1>
        Ends with SCHOOL
    </h1>
</c:if>

Для экранирования можно использовать функцию escapeXml (не забывайте, что в c:out есть атрибут, обеспечивающий сходную функциональность):

<c:set value="boy goes &amp;copy; to school" var="boy" />
<c:out value="${boy}" />
<c:out value="${fn:escapeXml(boy)}" />
<c:out value="${boy}" escapeXml="true" />

Для анализа строки есть множество функций. Позиция, с которой начинается другая строка:

<c:set value="boy goes to school" var="boy" />
<c:out value="${fn:indexOf(boy, 'to' )}" />

Вырежем из исходной строки ее часть, начиная с первого вхождения “to”:

<c:set value="boy goes to school" var="boy" />
<c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), 100)}" />

Третьим параметром функции выступает число – до какого символа нужно вырезать фрагмент. В следующем примере вырезаются только два символа из строки:

<c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), fn:indexOf(boy, 'to' )+2)}" />

Для того чтобы вырезать фрагмент строки с некоторой позиции до самого конца, можно не заморачиваться расчетами длины строки, а указать как последний параметр функции любое отрицательное число:

<c:out value="${fn:substring(boy, fn:indexOf(boy, 'to' ), -1)}" />

length – универсальная функция и умеет считать длину не только строки, но произвольной коллекции:

<sql:query var="users"  dataSource="jdbc/VeloDemoDS">
    select * from tm_user
</sql:query><c:set value="boy goes to school" var="boy" />

Пример длины строки:

<c:out value="${fn:length(boy)}" />

Считаем длину списка записей (сколько записей было отобрано из таблицы)

<c:out value="${fn:length(users.rows)}" />

substringAfter и substringBefore:

<c:set value="boy goes to school" var="boy" />
After:<c:out value="${fn:substringAfter(boy, 'to')}" />
Before:<c:out value="${fn:substringBefore(boy, 'to')}" />

Преобразование в верхний и нижний регистр может быть выполнено с помощью функций toLowerCase и toUpperCase:

<c:set value="boy goes to school" var="boy" />
<c:out value="${fn:toLowerCase(boy)}" />
<c:out value="${fn:toUpperCase(boy)}" />

Для удаления символов пробелов, размещенных по обоим краям строки, используйте функцию trim:

<c:set value="  boy goes to school   " var="boy" />
<h1><c:out value="${fn:trim(boy)}"/></h1>

Чтобы заменить некоторый символ в строке на другой, используем функцию replace:

<c:set value="boy goes to school" var="boy" />
<c:out value="${fn:replace(boy, 'school', 'bar')}"/>

split и join разделяют и объединяют строки:

<c:set value="1,2,3,4" var="str_digits" /><c:set value="${fn:split(str_digits, ',')}" var="arr_digits" />
<c:set value="${fn:join(arr_digits, '!')}"  var="str2_digits"/>

Ссылки