Spring MVC первое веб приложение

Сегодня я хочу написать о том, как начать работать с мега популярным фреймоворком Spring MVC. Если верить статистике нагугленых сайтов, то это первый по популярности Java фреймворк.

В прошлой статье по Spring я рассказывал, как настроить приложение без xml конфигураций и web.xml. Если Вы только начинаете изучать веб программирование на джава, то наверное еще не слышали таких терминов. НО, уверяю Вас, ничего сложного в этой статье не будет даже если Вы не видели, как разрабатывать веб приложения.

Давайте напишем веб приложение, которое имеет несколько страничек и в зависимости от нажатой кнопки выдает нам их. Типичный веб сайт только без базы данных. Такого простого пример будет достаточно чтобы познакомить новичков с UrlMapping и Controller.

Для начала нужно понять, что такое MVC.

MVC расшифровывается как Model View Controller. Это тип архитектуры приложения в которой логические части приложения условно разбиваются на три блока:

  1. Модель;
  2. Вид;
  3. Контроллер.

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

Вид — это внешняя составляющая приложения. Это может быть HTML страница, диаграмма или UI.

Контроллер — отвечает за получение входных и поток исходных данных. В его функции входит отслеживание действий пользователя.

модель-вид-контроллер

С архитектурой разобрались. Теперь пришла очередь написать приложение на java. В своей IDEE создайте простой мавен проект. В этой статье, мы будем использовать Eclipse в качестве IDEE.

Для того, чтобы создать новый Maven проект в эклипс нужно выполнить следующие действия: File -> New -> Maven project -> Create a simple project (skip archetype selection)

create new maven project

 

Заполните group id, artifact id и выберите war в качестве упаковки приложения.

создать простое мавен приложение

Нажимаем Finish и проект создан.

Если Вы все сделали согласно выше написанным инструкциям, то должны увидеть вот такую структуру:

структура приложения

Теперь нужно добавить директорию WEB-INF в папку webapp. Не обращайте внимания на ошибку файла pom.xml. Он выдает ошибку поскольку мы «пакуем» наше приложение в war архив. Это формат веб приложений. Раньше веб приложение на Java не возможно было написать без web.xml. Сейчас — это возможно, но по умолчанию pom.xml все равно ругается на его отсутствие. Для того, чтобы это предотвратить нужно добавить данную строку в pom.xml:

Код    
  1. <properties>
  2.     <failOnMissingWebXml>false</failOnMissingWebXml>
  3. </properties>

 

Теперь ошибка должна исчезнуть. Если она не исчезла не нужно беспокоиться. Это не повлияет на работу приложения.

Теперь добавим зависимости, которые нам будут необходимы для полноценного построения Spring MVC приложения:

  • spring-webmvc;
  • jstl — библиотека тегов для jsp страниц. О ней была отдельная статья: JSTL теги;
  • javaee-api (со скопом provided).

Пока все. Если нам что-то понадобиться в процессе работы, добавим походу программирования.

Файл pom.xml на текущий момент:

Код    
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3.     <modelVersion>4.0.0</modelVersion>
  4.     <groupId>com.javamaster</groupId>
  5.     <artifactId>simplemvcapp</artifactId>
  6.     <version>0.0.1-SNAPSHOT</version>
  7.     <packaging>war</packaging>
  8.     <properties>
  9.         <failOnMissingWebXml>false</failOnMissingWebXml>
  10.     </properties>
  11.     <dependencies>
  12.         <dependency>
  13.             <groupId>org.springframework</groupId>
  14.             <artifactId>spring-webmvc</artifactId>
  15.             <version>4.2.6.RELEASE</version>
  16.         </dependency>
  17.         <dependency>
  18.             <groupId>jstl</groupId>
  19.             <artifactId>jstl</artifactId>
  20.             <version>1.2</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>javax</groupId>
  24.             <artifactId>javaee-api</artifactId>
  25.             <version>7.0</version>
  26.             <scope>provided</scope>
  27.         </dependency>
  28.     </dependencies>
  29. </project>

Теперь добавим несколько пакетов для разделения логики проекта:

  • com.javamaster.config;
  • com.javamaster.controller;
  • com.javamaster.domain;
  • com.javamaster.service;
  • com.javamaster.repository

После всех манипуляций получилась следующая структура проекта:

new prj structure

Для того, чтобы начать писать классы и методы обработки работы веб приложения сначала нужно написать конфигурацию для Spring MVC приложения. Первичная конфигурация была описана в статье Spring MVC настройка без xml и web.xml. Поэтому мы просто скопируем файлы настроек с выше упомянутой статьи.

Когда настройки готовы можно приступать к написании логики работы приложения. Пусть наше приложение ведет учет некой системы заказов в ресторане: при поступлении нового заказа официант нажимает кнопку и система готова принимать новый заказ. По окончании работы можно посмотреть статистику по заказам.

Для этого напишем простую модель которая будет состоять только с одной сущности: Заказ.

В пакет com.javamaster.domain добавим новый класс Order с полями id, title, price:

Код    
  1. package com.javamaster.domain;
  2.  
  3. public class Order {
  4.  
  5.     private Integer id;
  6.     private String title;
  7.     private Double price;
  8.     public Integer getId() {
  9.         return id;
  10.     }
  11.     public void setId(Integer id) {
  12.         this.id = id;
  13.     }
  14.     public String getTitle() {
  15.         return title;
  16.     }
  17.     public void setTitle(String title) {
  18.         this.title = title;
  19.     }
  20.     public Double getPrice() {
  21.         return price;
  22.     }
  23.     public void setPrice(Double price) {
  24.         this.price = price;
  25.     }
  26.     @Override
  27.     public int hashCode() {
  28.         final int prime = 31;
  29.         int result = 1;
  30.         result = prime * result + ((id == null) ? 0 : id.hashCode());
  31.         result = prime * result + ((price == null) ? 0 : price.hashCode());
  32.         result = prime * result + ((title == null) ? 0 : title.hashCode());
  33.         return result;
  34.     }
  35.     @Override
  36.     public boolean equals(Object obj) {
  37.         if (this == obj)
  38.             return true;
  39.         if (obj == null)
  40.             return false;
  41.         if (getClass() != obj.getClass())
  42.             return false;
  43.         Order other = (Order) obj;
  44.         if (id == null) {
  45.             if (other.id != null)
  46.                 return false;
  47.         } else if (!id.equals(other.id))
  48.             return false;
  49.         if (price == null) {
  50.             if (other.price != null)
  51.                 return false;
  52.         } else if (!price.equals(other.price))
  53.             return false;
  54.         if (title == null) {
  55.             if (other.title != null)
  56.                 return false;
  57.         } else if (!title.equals(other.title))
  58.             return false;
  59.         return true;
  60.     }
  61.     @Override
  62.     public String toString() {
  63.         return "Order [id=" + id + ", title=" + title + ", price=" + price + "]";
  64.     }
  65. }

Это обычный POJO класс: Plain Old Java Object. У него есть поля, конструктор по умолчанию, геттеры-сеттеры, переопределены методы equals, hashCode. Для удобства мы переопределили его toString() метод.

Так как в этой статье у нас нет подключения к базе данных, мы будем хранить данные в списках (List). Для этого в пакете com.javamaster.repository добавим интерфейс OrderRepository, который будет содержать методы по действиях над нашей моделью:

Код    
  1. package com.javamaster.repository;
  2.  
  3. import java.util.List;
  4.  
  5. import com.javamaster.domain.Order;
  6.  
  7. public interface OrderRepository {
  8.  
  9.     void save(Order order);
  10.    
  11.     void delete(Order order);
  12.    
  13.     List<Order> getAll();
  14.    
  15.     Order getById(Integer id);
  16. }

 

Теперь напишем имплементацию данного интерфейса:

Код    
  1. package com.javamaster.repository;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. import com.javamaster.domain.Order;
  7.  
  8. public class OrderRepositoryImpl implements OrderRepository {
  9.    
  10.     private List<Order> orders = new ArrayList<Order>();
  11.    
  12.     public OrderRepositoryImpl() {
  13.         Order order = new Order();
  14.         order.setId(1);
  15.         order.setPrice(234d);
  16.         order.setTitle("Pizza peperoni");
  17.         Order order2 = new Order();
  18.         order2.setId(2);
  19.         order2.setPrice(123d);
  20.         order2.setTitle("Sushi Philadelfia");
  21.         orders.add(order);
  22.         orders.add(order2);
  23.     }
  24.  
  25.     public void save(Order order) {
  26.         orders.add(order);
  27.     }
  28.  
  29.     public void delete(Order order) {
  30.         orders.remove(order);
  31.     }
  32.  
  33.     public List<Order> getAll() {
  34.         return orders;
  35.     }
  36.  
  37.     public Order getById(Integer id) {
  38.         return orders.get(id);
  39.     }
  40. }

 

Теперь можно написать наш сервис. Сейчас, пока структура и функциональность проекта не очень велика сервис будет повторять структуру репозитория:

Код    
  1. package com.javamaster.service;
  2.  
  3. import java.util.List;
  4.  
  5. import com.javamaster.domain.Order;
  6.  
  7. public interface OrderService {
  8.    
  9.     void save(Order order);
  10.  
  11.     void delete(Order order);
  12.  
  13.     List<Order> getAll();
  14.  
  15.     Order getById(Integer id);
  16. }

 

И реализация сервиса:

Код    
  1. package com.javamaster.service;
  2.  
  3. import java.util.List;
  4.  
  5. import com.javamaster.domain.Order;
  6. import com.javamaster.repository.OrderRepository;
  7. import com.javamaster.repository.OrderRepositoryImpl;
  8.  
  9. public class OrderServiceImpl implements OrderService {
  10.    
  11.     private OrderRepository orderRepository = new OrderRepositoryImpl();
  12.  
  13.     public void save(Order order) {
  14.         if(order!=null) {
  15.             List<Order> orders = orderRepository.getAll();
  16.             if(!orders.isEmpty()) {
  17.                 Order lastOrder = orders.get(orders.size() - 1);
  18.                 order.setId(lastOrder.getId()+1);
  19.                 orderRepository.save(order);
  20.             }
  21.         }
  22.     }
  23.  
  24.     public void delete(Order order) {
  25.         if(order!=null) {
  26.             orderRepository.delete(order);
  27.         }  
  28.     }
  29.  
  30.     public List<Order> getAll() {
  31.         return orderRepository.getAll();
  32.     }
  33.  
  34.     public Order getById(Integer id) {
  35.         if(id!=null) {
  36.             return orderRepository.getById(id);
  37.         }
  38.         return null;
  39.     }
  40.  
  41. }

 

В сервисе Вы не увидите ничего нового. Здесь простая проверка на null и слежение за индексом при добавлении записи.

Теперь можно переходить до написания внешнего вида и контроллера.

Для этого создадим в пакете com.javamaster.controller класс OrderController, который мы добавим аннотацию @Controller. Данная аннотация будет говорить spring о том, что это контроллер и обрабатывать его нужно соответственно:

Код    
  1. package com.javamaster.controller;
  2.  
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.ui.Model;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestMethod;
  7.  
  8. @Controller
  9. public class OrderController {
  10.  
  11.     @RequestMapping(value="/", method=RequestMethod.GET)
  12.     public String getOrderPage(Model model) {
  13.         return "order";
  14.     }
  15. }

 

В данном классе @RequestMapping отвечает за маппинг страниц. Данная аннотация принимает параметры value, method. Value — это адрес в браузере; Method — метод запроса. Подробнее о методах можно почитать в статье Как работает Servlet в Java. Контроллер в Spring как раз и есть аналогом Servlet.

Когда в браузере будет набрана комбинация http://host:port/applicationName/ сработает метод getOrderPage. Именно его маппинг отвечает за «/». Во время выполнения этого метода будет возвращена строка «order». Так как это не простой класс, а контроллер (читай сервлет), то строка будет отвечать за отображение страницы которая находиться в папке WEB-INF и которая имеет расширение jsp. Именно так мы настроили наше приложение в классах конфигураций:

Код    
  1. @Bean
  2.     ViewResolver viewResolver(){
  3.         InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  4.         resolver.setPrefix("/WEB-INF/");
  5.         resolver.setSuffix(".jsp");
  6.         return resolver;
  7.     }

Теперь осталось разобрать входной параметр метода: Model model. Model нужна для того, чтобы передать на фронтенд атрибуты и другие параметры. Ее использование посмотрим ниже.

Сейчас нужно написать jsp-страницу order.jsp:

Код    
  1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  2.    pageEncoding="ISO-8859-1"%>
  3.  <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  5. <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  6. <title>Insert title here</title>
  7. table {
  8.     font-family: arial, sans-serif;
  9.     border-collapse: collapse;
  10.     width: 100%;
  11. }
  12.  
  13. td, th {
  14.     border: 1px solid #dddddd;
  15.     text-align: left;
  16.     padding: 8px;
  17. }
  18.  
  19. tr:nth-child(even) {
  20.     background-color: #dddddd;
  21. }
  22. </head>
  23.   <tr>
  24.     <th>Title</th>
  25.     <th>Price</th>
  26.     <th>Action</th>
  27.   </tr>
  28. </body>
  29. </html>

 

Это обычная страница с простой таблицей. Стоит обратить внимание на строку <%@ taglib prefix=»c» uri=»http://java.sun.com/jsp/jstl/core» %> — это подключение библиотеки тегов jstl.

Теперь, когда структура проекта готова можно писать логику работы веб приложения:

окончательная структура проекта

Для начала отобразим все заказы в таблице при первом обращении к приложению. Для этого немного изменим метод getOrderPage и добавим в него немного функционала:

Код    
  1. package com.javamaster.controller;
  2.  
  3. import java.util.List;
  4.  
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.ui.Model;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9.  
  10. import com.javamaster.domain.Order;
  11. import com.javamaster.service.OrderService;
  12. import com.javamaster.service.OrderServiceImpl;
  13.  
  14. @Controller
  15. public class OrderController {
  16.    
  17.     private OrderService orderService = new OrderServiceImpl();
  18.  
  19.     @RequestMapping(value="/", method=RequestMethod.GET)
  20.     public String getOrderPage(Model model) {
  21.         List<Order> orders = orderService.getAll();
  22.         model.addAttribute("orderList", orders);
  23.         return "order";
  24.     }
  25. }

 

В классе выше мы создали переменную типа OrderService. Далее в методе getOrderPage мы получили список всех заказов и передали их на jsp страницу в виде атрибута orderList.

Теперь, чтобы увидеть orderList на странице воспользуемся функционалом библиотеки jstl:

Код    
  1.  <tr>
  2.  <th>Title</th>
  3.  <th>Price</th>
  4.  <th>Action</th>
  5.  </tr>
  6.  <c:forEach var="order" items="${orderList}">
  7.  <tr>
  8.  <td>${order.title}</td>
  9.  <td>${order.price}</td>
  10.  <td></td>
  11.  </tr>
  12.  </c:forEach>

 

Настало время запустить приложение, чтобы посмотреть: действительно ли все работает. Для этого нажмем правой кнопкой мыши на проекте Run as -> Run on server

run on server

Установка сервера и запуск веб приложения на сервере описано в статье Простой сайт на Java.

start result spring mvc program

Теперь можно добавить кнопку нового заказа. Под таблицей добавим гиперссылку, которая будет вести на страницу создания заказа: <a href=»/add-new-order»>Add new order</a>

Теперь напишем метод в контроллере, который покажет нам эту страницу:

Код    
  1. @RequestMapping(value = "/add-new-order", method=RequestMethod.GET)
  2.     public String addNewOrderPage() {
  3.         return "addNewOrder";
  4.     }

Далее создадим jsp страницу addNewOrder в которую добавим форму добавления заказа:

Код    
  1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  2.    pageEncoding="ISO-8859-1"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  5. <title>Insert title here</title>
  6. </head>
  7. <form action="/add-new-order" method="POST">
  8. <label>Title</label>
  9. <input type="text" name="title">
  10. <label>Price</label>
  11. <input type="text" name="price">
  12. <input type="submit" value="Add new order">
  13. </form>
  14. </body>
  15. </html>

Здесь ничего нового пока не открыли. Теперь напишем метод обработки данной формы:

Код    
  1. @RequestMapping(value="/add-new-order", method=RequestMethod.POST)
  2.     public String addNewOrder(@RequestParam(value="title") String title, @RequestParam(value="price") Double price) {
  3.         Order order = new Order();
  4.         order.setTitle(title);
  5.         order.setPrice(price);
  6.         orderService.save(order);
  7.         return "redirect:/";
  8.     }

Здесь в отличии от других методов используется метод POST. Входные параметры считываются аннотацией @RequestParam. Она принимает value значение, которого должно совпадать с именем поля на форме. Вместо возврата страницы мы используем команду redirect:, которая сделает перенаправление на другой урл. Небольшая анимация работоспособности приложения:

add new record

Осталось добавить кнопку удаления записи. Для этого в свободную ячейку таблицы нужно добавить гиперссылку с указанием на удаление и id записи, которую хотим удалить:

Код    
  1.   <tr>
  2.     <th>Title</th>
  3.     <th>Price</th>
  4.     <th>Action</th>
  5.   </tr>
  6.   <c:forEach var="order" items="${orderList}">
  7.    <tr>
  8.   <td>${order.title}</td>
  9.     <td>${order.price}</td>
  10.     <td><a href="/delete/${order.id}">Delete this item</a></td>
  11.      </tr>
  12.   </c:forEach>

Далее нужно написать метод обработки он не будет сильно отличаться от предыдущих:

Код    
  1. @RequestMapping(value="delete/{id}", method=RequestMethod.GET)
  2.     public String deleteItem(@PathVariable Integer id) {
  3.         Order order = orderService.getById(id);
  4.         orderService.delete(order);
  5.         return "redirect:/";
  6.     }

Результат работы веб приложения:

delete item

На этом пока все. Естественно, можно дальше усложнять приложение: подключить базу данных, написать стили для страниц, добавить больше функционала. Главное в этой статье — познакомиться со структурой MVC приложения и попробовать написать первое полноценное Spring MVC приложение.

Весь код приложения можно найти по ссылке.

 

Добавить комментарий