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
:
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;
}
}
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>
- Обратите внимание на добавление
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>
- Обратите внимание на добавление
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
, как показано в следующем примере:
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.");
}
}
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
:
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}
@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
является типом по умолчанию.