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) входить до pring-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. Для отримання вичерпної інформації щодо окремих тегів див. довідник з АРІ або звернися до опису бібліотеки тегів.

Перетворення 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 є типом за замовчуванням.