Spring Framework имеет встроенную интеграцию для использования Spring MVC с JSP и JSTL.

Распознаватели представлений

При разработке JSP обычно объявляется бин InternalResourceViewResolver.

InternalResourceViewResolver можно использовать для диспетчеризации к любому ресурсу сервлета, но в особенности к JSP. В качестве наиболее оптимального метода мы настоятельно рекомендуем размещать файлы JSP в директории под директорией "WEB-INF", чтобы клиенты не могли получить к ним прямой доступ.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

JSPs против JSTL

При использовании стандартной библиотеки тегов JSP (JSTL) необходимо использовать специальный класс представления JstlView, поскольку JSTL требует определенной подготовки, чтобы такие вещи, как функции I18N, работали.

Библиотека тегов JSP для Spring

Spring обеспечивает привязку данных параметров запроса к объектам команд, как описано в предыдущих главах. Чтобы облегчить разработку JSP-страниц в сочетании с этими функциями привязки данных, Spring предусматривает несколько тегов, которые еще больше упрощают работу. Все теги Spring имеют функции HTML-экранирования, которые можно активировать или дезактивировать для экранирования символов.

Дескриптор библиотеки тегов spring.tld (TLD) включен в spring-webmvc.jar. Для получения исчерпывающей информации по отдельным тегам просмотрите справочник по API или обратитесь к описанию библиотеки тегов.

Библиотека тегов форм для Spring

Начиная с версии 2.0, Spring предусматривает полный набор тегов, поддерживающих привязку данных для работы с элементами форм при использовании JSP и Spring Web MVC. Каждый тег обеспечивает поддержку набора атрибутов соответствующего аналога HTML-тега, что делает теги привычными и интуитивно понятными в использовании. Генерируемый тегами HTML соответствует стандартам HTML 4.01/XHTML 1.0.

В отличие от других библиотек тегов форм/вводов, библиотека тегов форм для Spring интегрирована с Spring Web MVC, что позволяет предоставить тегам доступ к объекту команды и справочным данным, с которыми работает ваш контроллер. Как показано в следующих примерах, теги форм облегчают разработку, чтение и сопровождение JSP.

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

Конфигурация

Библиотека тегов форм поставляется в комплекте с spring-webmvc.jar. Дескриптор библиотеки называется spring-form.tld.

Чтобы использовать теги из этой библиотеки, добавьте следующую директиву в верхнюю часть вашей JSP-страницы:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

где form – префикс имени тега, который вы хотите использовать для тегов из этой библиотеки.

Тег формы

Этот тег визуализирует HTML-элемент "form" и открывает путь привязки к внутренним тегам для привязывания. Он помещает объект команды в PageContext, чтобы к объекту команды могли обращаться внутренние теги. Все остальные теги в этой библиотеке являются вложенными тегами тега form.

Предположим, что у нас есть объект предметной области под названием User. Это JavaBean с такими свойствами, как firstName и lastName. Можно использовать его в качестве базового объекта формы нашего контроллера формы, который возвращает файл form.jsp. В следующем примере показано, как может выглядеть form.jsp:

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

Значения firstName и lastName извлекаются из объекта команды, помещенного в PageContext контроллером страницы. Продолжайте читать, чтобы ознакомиться с более сложными примерами использования внутренних тегов с тегом form.

В следующем листинге показан сгенерированный HTML, который выглядит как стандартная форма:

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value="Harry"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value="Potter"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

В предыдущем JSP предполагается, что имя переменной базового объекта формы - command. Если вы поместили базовый объект формы в модель под другим именем (определенно оптимальный вариант), то можете привязать форму к именованной переменной, как показано в следующем примере:

<form:form modelAttribute="user">
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

Тег input

Этот тег визуализирует HTML-элемент input с привязанным значением и type='text' по умолчанию. Вы также можете использовать специфические для HTML5 типы, такие как email, tel, date и другие.

Тег checkbox

Этот тег визуализирует HTML-тег input с type, установленным в checkbox.

Предположим, что у нашего User есть такие личные пользовательские параметры, как подписка на рассылку новостей и список увлечений. В следующем примере показан класс Preferences:

Java
public class Preferences {
    private boolean receiveNewsletter;
    private String[] interests;
    private String favouriteWord;
    public boolean isReceiveNewsletter() {
        return receiveNewsletter;
    }
    public void setReceiveNewsletter(boolean receiveNewsletter) {
        this.receiveNewsletter = receiveNewsletter;
    }
    public String[] getInterests() {
        return interests;
    }
    public void setInterests(String[] interests) {
        this.interests = interests;
    }
    public String getFavouriteWord() {
        return favouriteWord;
    }
    public void setFavouriteWord(String favouriteWord) {
        this.favouriteWord = favouriteWord;
    }
}
Kotlin
class Preferences(
        var receiveNewsletter: Boolean,
        var interests: StringArray,
        var favouriteWord: String
)

Соответствующий файл form.jsp может выглядеть следующим образом:

<form:form>
    <table>
        <tr>
            <td>Subscribe to newsletter?:</td>
            <%-- Подход 1: Свойство имеет тип java.lang.Boolean --%>
            <td><form:checkbox path="preferences.receiveNewsletter"/></td>
        </tr>
        <tr>
            <td>Interests:</td>
            <%-- Подход 2: Свойство является массивом или имеет тип java.util.Collection --%>
            <td>
                Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
                Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
                Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
            </td>
        </tr>
        <tr>
            <td>Favourite Word:</td>
            <%-- Подход 3: Свойство имеет тип java.lang.Object --%>
            <td>
                Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
            </td>
        </tr>
    </table>
</form:form>

Существует три подхода к тегу checkbox, которые должны удовлетворить все ваши потребности касательно флажков.

  • Подход первый: Если связанное значение имеет тип java.lang.Boolean, input(checkbox) помечается как checked, если связанное значение равно true. Атрибут value соответствует разрешенному значению свойства setValue(Object) value.

  • Подход второй: Если связанное значение имеет тип array или java.util.Collection, input(checkbox) помечается как checked, если сконфигурированное значение setValue(Object) присутствует в связанной Collection.

  • Подход третий: В случае любого другого типа связанного значения input(checkbox) помечается как checked, если сконфигурированное setValue(Object) равно связанному значению.

Обратите внимание, что, независимо от подхода, генерируется одна и та же HTML-структура. В следующем фрагменте HTML определено несколько флажков:

<tr>
    <td>Interests:</td>
    <td>
        Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
    </td>
</tr>

Возможно, вы не ожидаете увидеть дополнительное скрытое поле после каждого флажка. Если флажок на HTML-странице не установлен, его значение не отправляется на сервер как часть параметров HTTP-запроса после отправки формы, поэтому необходимо обходное решение для этой особенности HTML, чтобы привязка данных формы Spring работала. Тег checkbox следует существующему соглашению Spring о включении скрытого параметра с префиксом подчеркивания (_) для каждого флажка. Делая это, вы фактически говорите Spring, что "флажок был виден в форме, и я хочу, чтобы мой объект, к которому привязываются данные формы, отражал состояние флажка, независимо ни от чего".

Тег checkboxes

Этот тег визуализирует несколько HTML-тегов input с type установленным в checkbox.

Этот раздел основывается на примере из предыдущего раздела о теге checkbox. Иногда предпочтительнее не перечислять все возможные хобби на странице JSP. Лучше указать список доступных опций во время выполнения и передать его в тег. В этом и заключается назначение тега checkboxes. Вы можете передать Array, List или Map, содержащие доступные опции в свойстве items. Как правило, связанное свойство представляет собой коллекцию, чтобы оно могло содержать несколько значений, выбранных пользователем. В следующем примере показана страница JSP, в которой используется этот тег:

<form:form>
    <table>
        <tr>
            <td>Interests:</td>
            <td>
                <%-- Свойство является массивом или имеет тип java.util.Collection --%>
                <form:checkboxes path="preferences.interests" items="${interestList}"/>
            </td>
        </tr>
    </table>
</form:form>

В этом примере предполагается, что interestList – это List, доступный как атрибут модели, который содержит строки значений для выбора. Если вы используете Map, ключ записи Map используется в качестве значения, а значение записи Map используется в качестве выводимой на экран метки. Вы также можете использовать кастомный объект, в котором можно указать имена свойств для значения с помощью itemValue и метки – с помощью itemLabel.

Тег radiobutton

Этот тег визуализирует HTML-элемент input с type, установленным в radio.

Типичная схема использования предусматривает несколько экземпляров тегов, привязанных к одному и тому же свойству, но с разными значениями, как показано в следующем примере:

<tr>
    <td>Sex:</td>
    <td>
        Male: <form:radiobutton path="sex" value="M"/> <br/>
        Female: <form:radiobutton path="sex" value="F"/>
    </td>
</tr>

Тег radiobuttons

Этот тег визуализирует несколько HTML-элементов input с type, установленным в radio.

Как и в случае с >тегом checkboxes вы можете передать доступные опции в качестве переменной времени выполнения. Для этого можно использовать тег radiobuttons. Вы передаете Array, List или Map, содержащие доступные опции в свойстве items. Если вы используете Map, ключ записи Map используется в качестве значения, а значение записи Map – в качестве выводимой на экран метки. Вы также можете использовать пользовательский объект, в котором можно указать имена свойств для значения с помощью itemValue и метки – с помощью itemLabel, как показано в следующем примере:

<tr>
    <td>Sex:</td>
    <td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>

Тег password

Этот тег визуализирует HTML-тег input с типом, установленным в password, с привязанным значением.

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password"/>
    </td>
</tr>

Обратите внимание, что по умолчанию значение пароля не показывается. Если нужно, чтобы значение пароля показывалось, то можете установить значение атрибута showPassword в true, как показано в следующем примере:

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password" value="^76525bvHGq" showPassword="true"/>
    </td>
</tr>

Тег select

Этот тег визуализирует HTML-элемент "select". Он поддерживает привязку данных к выбранной опции, а также использование вложенных тегов option и options.

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

<tr>
    <td>Skills:</td>
    <td><form:select path="skills" items="${skills}"/></td>
</tr>

Если User владеет навыками травологии, то HTML-источник строки "Skills" может выглядеть следующим образом:

<tr>
    <td>Skills:</td>
    <td>
        <select name="skills" multiple="true">
            <option value="Potions">Potions</option>
            <option value="Herbology" selected="selected">Herbology</option>
            <option value="Quidditch">Quidditch</option>
        </select>
    </td>
</tr>

Тег option

Этот тег визуализирует HTML-элемент option. Он устанавливает selected, основываясь на связанном значении. В следующем HTML показан типичный вывод для него:

<tr>
    <td>House:</td>
    <td>
        <form:select path="house">
            <form:option value="Gryffindor"/>
            <form:option value="Hufflepuff"/>
            <form:option value="Ravenclaw"/>
            <form:option value="Slytherin"/>
        </form:select>
    </td>
</tr>

Если дом User-а находится в Гриффиндоре, то HTML-источник строки "House" будет выглядеть следующим образом:

<tr>
    <td>House:</td>
    <td>
        <select name="house">
            <option value="Gryffindor" selected="selected">Gryffindor</option>
            <option value="Hufflepuff">Hufflepuff</option>
            <option value="Ravenclaw">Ravenclaw</option>
            <option value="Slytherin">Slytherin</option>
        </select>
    </td>
</tr>
  1. Обратите внимание на добавление selected атрибута.

Тег options

Этот тег визуализирует список HTML-элементов option. Он устанавливает selected атрибут на основе связанного значения. В следующем HTML показан типичный вывод для него:

<tr>
    <td>Country:</td>
    <td>
        <form:select path="country">
            <form:option value="-" label="--Please Select"/>
            <form:options items="${countryList}" itemValue="code" itemLabel="name"/>
        </form:select>
    </td>
</tr>

Если бы User жил в Великобритании, HTML-источник строки "Country" выглядел бы следующим образом:

<tr>
    <td>Country:</td>
    <td>
        <select name="country">
            <option value="-">--Please Select</option>
            <option value="AT">Austria</option>
            <option value="UK" selected="selected">United Kingdom</option>
            <option value="US">United States</option>
        </select>
    </td>
</tr>
  1. Обратите внимание на добавление selected атрибута.

Как показано в предыдущем примере, комбинированное использование тега option с тегом options создает тот же стандартный HTML, но позволяет явно указать значение в JSP, которое предназначено только для вывода на экран (где ему и место), как, например, в случае строки по умолчанию в примере: "-- Please Select".

Атрибут items обычно заполняется коллекцией или массивом объектов элементов данных. itemValue и itemLabel ссылаются на свойства бина этих объектов элементов данных, если они указаны. В противном случае сами объекты элементов данных превращаются в строки. Как вариант, можно задать Map элементов, и в этом случае ключи Map будут интерпретироваться как значения опций, а значения Map будут соответствовать меткам опций. Если также заданы метки itemValue или itemLabel (или обе), то свойство значения элемента данных будет применяться к ключу Map, а свойство метки элемента данных – к значению Map.

Тег textarea

Этот тег визуализирует HTML-элемент textarea. В следующем HTML показан типичный выводимый результат для него:

<tr>
    <td>Notes:</td>
    <td><form:textarea path="notes" rows="3" cols="20"/></td>
    <td><form:errors path="notes"/></td>
</tr>

Тег hidden

Этот тег визуализирует HTML-тег input с type, установленным в hidden, с привязанным значением. Чтобы отправить несвязанное скрытое значение, используйте HTML-тег input с type, установленным в hidden. В следующем HTML показан типичный выводимый результат для него:

<form:hidden path="house"/>

Если будет решено передать значение house как скрытое, то HTML будет выглядеть следующим образом:

<input name="house" type="hidden" value="Gryffindor"/>

Тег errors

Этот тег визуализирует ошибки поля в HTML-элементе span. Он предоставляет доступ к ошибкам, созданным в вашем контроллере или ошибкам, которые были созданы любыми валидаторами, связанными с вашим контроллером.

Предположим, что нам нужно вывести на экран все сообщения об ошибках для полей firstName и lastName после отправки формы. У нас есть валидатор для экземпляров класса User, который называется UserValidator, как показано в следующем примере:

Java
public class UserValidator implements Validator {
    public boolean supports(Class candidate) {
        return User.class.isAssignableFrom(candidate);
    }
    public void validate(Object obj, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
    }
}
Kotlin
class UserValidator : Validator {
    override fun supports(candidate: Class<*>): Boolean {
        return User::class.java.isAssignableFrom(candidate)
    }
    override fun validate(obj: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
    }
}

form.jsp может выглядеть следующим образом:

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <%-- Show errors for firstName field --%>
            <td><form:errors path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <%-- Show errors for lastName field --%>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

Если мы отправим форму с пустыми значениями в полях firstName и lastName, то HTML будет выглядеть следующим образом:

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <%-- Associated errors to firstName field displayed --%>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <%-- Associated errors to lastName field displayed --%>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

Что если мы хотим отобразить весь список ошибок для данной страницы? В следующем примере показано, что тег errors также поддерживает некоторые базовые функции подстановки.

  • path="*": Выводит на экран все ошибки.

  • path="lastName": Выводит на экран все ошибки, связанные с полем lastName.

  • Если path опущен, на экран выводятся только ошибки объекта.

В следующем примере в верхней части страницы отображается список ошибок, а рядом с полями – ошибки, специфические для каждого поля:

<form:form>
    <form:errors path="*" cssClass="errorBox"/>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <td><form:errors path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

HTML будет выглядеть следующим образом:

<form method="POST">
    <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

Дескриптор библиотеки тегов spring-form.tld (TLD) включен в spring-webmvc.jar. Для получения исчерпывающей информации по отдельным тегам см. справочник по API или обратитесь к описанию библиотеки тегов.

Преобразование HTTP-методов

Ключевым принципом REST является использование "унифицированного интерфейса". Это означает, что всеми ресурсами (URL-адресами) можно манипулировать с помощью одних и тех же четырех HTTP-методов: GET, PUT, POST и DELETE. Для каждого метода спецификация HTTP определяет точную семантику. Например, GET всегда должен быть безопасной операцией, то есть не иметь побочных эффектов, а PUT или DELETE должны быть идемпотентными, то есть вы можете повторять эти операции снова и снова, но конечный результат должен быть одним и тем же. Хотя HTTP определяет эти четыре метода, HTML поддерживает только два из них: GET и POST. К счастью, есть два возможных обходных пути: вы можете либо использовать JavaScript для выполнения PUT или DELETE, либо выполнить POST с "настоящим" методом в качестве дополнительного параметра (по образцу скрытого поля ввода в HTML-форме). HiddenHttpMethodFilter из Spring задействует этот последний прием. Этот фильтр является обычным фильтром сервлетов, поэтому его можно использовать в сочетании с любым веб-фреймворком (не только Spring MVC). Добавьте этот фильтр в ваш web.xml, и POST с параметром скрытого method будет преобразован в запрос соответствующего HTTP-метода.

В целях преобразования HTTP-методов тег формы Spring MVC был обновлен с целью обеспечения поддержки установки HTTP-метода. Например, следующий фрагмент взят из примера Pet Clinic:

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

В предыдущем примере выполняется HTTP-метод POST, а "настоящий" метод DELETE скрыт за параметром запроса. Он подхватывается HiddenHttpMethodFilter, который определяется в web.xml, как показано в следующем примере:

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

В следующем примере показан соответствующий метод с аннотацией @Controller:

Java
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
    this.clinic.deletePet(petId);
    return "redirect:/owners/" + ownerId;
}
Kotlin
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
    clinic.deletePet(petId)
    return "redirect:/owners/$ownerId"
}

Теги HTML5

Библиотека тегов формы Spring позволяет вводить динамические атрибуты, что означает, что вы можете вводить любые атрибуты, специфичные для HTML5.

Тег формы input поддерживает ввод атрибута типа, отличного от text. Это позволяет визуализировать новые типы вводимых данных, специфичных для HTML5, такие как email, date, range и другие. Обратите внимание, что вводить type='text' не требуется, так как text является типом по умолчанию.