-
Notifications
You must be signed in to change notification settings - Fork 56
Введение в spring web mvc
Создадим в среде NetBeans проект Maven Java приложения и добавим все необходимые зависимости для построения web-приложения.
Добавляем зависимости для Spring Framework в pom.xml
, вынеся версии в раздел свойств.
pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.10</junit.version>
<spring.version>3.1.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
В качестве сервера сервлетов будем использовать сервер Jetty, а точнее плагин для maven, который позволяет развёртывать приложение в сервере Jetty.
Добавим в pom.xml
описание загрузки плагина для jetty и произведём соответствующие настройки.
Рассмотрим настраиваемые свойства:
-
scanIntervalSeconds
- интервал в секундах через который Jetty проверяет директории на изменения для редеплоя приложения -
connectors
- список подключений -
port
- настраивает порт, на котором будет запущен jetty
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>8.1.8.v20121106</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<stopKey>foo</stopKey>
<stopPort>9999</stopPort>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9090</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build>
После подключения плагина стало возможным проводить развёртывание проекта с использованием команды mvn jetty:run
.
Попробуем запустить проект.
$ mvn jetty:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building pres_0_3 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> jetty-maven-plugin:8.1.8.v20121106:run (default-cli) @ pres_0_3 >>>
[INFO]
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ pres_0_3 ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/wiz/TRASH/myspringlearning/pres_0_3/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ pres_0_3 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ pres_0_3 ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/wiz/TRASH/myspringlearning/pres_0_3/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ pres_0_3 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< jetty-maven-plugin:8.1.8.v20121106:run (default-cli) @ pres_0_3 <<<
[INFO]
[INFO] --- jetty-maven-plugin:8.1.8.v20121106:run (default-cli) @ pres_0_3 ---
[INFO] Configuring Jetty for project: pres_0_3
[INFO] webAppSourceDirectory not set. Defaulting to /home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = /home/wiz/TRASH/myspringlearning/pres_0_3/target/classes
[INFO] Context path = /
[INFO] Tmp directory = /home/wiz/TRASH/myspringlearning/pres_0_3/target/tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] web.xml file = null
[INFO] Webapp directory = /home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
2012-11-17 20:33:08.842:INFO:oejs.Server:jetty-8.1.8.v20121106
2012-11-17 20:33:09.901:INFO:oejpw.PlusConfiguration:No Transaction manager found - if your webapp requires one, please configure one.
Null identity service, trying login service: null
Finding identity service: null
2012-11-17 20:33:12.678:INFO:/:No Spring WebApplicationInitializer types detected on classpath
2012-11-17 20:33:13.749:INFO:oejsh.ContextHandler:started o.m.j.p.JettyWebAppContext{/,file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp},file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
2012-11-17 20:33:13.749:INFO:oejsh.ContextHandler:started o.m.j.p.JettyWebAppContext{/,file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp},file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
2012-11-17 20:33:13.750:INFO:oejsh.ContextHandler:started o.m.j.p.JettyWebAppContext{/,file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp},file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
2012-11-17 20:33:14.063:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:9090
[INFO] Started Jetty Server
[INFO] Starting scanner at interval of 10 seconds.
^C2012-11-17 20:33:18.002:INFO:oejsl.ELContextCleaner:javax.el.BeanELResolver purged
2012-11-17 20:33:18.002:INFO:oejsh.ContextHandler:stopped o.m.j.p.JettyWebAppContext{/,file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp},file:/home/wiz/TRASH/myspringlearning/pres_0_3/src/main/webapp
2012-11-17 20:33:18.058:INFO:oejut.ShutdownThread:shutdown already commenced
[INFO] Jetty server exiting.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Теперь можно приступать к созданию директорий для web-приложения.
В Maven web-приложения располагаются в директории webapp
.
Всё содержимое web-приложения по требованиям Java должно располагаться в директории WEB-INF
(по аналогии с META-INF
).
Создаём директорию src/main/webapp
и src/main/webapp/WEB-INF
.
Теперь необходимо создать дескриптор развертывания для сервлетов (данный файл используется сервером сервлетов, в нашем случае Jetty).
Создаём src/main/webapp/WEB-INF/web.xml
web.xml
<?xml version="1.0" ?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Данные файл описывает запускаемые сервлеты и мэппинг сервлетов на пути на web-сервере.
Рассмотрим секции подробнее:
-
servlet
- задаёт описание сервлета -
servlet-name
- название сервлета, которое будет использовано для мэппинга -
servlet-class
- задаёт класс сервлета (в нашем случае это будетorg.springframework.web.servlet.DispatcherServlet
) -
load-on-startup
- задаёт загрузку сервлета при старте сервера -
servlet-mapping
- описывает отображение частей пути в URL на сервлеты -
url-pattern
- шаблон URL, при отправке запроса на который будет задействован сервлет
В файле web.xml
мы описали, что будет использоваться сервлет диспетчер, рассмотрим работу и необходимость наличия сервлета диспетчера.
Сервлет диспетчер является точкой входа для всех запросов. Фактически данный сервлет реализует шаблон FrontController, то есть принимает запросы, обрабатывает и отдаёт необходимому контроллеру.
В данном случае в качестве серлета диспетчера используется сервлет ``org.springframework.web.servlet.DispatcherServlet`, который входит в состав Spring.
Действия выполняемые данным сервлетом:
- Приём запроса
- Обработка запроса соответствующими фильтрами
- Вызов соответствующих
listener'ов
- Определение класса контроллера
- Передача управления на метод класса контроллера
- Получение от контроллера объекта модели и имени шаблона
- Определение шаблона по имени (
ViewResolver
) - Рендеринг шаблона по имени с передачей в него параметров модели, полученных от контроллера
Схема работы сервлетов на сервере сервлетов (http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html).
Далее нам необходимо создать файл с описанием контекта сервлета. Создаём src/main/webapp/WEB-INF/appServlet-servlet.xml
, в котором опишем настройки для web-приложения.
appServlet-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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<mvc:annotation-driven />
<context:component-scan base-package="a1s.learn.controller" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jspx" />
</bean>
</beans>
Рассмотрим элементы подробнее:
-
<mvc:annotation-driven />
- указывает, что для описания сущностей MVC будут использоваться аннотации -
<context:component-scan base-package="a1s.learn.controller" />
- указывает на пакеты, в которых будет происходить сканирование на наличие аннотаций - Бин
viewResolver
представляет собой объект классаorg.springframework.web.servlet.view.InternalResourceViewResolver
, который производит определение шаблона по имени.- Свойство
prefix
задаёт префикс для шаблонов (в нашем случае это путь) - Свойство
suffix
задаёт суффикс (в нашем случае расширение) для файла шаблона
- Свойство
В случае, если необходимы глобальные настройки для Spring context, которые сервлеты разделяют между собой (например, подключение к БД или настройка messageSource
) существует возможность определить корневой контекст. Для определения корневого контекста необходимо использовать listener org.springframework.web.context.ContextLoaderListener
, который осуществляет соответствующую загрузку, а также указать путь к файлу с корневым контекстом.
Добавим в web.xml следующие строки:
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Рассмотрим подробнее:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root.xml</param-value>
</context-param>
Данный элемент настраивает параметр contextConfigLocation
в рамках контекста и задаёт путь к файлу с корневым контекстом как /WEB-INF/spring/root.xml
.
Элемент <listener />
в свою очередь настраивает слушателя для загрузки соответствующего контекста.
Создадим файл-заготовку для корневого контекса по адресу src/main/webapp/WEB-INF/spring/root.xml
.
root.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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!-- Root context -->
</beans>
Создадим основной контроллер MainController.java
.
MainController.java
package a1s.learn.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/")
public class MainController {
@RequestMapping(method= RequestMethod.GET)
public String main(Model ui)
{
return "user/list";
}
}
Рассмотрим аннотации:
-
@Controller
- указывает, что этот класс является контроллером -
@RequestMapping("/")
- указывает, что этот контроль обрабатывает все URL от корня -
@RequestMapping(method= RequestMethod.GET)
- указывает, что метод обрабатывает методGET
Метод main()
принимает в качестве параметров объект Model ui
, который представляет модель.
В качестве результата контроллер возвращает строку user/list
, что означает, что необходимо произвести рендеринг по шаблону user/list
.
Наши шаблоны для вывода будут располагаться по адресу src/main/webapp/WEB-INF/views/
.
Создаём директорию src/main/webapp/WEB-INF/views/user
для шаблонов отображения данных о пользователе.
Создаём шаблон jspx src/main/webapp/WEB-INF/views/user/list.jspx
.
<?xml version="1.0" encoding="UTF-8"?>
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
version="2.0"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/>
<jsp:output omit-xml-declaration="yes" />
${contacts}
</div>
Рассмотрим создание view более подробно:
- так как используется jspx, то используются более строгие правила оформления в стиле XML и сам заголовок XML-файлов
<?xml version="1.0" encoding="UTF-8"?>
-
xmlns:jsp="http://java.sun.com/JSP/Page"
- указывает пространства имён JSP для jsp tld -
xmlns:spring="http://www.springframework.org/tags"
- указывает пространства имён spring для spring tld -
xmlns:c="http://java.sun.com/jsp/jstl/core"
- указывает пространства имён c для jstl core tld - tld - Tag Library Descriptors, библиотека тэгов
-
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/>
- указывает на тип страницы и кодировку -
<jsp:output omit-xml-declaration="yes" />
- указывает, что необходимо пропустить XML-описание при рендеринге -
${contacts}
указывает, что необходимо вывести элемент модели с ключомcontacts
Создадим простой контроллер, который производит редирект со страницы /
на страницу user/list
и заполняет свойство модели contacts
для вывода и принимает в качестве параметров некий идентификатор.
MainController.java
package a1s.learn.controller;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
@RequestMapping("/")
public class MainController {
@RequestMapping(value="user/list/id/{id}",method= RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public String userList(Model ui, @PathVariable("id") Long id)
{
ui.addAttribute("contacts", "Ce contacts"+id.toString());
return "user/list";
}
@RequestMapping(value="user/list/id/{id}", method= RequestMethod.GET,produces="text/json")
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public String userListJson(Model ui, @PathVariable("id") Long id)
{
return "{id:"+id.toString()+"}";
}
@RequestMapping("/")
public String main(Model ui)
{
return "redirect:user/list";
}
}
Рассмотрим контроллер более подробно:
@RequestMapping("/")
public String main(Model ui)
{
return "redirect:user/list";
}
Данный метод аннотирован с помощью @RequestMapping("/")
, что означает, что используется URL '/'.
Метод возвращает строку redirect:user/list
, что приводит к переходу на страницу user/list
при
@RequestMapping(value="user/list/id/{id}", method= RequestMethod.GET,produces="text/json")
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public String userListJson(Model ui, @PathVariable("id") Long id)
{
return "{id:"+id.toString()+"}";
}
- Аннотация
@ResponseBody
означает, что результатом работы метода(возвращаемым значением) является тело ответа. -
@ResponseStatus(HttpStatus.OK)
- будет возвращаться HTTP-статус 200 OK. -
@RequestMapping(value="user/list/id/{id}", method=RequestMethod.GET,produces="text/json")
- указывает что метод будет вызван при запросе URLuser/list/id/*/
, если метод запроса = GET, а также, что этот метод выдаёт содержимое типаtext/json
. - Параметр
produces
будет сравниваться с заголовкомAccept
. И если клиент не поддерживает такое содержимое, то данный метод вызван не будет. -
@PathVariable("id") Long id
указывает, что в метод необходимо передать параметрid
типаLong
, который необходимо взять из пути (шаблон URL и расположение параметраid
уже описание в@RequestMapping(value="user/list/id/{id}"...
)
@RequestMapping(value="user/list/id/{id}",method= RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public String userList(Model ui, @PathVariable("id") Long id)
{
ui.addAttribute("contacts", "Ce contacts"+id.toString());
return "user/list";
}
Данный метод - аналог предыдущего, но будет работать, если клиент не поддерживает text/json
и проводить рендеринг пошаблону.
Возьмём curl
для проверки работы контроллера.
$ curl -H 'Accept: text/html' -v http://127.0.0.1:9090/user/list/id/123
* About to connect() to 127.0.0.1 port 9090 (#0)
* Trying 127.0.0.1...
* connected
* Connected to 127.0.0.1 (127.0.0.1) port 9090 (#0)
> GET /user/list/id/123 HTTP/1.1
> User-Agent: curl/7.27.0
> Host: 127.0.0.1:9090
> Accept: text/html
>
* additional stuff not fine transfer.c:1037: 0 0
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< Content-Language: ru-RU
< Content-Type: text/html;charset=UTF-8
< Set-Cookie: JSESSIONID=110ioyauffrfjf7v3bir67y77;Path=/
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< Content-Length: 52
< Server: Jetty(8.1.8.v20121106)
<
<div version="2.0">
Ce contacts123
* Connection #0 to host 127.0.0.1 left intact
</div>* Closing connection #0
$ curl -H 'Accept: text/json' -v http://127.0.0.1:9090/user/list/id/123
* About to connect() to 127.0.0.1 port 9090 (#0)
* Trying 127.0.0.1...
* connected
* Connected to 127.0.0.1 (127.0.0.1) port 9090 (#0)
> GET /user/list/id/123 HTTP/1.1
> User-Agent: curl/7.27.0
> Host: 127.0.0.1:9090
> Accept: text/json
>
* HTTP 1.1 or later with persistent connection, pipelining supported
< HTTP/1.1 200 OK
< Content-Length: 8
< Content-Type: text/json
< Server: Jetty(8.1.8.v20121106)
<
* Connection #0 to host 127.0.0.1 left intact
{id:123}* Closing connection #0
Из вывода видно как заголовок Accept
влияет на содержимое, а также видна работа с параметрами.
Spring при работе с формами производит установку свойств объектов классов предметной области, поэтому создадим объекты предметной области для иллюстрации работы с формами.
Создадим классы для пользователя и группы.
User.java
package a1s.learn;
public class User {
protected Long id;
protected Group group;
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", group=" + group + ", name=" + name + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected String name;
}
Group.java
package a1s.learn;
public class Group {
protected String name;
@Override
public String toString() {
return "Group{" + "name=" + name + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Реализуем простую форму в jspx.
<?xml version="1.0" encoding="UTF-8"?>
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
version="2.0"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
${user}
<form action="/user/list/form" method="POST">
<table>
<tr><td>ID</td><td><input name="id" type="text" /></td></tr>
<tr><td>NAME</td><td><input name="name" type="text" /></td></tr>
<tr><td>GROUP</td><td><input name="group.name" type="text" /></td></tr>
<tr><td></td><td><input type="submit" /></td></tr>
</table>
</form>
</div>
Имена элементов указывают на свойства объектов:
-
id
- установить свойствоid
-
group.name
- установить свойствоname
у вложенного объектаgroup
-
groups[2].name
- установить свойствоname
у объекта коллекции(List<Group>
, например)groups
-
groups[MY_GROUP].name
- установить свойствоname
у объекта с ключомMY_GROUP
в Map (Map<Groups>
, например)groups
@RequestMapping(value="user/list", method= RequestMethod.GET)
public String userListGet(Model ui)
{
ui.addAttribute("user", new User());
return "user/list";
}
@RequestMapping(value="user/list/form", method= RequestMethod.POST)
public String userListPost(User u, BindingResult br, Model ui)
{
System.out.println(br.toString());
System.out.println(u.toString());
ui.addAttribute("user", u);
return "user/list";
}
Рассмотрим методы более подробно:
@RequestMapping(value="user/list", method= RequestMethod.GET)
public String userListGet(Model ui)
{
ui.addAttribute("user", new User());
return "user/list";
}
Данный метод вызывается в случае запроса по адресу user/list
с использованием метода GET
. Метод создаёт пустой объект User
сохраняет в модель ui
по ключу user
. Далее управление передаётся view user/list
.
@RequestMapping(value="user/list/form", method= RequestMethod.POST)
public String userListPost(User u, BindingResult br, Model ui)
{
System.out.println(br.toString());
System.out.println(u.toString());
ui.addAttribute("user", u);
return "user/list";
}
Данный метод вызывается в случае запроса по адресу user/list/form
методом POST
. В метод передаются следующие параметры:
-
User u
- объект пользователя. ПараметрыPOST
-запроса будут сконвертированы в объект пользователя. -
BindingResult br
- содержит результаты конвертации параметров запроса в объект пользователя -
Model ui
- объект модели
Данный метод выводит в консоль результат конвертации запроса, а также сам объект пользователя, после чего объект пользователя добавляется в модель.
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
Указывает, что необходимо удалить все пробельные символы, что приводит выводу подобному ниже.
<div version="2.0">User{id=1, group=Group{name=asdsda}, name=adasd}<form method="POST" action="/user/list/form"><table><tr><td>ID</td><td><input type="text" name="id" /></td></tr><tr><td>NAME</td><td><input type="text" name="name" /></td></tr><tr><td>GROUP</td><td><input type="text" name="group.name" /></td></tr><tr><td /><td><input type="submit" /></td></tr></table></form></div>
MainController.java
@RequestMapping(value="user/list/form", method= RequestMethod.POST)
public String userListPost(@ModelAttribute("user") User u, BindingResult br, Model ui)
{
System.out.println(br.toString());
System.out.println(u.toString());
return "user/list";
}
list.jspx
<?xml version="1.0" encoding="UTF-8"?>
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
${user}
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
<div>
<form:errors path="*" cssClass="error" />
</div>
<table>
<tr>
<td>
<form:label path="id">
Идентификатор
</form:label>
</td>
<td>
<form:input path="id" />
</td>
</tr>
<tr>
<td colspan="2">
<form:errors path="id" cssClass="error" />
</td>
</tr>
<tr>
<td></td>
<td><form:input path="group.name" /></td>
</tr>
<tr>
<td></td>
<td><form:input path="name" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Send" />
</td>
</tr>
</table>
</form:form>
</div>
Рассмотрим тэги подробнее
-
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
- определяет форму на странице-
modelAttribute
- определяет ключ модели, под которым сохранён объект, управляемый формой -
action
- задаёт url (атрибутaction
тэга<form />
), на который будет передана форма -
method
- задаёт метод отправки формы
-
-
<form:label path="id">
- определяет подпись к элементу-
path
- задаёт свойство объекта из модели, к которому относится подпись
-
-
<form:input path="id" />
- задаёт элемент ввода -
<form:errors path="id" cssClass="alert alert-error" />
- задаёт вывод ошибок к свойству объекта-
cssClass
- задаёт стили применяемые к элементу с ошибками
-
-
<form:errors path="*" cssClass="error" />
- выводит все ошибки ко всем элементам формы
appServlet-servlet.xml
<mvc:resources mapping="/resources/**" location="/web-resources/" />
Создаём папку src/main/webapp/web-resources/.
Скачиваем Twitter Bootsrap http://twitter.github.com/bootstrap/getting-started.html#download-bootstrap
и распаковываем в /src/main/webapp/web-resources/bootstrap
list.jspx
<?xml version="1.0" encoding="UTF-8"?>
<html
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
>
<head>
<link rel="stylesheet" href="/resources/main.css" />
<link href="/resources/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
<script src="http://code.jquery.com/jquery-latest.js"><!-- --></script>
<script src="/resources/bootstrap/js/bootstrap.min.js"><!-- --></script>
</head>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
<body>
${user}
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
<form:errors path="*" cssClass="alert alert-error" />
<table>
<tr>
<td>
<form:label path="id">
Идентификатор
</form:label>
</td>
<td>
<form:input path="id" />
</td>
</tr>
<tr>
<td colspan="2">
<form:errors path="id" cssClass="alert alert-error" />
</td>
</tr>
<tr>
<td></td>
<td><form:input path="group.name" /></td>
</tr>
<tr>
<td></td>
<td><form:input path="name" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" class="btn btn-success" value="Send" />
</td>
</tr>
</table>
</form:form>
</body>
</html>
Ресурсы располагаются в файловой системе по адресу web-resources
и доступны по адресу /resources/
.
Важным момент является то, что JSP проводить оптимизацию тэгов и тэг в JSP вида <script src="..."></script>
будет преобразован к <script src="..." />
в html, что противоречит html в плане загрузки скриптов. По этой причине тэги для загрузки скриптов описаны как <script src="/resources/bootstrap/js/bootstrap.min.js"><!-- --></script>
, что на выходе даст: <script src="/resources/bootstrap/js/bootstrap.min.js"></script>
.
Важным аспектом разработки web-приложений является проверка достоверности и валидация данных. Spring framework имеет поддержку валидации JSR 303. Валидация по JSR 303 подразумевает аннотирование полей объекта с использованием аннотаций, которые определяют правила валидации.
Для начала необходимо добавить зависимость от поставщика валидации. В нашем случае этой библиотекой будет Hibernate validator.
pom.xml
<properties>
<hibernate.validator.version>4.3.0.Final</hibernate.validator.version>
</properties>
<!-- hibernate validation -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate.validator.version}</version>
</dependency>
Заменим <mvc:annotation-driven />
на создание валидатора и использование валидатора в MVC.
appServlet-servlet.xml
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" id="validator" />
<mvc:annotation-driven validator="validator" />
Добавим аннотации в User
User.java
package a1s.learn;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
public class User {
@Min(100)
@Max(200)
protected Long id;
protected Group group;
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", group=" + group + ", name=" + name + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Size(min=4,max=6)
@NotEmpty
protected String name;
}
Рассмотрим аннотации подробнее:
-
@NotEmpty
указывает, что поле обязательно для заполнения -
@Size(min=4,max=6)
указывает, что значение поля может быть от 4 до 6 символов -
@Min(100)
указывает, что значение поле должно быть больше или равно 100 -
@Max(200)
указывает, что значение поле должно быть меньше или равно 200
Необходимо добавить аннотацию @Valid
в MainController
, чтобы Spring MVC автоматически проверяла объекты.
MainController.java
@RequestMapping(value="user/list/form", method= RequestMethod.POST)
public String userListPost(@Valid @ModelAttribute("user") User u, BindingResult br, Model ui)
{
System.out.println(br.toString());
System.out.println(u.toString());
return "user/list";
}
Добавим в файл формы вывод ошибок.
<?xml version="1.0" encoding="UTF-8"?>
<html
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
>
<head>
<link rel="stylesheet" href="/resources/main.css" />
<link href="/resources/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
<script src="http://code.jquery.com/jquery-latest.js"><!-- --></script>
<script src="/resources/bootstrap/js/bootstrap.min.js"><!-- --></script>
</head>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
<body>
${user}
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
<form:errors path="*" cssClass="alert alert-error" />
<table>
<tr>
<td>
<form:label path="id">
Идентификатор
</form:label>
</td>
<td>
<form:input path="id" /><form:errors path="id" cssClass="alert alert-error" />
</td>
</tr>
<tr>
<td></td>
<td><form:input path="group.name" /><form:errors path="group.name" cssClass="alert alert-error" /></td>
</tr>
<tr>
<td></td>
<td><form:input path="name" /><form:errors path="name" cssClass="alert alert-error" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" class="btn btn-success" value="Send" />
</td>
</tr>
</table>
</form:form>
</body>
</html>
На рисунке показан снимок экрана с сообщениями об ошибках в случае неверного заполнения формы.
Кроме вывода по шаблону в web-приложениях может понадобиться группировка однотипных шаблонов (шаблон Composite). Для решения задач составления шаблонов в макет (layout) используется библиотека Apache tiles.
Apache tiles позволяет определить layout, в который будут вставлены jsp-страницы. Таким образом, достигается возможность группировки отдельных шаблонов в страницы.
Для начала необходимо добавить зависимость от библиотеки Apache tiles (tiles-core и tiles-jsp).
pom.xml
<properties>
<apache.tiles.version>2.2.2</apache.tiles.version>
</properties>
<!-- Apache tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>${apache.tiles.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>${apache.tiles.version}</version>
</dependency>
Для функционирования Apache tiles необходимо наличие логгера поддерживающего slf4j. В данном случае таким логгером будет logback.
Добавим зависимости от logback и удалим зависимость от JCL для spring-core.
Зависимости для SLF4J.
<properties>
<slf4j.version>1.7.2</slf4j.version>
</properties>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
Добавим logback в зависимости:
<properties>
<logback.version>1.0.7</logback.version>
</properties>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
Удалим зависимость от apache commons logging из spring-core.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
Для функционирования tiles необходим общий шаблон, который будет располагаться по адресу WEB-INF/layouts.xml
.
Создаём WEB-INF/layouts.xml
.
layouts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
<!-- Default Main Template -->
<definition name="default" template="/WEB-INF/layouts/default.jspx">
<put-attribute name="header" value="/WEB-INF/layouts/header.jspx" />
<put-attribute name="footer" value="/WEB-INF/layouts/footer.jspx" />
</definition>
</tiles-definitions>
В данном описании описан 1 макет по имени default
, файл макета расположен по адресу /WEB-INF/layouts/default.jspx
. В макете 3 атрибута (header
, footer
и body
). header
и footer
всегда постоянные, а вот body
будет заменяться.
default.jsp
<html
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
xmlns:tiles="http://tiles.apache.org/tags-tiles"
>
<head>
<link rel="stylesheet" href="/resources/main.css" />
<link href="/resources/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
<script src="http://code.jquery.com/jquery-latest.js"><!-- --></script>
<script src="/resources/bootstrap/js/bootstrap.min.js"><!-- --></script>
</head>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
<body>
<tiles:insertAttribute name="header" ignore="true" />
<tiles:insertAttribute name="body" ignore="true" />
<tiles:insertAttribute name="footer" ignore="true" />
</body>
</html>
Макет - обычная jsp-страница со вставками атрибутов tiles с использованием пространства имён tiles
.
-
<tiles:insertAttribute name="header" ignore="true" />
- приводит к вставке значения атрибутаheader
. -
ignore="true"
- означает, что в случае отсутствия атрибута исключение создаваться не будет.
header.jspx
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
xmlns:tiles="http://tiles.apache.org/tags-tiles"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
Header
</div>
footer.jspx
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
xmlns:tiles="http://tiles.apache.org/tags-tiles"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
Footer
</div>
Spring имеет поддержку Apache tiles версии 2. Для включения поддержки tiles необходимо настроить view resolver, для этого заменим определение view resolver'а в appServlet-servlet.xml
.
Необходимо заменить код
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jspx" />
</bean>
на
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView" />
</bean>
<bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" id="tilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts.xml</value>
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
-
org.springframework.web.servlet.view.UrlBasedViewResolver
- маршрутизирует обработку view по URL -
org.springframework.web.servlet.view.tiles2.TilesView
- класс поддержки apache tiles как view-класса для SpringMVC -
org.springframework.web.servlet.view.tiles2.TilesConfigurer
- настраивает собственно Apache tiles
Поддержка Apache tiles требует, чтобы все макеты были описаны в xml. Использование макетов по умолчанию не предусмотрено.
Описание
<list>
<value>/WEB-INF/layouts.xml</value>
<value>/WEB-INF/views/**/views.xml</value>
</list>
указывает, что основной файл описания макета расположен по адресу /WEB-INF/layouts.xml
, кроме того необходимо проверять описание tiles во всех вложенных директориях шаблонов /WEB-INF/views/**/views.xml
.
То есть:
- в контроллере возвращается не наименование JSP страницы, а наименование макета
- для каждого макета должно быть описана схема внедрения атрибутов
Реализуем файл макета для tiles, в котором описано как и в какой атрибут будет рендерится наша jsp-страница с формой.
Создадим файл /WEB-INF/views/user/views.xml
.
/WEB-INF/views/user/views.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
<tiles-definitions>
<!-- Default Main Template -->
<definition extends="default" name="user/list">
<put-attribute name="body" value="/WEB-INF/views/user/list.jspx" />
</definition>
</tiles-definitions>
Так как за страницу целиком теперь отвечает макет tiles, а не страница jsp, то из jsp-страницы можно удалить все указания загрузки ресурсов и т.д.
/WEB-INF/views/user/list.jspx
<?xml version="1.0" encoding="UTF-8"?>
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
${user}
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
<form:errors path="*" cssClass="alert alert-error" />
<table>
<tr>
<td>
<form:label path="id">
Идентификатор
</form:label>
</td>
<td>
<form:input path="id" /><form:errors path="id" cssClass="alert alert-error" />
</td>
</tr>
<tr>
<td></td>
<td><form:input path="group.name" /><form:errors path="group.name" cssClass="alert alert-error" /></td>
</tr>
<tr>
<td></td>
<td><form:input path="name" /><form:errors path="name" cssClass="alert alert-error" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" class="btn btn-success" value="Send" />
</td>
</tr>
</table>
</form:form>
</div>
По умолчанию logback выводит все сообщения в консоль, поэтому для исключения большого количества сообщений необходимо провести настройку логгера.
Создадим файл logback.xml
по адресу src/main/resources/logback.xml
.
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>true</resetJUL>
</contextListener>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{YYYY-MM-dd HH:mm:ss} %level %logger %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="info" />
<!--logger name="org.springframework.beans" level="debug" /-->
<logger name="a1s" level="debug" />
<root level="warn">
<appender-ref ref="console" />
</root>
</configuration>
Конфигурация настраивает logback на вывод сообщений в консоль. Кроме того, в консоль будут выводиться сообщения с уровнем не ниже info
для Spring framework и debug
для a1s
.
Соберём приложение и перейдём по адресу http://127.0.0.1:9090/.
На странице появились Header
и Footer
.
Создадим более сложную форму с различными элементами управления:
-
input
- для ввода строковых значений -
textarea
- для ввода большого объёма текста -
select
- для выбора из множества -
checkbox
- для булева выбора -
radiobutton
- для выбора элемента из списка
Для использования всех элементов управления изменим объект пользователя и группы.
User.java
package a1s.learn;
public class User {
protected Long id;
protected String name;
protected Group group;
protected boolean active = true;
protected String comment;
protected String securityLevel = "minimal";
@Override
public String toString() {
return "User{" + "id=" + id + ", name=" + name + ", group=" + group + ", active=" + active + ", comment=" + comment + ", securityLevel=" + securityLevel + '}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getSecurityLevel() {
return securityLevel;
}
public void setSecurityLevel(String securityLevel) {
this.securityLevel = securityLevel;
}
}
Group.java
package a1s.learn;
public class Group {
protected Long id;
protected String name;
public Group(Long id) {
this.id = id;
this.name= "Группа "+id;
}
public Group(Long id,String name) {
this.id = id;
this.name=name;
}
@Override
public String toString() {
return "Group{" + "id=" + id + ", name=" + name + '}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Будем реализовывать форму как представленная на рисунке ниже:
Для вывода элементов формы воспользуемся пространством имён form:
из spring-form.tld.
list.jspx
<?xml version="1.0" encoding="UTF-8"?>
<div
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:form="http://www.springframework.org/tags/form"
>
<jsp:directive.page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" />
<jsp:output omit-xml-declaration="yes" />
${user}
<form:form modelAttribute="user" action="/user/list/form" id="userForm" method="post">
<table>
<tr>
<td>
Наименование
</td>
<td>
<form:input path="name" />
</td>
</tr>
<tr>
<td colspan="2">
<ul>
<form:radiobuttons delimiter="" element="li" path="group" items="${groups}" itemValue="id" itemLabel="name" />
</ul>
</td>
</tr>
<tr>
<td>
Активность
</td>
<td>
<form:checkbox path="active" /><form:label path="active">Активен?</form:label>
</td>
</tr>
<tr>
<td colspan="2">
<form:textarea path="comment" rows="15" cols="60" />
</td>
</tr>
<tr>
<td colspan="2">
<form:radiobutton path="securityLevel" id="maxSecurityLevel" value="maximum" /><form:label path="securityLevel" for="maxSecurityLevel">Максимальный</form:label><br />
<form:radiobutton path="securityLevel" id="minSecurityLevel" value="minimal" /><form:label path="securityLevel" for="minSecurityLevel">Минимальный</form:label>
</td>
</tr>
<tr>
<td>
</td>
<td>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Send" />
</td>
</tr>
</table>
</form:form>
<form:errors path="*" />
</div>
Рассмотрим тэги более подробнее:
-
<form:input path="name" />
- поле ввода имени -
<ul><form:radiobuttons delimiter="" element="li" path="group" items="${groups}" itemValue="id" itemLabel="name" /></ul>
формирует список из элементов<li />
(element="li"
) для свойстваgroup
(path="group"
). В качестве элемента управления будут использоваться радиокнопки. Элементами списка будет список объектов, который сохранён в модели по ключуgroups
(items="${groups}"
). Значением для радиокнопки будет выбрано свойствоid
(itemValue="id"
) объекта из списка, а подписью - свойствоname
(itemLabel="name"
). Кроме того, радиокнопки можно разделять между собой с помощью разделителя (delimiter=""
) в нашем случае такого разделения нет. -
<form:textarea path="comment" rows="15" cols="60" />
- поля для ввода текст (textarea
) размерами 15 строк и 60 столбцов -
<form:checkbox path="active" /><form:label path="active">Активен?</form:label>
выводит флажок для отображения активности пользователя -
<form:radiobutton path="securityLevel" id="maxSecurityLevel" value="maximum" /><form:label path="securityLevel" for="maxSecurityLevel">Максимальный</form:label><br /><form:radiobutton path="securityLevel" id="minSecurityLevel" value="minimal" /><form:label path="securityLevel" for="minSecurityLevel">Минимальный</form:label>
- формирует список из радиокнопок для строковых значений (minimal
иmaximum
).
Элементы управление заполняются автоматически.
В большинстве мы использовали простые типы, кроме элемента с группами, и Spring проведёт преобразование типов автоматически, так как в состав spring уже входят преобразователи для базовых типов.
Таким образом, необходимо реализовать преобразователь из значения типа String
в значение типа Group
, чтобы заполнение формы было корректным.
Можно воспользоваться реализацией глобального преобразователя и зарегистрировать его в conversion service, но для специфических случаев может понадобиться реализация преобразователя по месту.
Реализуем метод, который будет указывать как проводить преобразование для поля group
.
MainController.java
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Group.class, "group", new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
Group g = new Group(Long.valueOf(text));
setValue(g);
}
});
}
Аннотация @InitBinder
указывает, что данный метод проводить настройку биндинга и преобразования.
WebDataBinder binder
- собственно объект биндера.
Мы регистрируем специфичный преобразователь в объект класса Group
для поля group
с использованием расширения класса PropertyEditorSupport
(фактически создаём собственный propertyeditor). Наш property editor будет создавать объект класса Group
из переданного строкового значения и устанавливать объект как значение необходимо объекта.
Теперь необходимо реализовать заполнение groups
в модели. Для этого реализуем соответствующий метод и добавим заполнение модели в необходимые методы.
@RequestMapping(value="user/list", method= RequestMethod.GET)
public String userListGet(Model ui)
{
ui.addAttribute("user", new User());
ui.addAttribute("groups", this.getGroups());
return "user/list";
}
@RequestMapping(value="user/list/form", method= RequestMethod.POST)
public String userListPost(@Valid @ModelAttribute("user") User u, BindingResult br, Model ui)
{
System.out.println(br.toString());
System.out.println(u.toString());
ui.addAttribute("groups", this.getGroups());
return "user/list";
}
protected List<Group> getGroups()
{
List<Group> groups = new ArrayList<Group>();
groups.add(new Group(Long.valueOf(1),"Группа 1"));
groups.add(new Group(Long.valueOf(2),"Группа 2"));
groups.add(new Group(Long.valueOf(3),"Группа 3"));
return groups;
}
После этого можно собрать приложение и перейти по адресу http://127.0.0.1:9090/user/list.
TODO! form:select, form:checkboxes
TODO!
TODO!
TODO!
TODO!
TODO!
В ряде случае необходимо реализовать общую обработку на стороне JSP. Например, формирование таблиц из коллекций или форматирование даты.
Можно реализовать класс helper'а и вызывать его с использованием средств JSP, а можно определить собственный тэг, который будет производить обработку.
В JSP существует механизм расширения с использованием tld(tag library definition). Фактически реализуются классы, которые расширяют поведение JSP-парсера.
Для создания собственной библиотеки тэгов необходимо реализовать:
- класс, который реализует поведение и расширяет объект
javax.servlet.TagSupport
- описание библиотеки тэгов (xml-файл описания .tld)
Реализуем тэг для вывода коллекций в таблицу с раскраской чётных и нечётных строк.
Определим описание tld-библиотеки в файле /WEB-INF/a1stable.tld
a1stable.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib 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-jsptaglibrary_2_0.xsd"
version="2.0">
<description>a1s Tag Library</description>
<tlib-version>3.0</tlib-version>
<short-name>a1s</short-name>
<uri>http://www.a1-systems.com/tags</uri>
<tag>
<description>
Render table
</description>
<name>table</name>
<tag-class>a1s.learn.Table</tag-class>
<attribute>
<description>Items to render</description>
<name>str</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
-
taglib
description
uri
tlib-version
short-name
-
tag
description
-
name
- наименование тэга, которое будет использоваться после имени пространства имён (в нашем случае<a1s:*table* />
) -
tag-class
- наименование класса, который реализует логику работы тэга -
attribute
- атрибут тэга и класса, который будет распознан и засечен в объект тэга-
description
- описание параметра -
name
- название параметра -
required
- обязательный или нет параметр? -
rtexprvalue
- необходимо ли проводить подстановку значений?
-
Далее в шаблоне jsp необходимо подключить библиотеку типов
list.jspx
<div
...
xmlns:a1s="http://www.a1-systems.com/tags"
>
Реализуем класса тэга.
Table.java
package a1s.learn;
import java.io.IOException;
import java.util.List;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Table extends TagSupport {
protected static final Logger log = LoggerFactory.getLogger(Table.class);
private List<Object> str;
public List<Object> getStr() {
return str;
}
public void setStr(List<Object> str) {
this.str = str;
}
@Override
public int doStartTag() throws JspException {
log.debug("Start tag");
try {
//Get the writer object for output.
JspWriter out = pageContext.getOut();
int i=1;
out.print("<table border=\"1\">");
for (Object item:str) {
out.print("<tr style=\"background:#"+(i % 2 == 0 ? "ссс" : "fff")+"\">");
out.print("<td>"+i+"</td>");
out.print("<td>"+item.toString()+"</td>");
out.print("</tr>");
i++;
}
out.print("</table>");
} catch (IOException e) {
log.error(e.getStackTrace().toString());
}
return SKIP_BODY;
}
}
TODO!
Добавим на страницу вывод таблицы с коллекцией themes
.
list.jspx
<a1s:table str="${themes}" />
Скомпилируем и соберём проект. Результирующий вид страницы с внедрённым тэгом: