Spring MVC Tutorial

 
 
 
Сообщения:9476
Здесь описывается Spring MVC 2, если вы решили изучить технологию, а не просто работаете в legacy проекте, лучше начинайте сразу с 3его на аннотациях.
Intro
Структура Spring MVC приложения
Конфигурация web.xml
Action-servlet
Controllers
SimpleFormController
MultiActionController
Data Binding
Simple Validation

Дальше будет больше. Если кто хочет помочь или увидит какие-то боки, сообщайте, буду признателен.
Изменен:19 май 2012 12:27
 
 
Сообщения:9476
Intro

Задача любого Web MVC фреймворка - сделать разработку Web-приложений простой, напрочь отделив Представление, Контроллер и Модель данных приложения. Spring MVC - не есть исключение. В данном цикле мы рассмотрим построение простого Web приложения с использованием
  • Spring MVC - для построение MVC-архитектуры
  • Spring Security - для распределения прав и возможностей пользователей
  • SiteMesh - для построения шаблона страниц
  • Hibernate - для работы с БД
  • Maven - для управления структурой и жизненным циклом проекта
В конце концов будет создано полноценное мини Web приложение, так же, для ленивых проект будет выложен в архиве.
Хочу обратить внимание, что для полного понимания материала, нужно иметь хотя бы начальные знания в Servlet API.
Для тех, кто не использует Maven, вам потребуются следующие библиотеки:
  • spring core, spring beans - основные библиотеки для использования минимальных возможностей Spring - IoC.
  • spring webmvc, spring web - для работы со Spring MVC классами.

Как вы могли заметить, библиотеки указаны версии 2.5.6, однако последняя версия Spring Framework - 3.x. Здесь хотелось бы уточнить, что изучив 2.5.6 вы ничего не потеряете, т.к. 3.0 полностью совместим с более ранней версией. Так же важно отметить, что Spring MVC приложения бывают двух типов: основанные на аннотациях и на XML. Лично я обожаю XML, поэтому здесь будет рассмотрен соответственно вариант не на аннотациях ;) Те, кому интересен вариант с оными, могут взглянуть на статью по Spring MVC 3.
Изменен:29 апр 2010 11:22
 
 
Сообщения:9476
Структура Spring MVC приложения

Любое Spring MVC приложение имеет в себе:
1. web.xml который конфигурируется таким образом, чтоб Spring MVC перехватывал и обрабатывал все запросы самостоятельно.
2. Контейнер контроллеров - это конфигурационный файл, в котором, главным образом, описываются контроллеры, фильтры и страницы, с которыми эти элементы должны работать. Этот файл как правило лежит в WEB-INF.

Для осуществления своей первоначальной цели, данный фреймворк имеет такие сущности:
  • Controller - это класс, функция которого - обработать запрос пользователя и дать какой-то ответ. Controller - это интерфейс, и все контроллеры системы обязаны реализовывать этот интерфейс.
  • ModelAndView - это класс, объект которого будет возвращен контроллером. Этот объект содержит в себе данные, которые необходимо отобразить на странице. Можно рассматривать этот класс как контейнер атрибутов и параметров.
Изменен:27 ноя 2009 13:07
 
 
Сообщения:9476
Конфигурация web.xml

web.xml - это главный файл, в котором описываются сервлеты, фильтры и т.д. Для того, чтоб Spring мог перехватывать запросы, которые посылает клиент, мы должны прописать в нем специальный сервлет, предоставляемый Spring'ом. Имя этому сервлету: Dispatcher Servlet.
    <servlet>
        <servlet-name>Action</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Action</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>

</web-app>
Здесь мы дали имя сервлету - Action. Имя это очень важно, потому как файл с контроллерами должен быть назван [servletName]-servlet.xml, поэтому будьте бдительны, чтоб эти названия совпадали.
Итак, Action-servlet.xml - это наш WebApplicationContext - обычный ApplicationContext, только имеет дополнительные фичи касательно Web разработок. Например, он знает с каким сервлетом он ассоциирован. Доступ к нему, если понадобиться, можно получить через класс RequestContextUtils.
Нам так же понадобятся еще несколько Spring-контекстов, их можно указать следующим образом в web.xml:
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/applicationContext.xml</param-value>
	</context-param>
Для того, чтоб этот контекст был загружен, нужно объявить слушатель:
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

Итого, наш web.xml будет выглядеть следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<servlet>
		<servlet-name>Action</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>Action</servlet-name>
		<url-pattern>*.form</url-pattern>
	</servlet-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/ApplicationContext.xml</param-value>
	</context-param>

</web-app>
 
 
Сообщения:9476
Action-servlet

Итак, у нас есть на данный момент два конфига:
  • Action-servlet.xml - напоминаю это файл, где будут описаны все контроллеры. Название его взято не с неба - мы его прописали в нашем главном сервлете в web.xml.
  • ApplicationContext.xml - это произвольный Spring-контекст, в котором мы будем описывать какие-либо бины. Этот конфигурационный файл мы прописали в <context-param> в web.xml. Он у нас будет загружаться только потому, что мы его там прописали :)

Прежде, чем идти дальше, давайте определимся с таким понятием, как view. View - это логическое имя страницы. То есть если у нас есть страница home.jsp, на нее переходят по адресу home.html, обрабатывает этот запрос HomeController, то view здесь будет - home. На самом деле можно определить любое название для вида, однако, дабы не сбивать людей с толку, его называют так, чтоб имя логически подходило под контекст.
Рассмотрим пример Action-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!--Controllers-->

    <bean name="loginController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
        <property name="viewName" value="login" />
    </bean>
    <bean name="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/login.form">loginController</prop>
            </props>
        </property>
    </bean>
    <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/pages/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>
Теперь разложим все по полочкам:
  • SimpleUrlHandlerMapping - этот класс отвечает за выбор контроллера для обработки адреса. То есть мы жестко прописываем имя контроллера и URL, которую он будет обрабатывать. В данном случае в нем объявлен только один контроллер - loginController, который обрабатывает запросы с адресом /login.html. Есть еще несколько handler'ов, каждый из них по своему находит нужный ему контроллер по URL.
  • InternalResourceViewResolver - этот класс будет делать следующее: получать логическое имя вида(view), добавлять к нему с обоих сторон префикс и суффикс. Так мы получим название страницы, которую нужно запустить. Например, если мы работаем с view, который называется index, то после обработки этого названия view resolver'ом мы получим /WEB-INF/pages/jsp/index.jsp, что будет значить, что именно эту страницу и нужно показать пользователю.
  • Controller - контроллеры могут быть разных классов, в примере выше это ParameterizableViewController, который представляет собой стандартный Spring Controller, ничего не делающий, кроме как определяющий view(что это значит узнаем позже).
Изменен:27 дек 2009 08:24
 
 
Сообщения:9476
Controllers

Контроллеры - это тот слой, в котором должна выполняться логика обработки форм, ответы сервера, разбор параметров, принятых со страницы и т.д., то есть это вместилище бизнес логики приложения.
В Spring MVC существует целая иерархия контроллеров, базовый интерфейс среди которых, - Controller. Все контроллеры в Spring MVC реализуют этот интерфейс. Однако нам реализовывать его не нужно будет. Дабы избавить нас от многих монотонных и неинтересных движений, в Spring MVC есть несколько контроллеров для разных видов запросов и ответов. Самые используемые - это AbstractController, SimpleFormController и MultiActionController. Однако иногда бывает полезным AbstractWizardFormController, с помощью которого можно создать последовательность страниц как обычные визарды в настольных приложениях.
Рассмотрим обычный контроллер для стартовой страницы приложения-библиотеки. Эта страница будет выводить "драсте" и количество зарегистрированных пользователей на сайте.
package ru.javatalks.library.viewcontroller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author ctapobep
 *         26.12.2009 17:11:56
 */
public class IndexController extends AbstractController {
    private String view;

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        ModelAndView mv = new ModelAndView(view);
        int amountOfPeople = 1;
        mv.addObject("amountOfPeople", amountOfPeople);
        return mv;
    }

    public void setView(String view) {
        this.view = view;
    }
}
index.jsp, как и все подобные страницы, будет лежать в каталоге WEB-INF/pages/jsp, и выглядит она так:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Здоровеньки булы! Нас уже <c:out value="${amountOfPeople}"/>
В нашем Action-servlet.xml теперь появится следующее содержимое(содержимое из предыдущего поста нам пока не нужно, обойдемся без него):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean name="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                  <prop key="/index.form">indexController</prop>
            </props>
        </property>
    </bean>
    <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/pages/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="indexController" class="ru.javatalks.library.viewcontroller.IndexController">
        <property name="view" value="index"/>
    </bean>
</beans>
Итак, подведем итоги:
Наш IndexController получает запрос по URL http://localhost:8080/library/index.form, далее он обрабатывает запрос, кладет в request переменную, дальше Spring MVC передает управление странице index.jsp (если кто не заметил, мы в бин indexController заинжектили строку index, а в контроллере мы создали ModelAndView, куда в конструктор передали эту строку - это и есть наш view, который будет после трансформирован в WEB-INF/pages/jsp/index.jsp). JSP выводит нам соответствующее сообщение, на этом цикл завершается.
Примечание: если вы свое приложение назвали не library, то и в адресной строке, соответственно, нужно тоже установить свое название.
Изменен:29 дек 2009 04:43
 
 
Сообщения:9476
SimpleFormController

SimpleFormController - это тот контроллер, который следует использовать если на странице есть формы. Рассмотрим контроллер для регистрации пользователей на сайте. В этом контроллере будут определены основные методы SimpleFormController'a, о которых следует знать. Итак, на форме будут присутствовать поля для ввода имени, пароля, а так же нужно будет выбрать страну, в которой находиться пользователь. Соответствующий бин в Action-servlet.xml выглядит так:
    <bean id="registrationController"
          class="ru.javatalks.library.viewcontroller.RegistrationController">
        <property name="formView" value="registration"/>
        <property name="successView" value="home"/>
        <property name="commandClass"
                  value="ru.javatalks.library.viewcontroller.dto.RegistrationDto"/>
        <property name="commandName" value="dto"/>
    </bean>
По пунктам:
  • formView - это как раз тот view, на который будет отправляться пользователь в случае ошибок при вводе или каких-то еще нестандартных ситуаций. В случае регистрации, если пользователь ввел что-то неверно, мы его отправляем обратно на эту же страницу.
  • successView - на этот view мы перейдем, если зарегистрируемся успешно. Как правило пользователь переходить на домашнюю страницу в таком случае. Мы не исключение
  • commandClass - в данном случае это класс, в поля которого будут заполняться формы. То есть если мы определили форму, на которой есть имя, пароль и страна, потом подсунули обычный POJO с такими же полями, описали на форме какое поле соответствует какому полю в классе, и Spring MVC сам заполнит эти поля в бин. Обычно объекты, в которые заполняются данными для передачи куда-либо, называются DTO(Data Transfer Object).
  • commandName - это название объекта на странице. То есть для того, чтоб обратиться к полям нашего DTO, мы должны дать ему имя - идентификатор объекта на странице. Аналогично, если мы сервлетом передали какой-то объект в request, то на странице мы сможем обратиться по его имени через EL: ${objectName.field}. Подобные принципы использует и Spring MVC для обращению к command object на странице.
Так выглядит этот пресловутый DTO:
package ru.javatalks.library.viewcontroller.dto;

/**
 * 27.12.2009 10:50:24
 *
 * @author ctapobep
 */
public class RegistrationDto {
    private String username;
    private String password;
    private String country;

    public RegistrationDto() {
    }

    public RegistrationDto(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
А вот код самого контроллера:
package ru.javatalks.library.viewcontroller;

import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import ru.javatalks.library.viewcontroller.dto.RegistrationDto;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

/**
 * 27.12.2009 10:42:04
 *
 * @author ctapobep
 */
public class RegistrationController extends SimpleFormController {

    @Override
    protected Object formBackingObject(HttpServletRequest httpServletRequest) throws Exception {
        return new RegistrationDto();
    }

    @Override
    protected Map referenceData(HttpServletRequest httpServletRequest) throws Exception {
        Map<String, Object> attributes = new HashMap<String, Object>(1);
        attributes.put("countries", getAllCountries());
        return attributes;
    }

    @Override
    protected ModelAndView onSubmit(Object command, BindException e) throws Exception {
        RegistrationDto dto = (RegistrationDto) command;
        ModelAndView mv = new ModelAndView(getSuccessView());
        mv.addObject("username", dto.getUsername());
        mv.addObject("country", dto.getCountry());
        return mv;
    }

    public Map<String, String> getAllCountries() {
        Map<String, String> map = new TreeMap<String, String>();
        Locale.setDefault(new Locale("en"));
        Locale[] locales = Locale.getAvailableLocales();
        for (int i = 0; i < locales.length; i++) {
            String country = locales[i].getDisplayCountry();
            map.put(country, country);
        }
        return map;
    }

}
Опять же по пунктам:
  • formBackgingObject() - этот метод, если его не переопределять, возвращает новый объект нашего RegistrationDto класса. Поэтому этот класс должен обязательно иметь конструктор по умолчанию - без параметров. В данном случае мы тоже создаем просто новый объект - это сделано чисто для того, чтоб показать, что этот метод есть. Мы б могли, кстати, заполнить этот объект какими-то значениями по умолчанию, тогда на форме сразу появятся эти значения в полях ввода.
  • referenceData() - метод, который возвращает какие-то дополнительные, как правило статические, данные, которые нужно показать пользователю сразу. В данном случае мы возвращает список стран, чтоб он мог выбрать одну из них в выпадающем списке (на самом деле этот способ не вернет все страны мира, в реальных приложениях эти значения должны храниться где-то в БД). Так вот, карта, возвращаемая данным методом, помещается в атрибуты request, то есть к этим объектам, что находятся в Map, можно обращаться с помощью обычного EL.
  • onSubmit() - выполняется когда пользователь делает submit формы. Здесь мы, кстати, сталкиваемся вновь с нашим ModelAndView. Помните мы установили значение для successView - в конструктор, как видите, мы передаем этот view в конструктор. После этого, выполнение переходит на successView.
Теперь вы увидите страницу нашей регистрации. Не печальтесь, если увидите там незнакомые теги, мы их все рассмотрим позже, пока интуитивно вы должны понимать приблизительный смысл:
registration.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head><title>Registration</title></head>
<body>
<form:form commandName="dto">
    <form:input path="username"/>
    <form:password path="password"/>
    <form:select path="country">
        <form:options items="${countries}"/>
    </form:select>
    <input type="submit">
</form:form>
</body>
</html>
А вот home.jsp, на которую мы переходим после submit регистрации:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Simple jsp page</title></head>
<body><c:out value="${username}"/>
<br><c:out value="${country}"/></body>
</html>
Если захотите уже сейчас попробовать самостоятельно запустить приложение, то увидите после submit'a регистрации эту home-страницу с теми значениями, что вы вводили. Если кто не догадался, Action-servlet.xml теперь будет содержать следующие строки:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean name="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/index.form">indexController</prop>
                <prop key="/registration.form">registrationController</prop>
            </props>
        </property>
    </bean>
    <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/pages/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="indexController" class="ru.javatalks.library.viewcontroller.IndexController">
        <property name="view" value="index"/>
    </bean>
    <bean id="registrationController"
          class="ru.javatalks.library.viewcontroller.RegistrationController">
        <property name="formView" value="registration"/>
        <property name="successView" value="home"/>
        <property name="commandClass"
                  value="ru.javatalks.library.viewcontroller.dto.RegistrationDto"/>
        <property name="commandName" value="dto"/>
    </bean>
</beans>
Однако имейте в виду, что просто перейти на страницу home не выйдет, т.к. мы не определили ни URL для нее, ни контроллер.
 
 
Сообщения:9476
MultiActionController

Данный класс используется когда нам нужно дабы один и тот же контроллер отвечал за несколько дел. Итак, сразу к примеру. На странице будет две ссылки: Say Hello & Say Bye. Мы сделаем так, чтоб при нажатии на эти ссылки вызывались разные методы контроллера. Итак, страница say.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Say something</title>
</head>
<body>
<a href="say.form?word=hello">Say Hello</a>
<a href="say.form?word=bye">Say Bye</a>
</body>
</html>
Как видите ссылки указывают на одну и ту же страницу, однако параметр word у них разный. По нему мы и будет определять какой метод будет отвечать за какую ссылку. Вот наш контроллер:
package ru.javatalks.library.viewcontroller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 29.12.2009 7:03:49
 *
 * @author ctapobep
 */
public class SayController extends MultiActionController {
    
    public ModelAndView hello(HttpServletRequest request, HttpServletResponse response) {
        return new ModelAndView("empty", "response", "hello");
    }

    public ModelAndView bye(HttpServletRequest request, HttpServletResponse response) {
        return new ModelAndView("empty", "response", "bye");
    }
}
Тут есть два метода, имена которых равны значениями параметра word на html-странице. Это важно, у них должны быть одинаковые названия. Контроллер в Action-servlet.xml описывается следующим образом:
    <bean id="sayController" class="ru.javatalks.library.viewcontroller.SayController">
        <property name="methodNameResolver">
            <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
                <property name="paramName" value="word"/>
            </bean>
        </property>
    </bean>
То есть мы описали по какому параметру определять какой метод контроллера вызывать. Собственно теперь наш контроллер рабочий, осталось только сделать empty.jsp, которая указывается при создании ModelAndView (а можно писать прямо в HttpServletResponse, выбирайте сами что вам больше по-душе). Тут все просто, эта страница нам будет еще помогать, когда нам нужно будет просто вывести какой-то текст:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
${response} 
Теперь при нажатии на одну ссылку, вызовется один метод контроллера, а при нажатии на другую соответственно другой метод.
Однако это не единственный способ применения MultiActionController'a. В вышеописанном случае контроллер привязан только к одной ссылке - say.form и метод определяется по значению параметра. Однако мы можем привязать его к двум URL:
                <prop key="/hello.form">sayController</prop>
                <prop key="/bye.form">sayController</prop>
Тогда можно обойтись и без параметров, метод будет определяться по URL. И ссылки на html-странице уже будут такими:
<a href="hello.form">Say Hello</a>
<a href="bye.form">Say Bye</a>
А вообще эти методы не обязательно должны быть именно с такой сигнатурой. Вот возможные варианты:
public (ModelAndView | Map | String | void) actionName(HttpServletRequest, HttpServletResponse [,HttpSession] [,CommandObject]);
Изменен:15 июл 2010 21:40
 
 
Сообщения:9476
Data Binding

В данном контексте data binding (связка данных) - это привязывание полей формы к полям классов. Эта тема непосредственно связана с SimpleFormController. В принципе мы уже столкнулись со связкой данных, и она довольно интуитивно понятна. Давайте немного изменим registration.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head><title>Registration</title></head>
<body>
<form:form commandName="dto">
    <table>
        <tr>
            <td>Username:</td>
            <td><form:input path="username"/></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><form:password path="repeatPassword"/></td>
        </tr>
        <tr>
            <td>Repeat Password:</td>
            <td><form:password path="password"/></td>
        </tr>
        <tr>
            <td>Country:</td>
            <td>
                <form:select path="country">
                    <form:options items="${countries}"/>
                </form:select>
            </td>
        </tr>
        <tr><td><input type="submit" value="Submit"></td></tr>
    </table>
</form:form>
</body>
</html>
Итак, здесь мы используем не стандартные HTML input tags, а Spring-tags. В этих тегах запрятана логика связки полей модели страницы (backing object) с полями на странице. Вот здесь мы описали как называется объект, с которым будет работать форма:
<form:form commandName="dto">
Напомню, что это название должно совпадать с тем, что указано во view в action-servlet.xml для RegistrationController. Далее мы описываем какое поле из модели страницы (RegistrationDto) должно заполняться из input тега:
<form:password path="password"/>
Если мы допустим ошибку, на странице увидем stack trace, который будет говорить, что такое-то свойство у RegistrationDto не найдено, поэтому пишите аккуратно. Стоит заметить, что если мы не ипользуем тег <form.. >, значит мы не можем просто связать поле с моделью таблицы, в таком случае нужно будет описать полный "путь" к свойству. Допустим поле age находиться вне формы, а просто в теге body:
<form:input commandName="dto.age">
Кстати, если вы были внимательны, то, наверное, заметили, что одно поле было добавлено, а значит и RegistrationDto должен поменяться. Добавьте поле и get/set методы все в тот же класс:
private String repeatPassword;
Так же хотелось бы заметить, что некоторые все еще пытаются использовать тег bind для связки, однако он считается уже устаревшим и имеющим более достойные альтернативы.
 
 
Сообщения:9476
Simple Validator

Процесс валидации опять же связан с SimpleFormController'ом. Валидация в Spring MVC возможна двумя способами: с помощью обычного класса, который реализует интерфейс Validator или с помощью Struts-валидации, основанной на XML. Сейчас мы поговорим про первый способ.
Итак, валидация касается 3х основных компонентов: полей на странице, модели страницы (backing object) и собственно валидатора. Создадим валидатор для формы регистрации и назовем его RegistrationValidator. В нем мы будем проверять ввел ли пользователь пароли, имя пользователя, правильный ли e-mail был введен:
package ru.javatalks.library.viewcontroller.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import ru.javatalks.library.viewcontroller.dto.RegistrationDto;

import java.util.regex.Pattern;

/**
 * Date: 08.02.2010 16:36:18
 *
 * @author ctapobep
 */
public class RegistrationValidator implements Validator {
    public boolean supports(Class aClass) {
        return aClass.equals(RegistrationDto.class);
    }

    public void validate(Object o, Errors errors) {
        RegistrationDto dto = (RegistrationDto) o;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "registration.username.empty", "Username should have more than 3 letters");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "registration.password.empty", "Password is the mandatory field!");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "repeatPassword", "registration.repeatPassword.empty", "Repeat the password");
        if (!dto.getPassword().equals(dto.getRepeatPassword())) {
            errors.rejectValue("repeatPassword", "registration.repeatPassword.notEquals", "Passwords are not equals");
        }
        validateEmailSyntax(dto.getEmail(), "email", "registration.email.wrongPattern", "Email is wrong!", errors);
    }

    public void validateEmailSyntax(String email, String field, String errorCode, String defaultMessage, Errors errors) {
        Pattern p = Pattern.compile(".+@.+\\.[a-zA-z]+");
        if (!p.matcher(email).find()) {
            errors.rejectValue(field, errorCode, defaultMessage);
        }
    }
}
Теперь - основные моменты:
  • Все валидаторы должны реализовывать интерфейс Validator.
  • Errors - сюда помещаются все ошибки, которые позже будут выведены на страницу. Замечу, что этот же объект Errors принимает участие в методе onSubmit() в SimpleFormController.
  • Про методы rejectXXX() можно почитать в документации, однако основную мысль давайте-таки рассмотрим. Вот пример:
    errors.rejectValue(field, errorCode, defaultMessage);
    Здесь мы передаем в объект errors поле модели страницы, к которому относится ошибка, код ошибки (в случае, если у нас есть и18я и мы описали код сообщения в resource bundles), так же мы передаем сообщение по умолчанию, которое будет выведено, если соответствующей записи в resource bundles не найдется. Стоит заметить, что если использовать rejectValue(), который принимает два параметра, то сообщение по умолчанию в него не передается, соответственно если код ошибки не был найдет в и18ых файлах, на страницу выведется stack trace с ошибкой, мол, не найдена такая запись для такой-то локали.
  • ValidationUtils - стандартный класс-утилита, которая помогает избежать лишнего кодописания.
Теперь что же у нас появится на странице:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head><title>Registration</title></head>
<body>
<form:form commandName="dto">
    <table>
        <tr>
            <td>Username:</td>
            <td><form:input path="username"/></td>
            <td><form:errors path="username"/> </td>
        </tr>
        <tr>
            <td>E-mail:</td>
            <td><form:input path="email"/></td>
            <td><form:errors path="email"/> </td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><form:password path="password"/></td>
            <td><form:errors path="password"/> </td>
        </tr>
        <tr>
            <td>Repeat Password:</td>
            <td><form:password path="repeatPassword"/></td>
            <td><form:errors path="repeatPassword"/> </td>
        </tr>
        <tr>
            <td>Country:</td>
            <td>
                <form:select path="country">
                    <form:options items="${countries}"/>
                </form:select>
            </td>
        </tr>
        <tr><td><input type="submit" value="Submit"></td></tr>
    </table>
</form:form>
</body>
</html>
Здесь мы начали использовать тег form:errors. В атрибуте path мы указываем поле модели страницы, для которого будет выведено сообщение об ошибке, если такая произойдет. В принципе это все, что нам нужно знать на данном этапе. Кстати, заметьте, что добавилось поле email, добавьте соответствующее поле и в классе RegistrationDto.
Последний штрих - это зарегистрировать наш валидатор. Сделать это просто - просто передать его в наш контроллер:
    <bean id="registrationController" class="ru.javatalks.library.viewcontroller.RegistrationController">
        <property name="formView" value="registration"/>
        <property name="successView" value="home"/>
        <property name="commandClass" value="ru.javatalks.library.viewcontroller.dto.RegistrationDto"/>
        <property name="commandName" value="dto"/>
        <property name="validator" ref="registrationValidator"/>
    </bean>
    <bean id="registrationValidator" class="ru.javatalks.library.viewcontroller.validator.RegistrationValidator"/>
Стоит так же заметить, что у контроллера может быть целый список валидаторов, кои нужно передавать в свойство validators.
 
 
Сообщения:319
Если бы ещё о Play Framework такая статья была - вообще бы было круто... :D
 
 
Сообщения:21
Вопрос по Data Binding. Как реализовать такое, если у нас в бине скажем список? Например есть бин в котором есть свойство означающее вид топлива типа List<Fuel> fuels. Где класс Fuel состоит из двух свойств это код топлива и количество литров. Как этот бин связать с формой?
 
 
Сообщения:131
Quote:
Теперь разложим все по полочкам:
SimpleUrlHandlerMapping - этот класс отвечает за выбор контроллера для обработки адреса. То есть мы жестко прописываем имя контроллера и URL, которую он будет обрабатывать. В данном случае в нем объявлен только один контроллер - loginController, который обрабатывает запросы с адресом /login.html. Есть еще несколько handler'ов, каждый из них по своему находит нужный ему контроллер по URL.

 <bean name="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/login.form">loginController</prop>
            </props>
        </property>
    </bean>


key="/login.form"
это называется урлом тут?
почему .form?
Изменен:06 авг 2013 11:05
 
 
Сообщения:131
и не вижу где же таки скачать можно?
 
 
Сообщения:72
Староверъ:
Здесь описывается Spring MVC 2, если вы решили изучить технологию, а не просто работаете в legacy проекте, лучше начинайте сразу с 3его на аннотациях.


Действительно, в третьей версии стало намного проще. Советую прочесть Spring Pro 3
 
Модераторы:wedens
Сейчас эту тему просматривают:Нет