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

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:

Код   
<properties>
    <failOnMissingWebXml>false</failOnMissingWebXml>
</properties>

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

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

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

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

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

Код   
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.javamaster</groupId>
    <artifactId>simplemvcapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</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:

Код   
package com.javamaster.domain;

public class Order {

    private Integer id;
    private String title;
    private Double price;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public Double getPrice() {
        return price;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((price == null) ? 0 : price.hashCode());
        result = prime * result + ((title == null) ? 0 : title.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Order other = (Order) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (price == null) {
            if (other.price != null)
                return false;
        } else if (!price.equals(other.price))
            return false;
        if (title == null) {
            if (other.title != null)
                return false;
        } else if (!title.equals(other.title))
            return false;
        return true;
    }
    @Override
    public String toString() {
        return "Order [id=" + id + ", title=" + title + ", price=" + price + "]";
    }
}

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

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

Код   
package com.javamaster.repository;

import java.util.List;

import com.javamaster.domain.Order;

public interface OrderRepository {

    void save(Order order);
   
    void delete(Order order);
   
    List<Order> getAll();
   
    Order getById(Integer id);
}

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

Код   
package com.javamaster.repository;

import java.util.ArrayList;
import java.util.List;

import com.javamaster.domain.Order;

public class OrderRepositoryImpl implements OrderRepository {
   
    private List<Order> orders = new ArrayList<Order>();
   
    public OrderRepositoryImpl() {
        Order order = new Order();
        order.setId(1);
        order.setPrice(234d);
        order.setTitle("Pizza peperoni");
        Order order2 = new Order();
        order2.setId(2);
        order2.setPrice(123d);
        order2.setTitle("Sushi Philadelfia");
        orders.add(order);
        orders.add(order2);
    }

    public void save(Order order) {
        orders.add(order);
    }

    public void delete(Order order) {
        orders.remove(order);
    }

    public List<Order> getAll() {
        return orders;
    }

    public Order getById(Integer id) {
        return orders.get(id);
    }
}

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

Код   
package com.javamaster.service;

import java.util.List;

import com.javamaster.domain.Order;

public interface OrderService {
   
    void save(Order order);

    void delete(Order order);

    List<Order> getAll();

    Order getById(Integer id);
}

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

Код   
package com.javamaster.service;

import java.util.List;

import com.javamaster.domain.Order;
import com.javamaster.repository.OrderRepository;
import com.javamaster.repository.OrderRepositoryImpl;

public class OrderServiceImpl implements OrderService {
   
    private OrderRepository orderRepository = new OrderRepositoryImpl();

    public void save(Order order) {
        if(order!=null) {
            List<Order> orders = orderRepository.getAll();
            if(!orders.isEmpty()) {
                Order lastOrder = orders.get(orders.size() - 1);
                order.setId(lastOrder.getId()+1);
                orderRepository.save(order);
            }
        }
    }

    public void delete(Order order) {
        if(order!=null) {
            orderRepository.delete(order);
        }  
    }

    public List<Order> getAll() {
        return orderRepository.getAll();
    }

    public Order getById(Integer id) {
        if(id!=null) {
            return orderRepository.getById(id);
        }
        return null;
    }

}

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

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

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

Код   
package com.javamaster.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
public class OrderController {

    @RequestMapping(value="/", method=RequestMethod.GET)
    public String getOrderPage(Model model) {
        return "order";
    }
}

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

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

Код   
@Bean
    ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

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

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

Код   
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
   pageEncoding="ISO-8859-1"%>
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
<style>
table {
    font-family: arial, sans-serif;
    border-collapse: collapse;
    width: 100%;
}

td, th {
    border: 1px solid #dddddd;
    text-align: left;
    padding: 8px;
}

tr:nth-child(even) {
    background-color: #dddddd;
}
</style>
</head>
<body>
<table>
  <tr>
    <th>Title</th>
    <th>Price</th>
    <th>Action</th>
  </tr>
</table>
</body>
</html>

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

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

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

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

Код   
package com.javamaster.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.javamaster.domain.Order;
import com.javamaster.service.OrderService;
import com.javamaster.service.OrderServiceImpl;

@Controller
public class OrderController {
   
    private OrderService orderService = new OrderServiceImpl();

    @RequestMapping(value="/", method=RequestMethod.GET)
    public String getOrderPage(Model model) {
        List<Order> orders = orderService.getAll();
        model.addAttribute("orderList", orders);
        return "order";
    }
}

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

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

Код   
<table>
 <tr>
 <th>Title</th>
 <th>Price</th>
 <th>Action</th>
 </tr>
 <c:forEach var="order" items="${orderList}">
 <tr>
 <td>${order.title}</td>
 <td>${order.price}</td>
 <td></td>
 </tr>
 </c:forEach>
</table>

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

run on server

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

start result spring mvc program

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

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

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

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

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

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

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

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

add new record

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

Код   
<table>
  <tr>
    <th>Title</th>
    <th>Price</th>
    <th>Action</th>
  </tr>
  <c:forEach var="order" items="${orderList}">
   <tr>
  <td>${order.title}</td>
    <td>${order.price}</td>
    <td><a href="/delete/${order.id}">Delete this item</a></td>
     </tr>
  </c:forEach>
</table>

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

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

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

delete item

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

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

Также, не забудьте посмотреть java mentor если у Вас будут трудности с проектами.

Понравилась статья? Поделиться с друзьями:
Комментарии: 5
  1. Alex

    Спасибо, работает на localhost!
    Но, при деплое WAR-файла в Tomcat на VDS сервере, надо указывать контекст приложения типа «/app», потому что на «/» сидит Tomcat manager.
    Вопрос: какой путь прослушивания забить в RequestMapping вместо «/add-new-order»?
    Это не работает : «/app/add-new-order»

    1. Denys (автор)

      Контекст приложения не влияет на пути @Controller.
      Используйте /add-new-order
      Spring должен автоматически упускать контекст. Для него это как часть доменного пути.

  2. timurgo

    Спасибо большое за статью!
    Очень доступно и информативно)

  3. nix

    @Bean
    ViewResolver viewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(«/WEB-INF/»);
    resolver.setSuffix(«.jsp»);
    return resolver;
    }
    Как .jsp заменить на .html??
    ..выдает ошибку, что file.html не найден

    1. Denys (автор)

      Попробуйте @Bean
      ViewResolver viewResolver(){
      InternalResourceViewResolver resolver = new InternalResourceViewResolver();
      resolver.setPrefix(«/WEB-INF/»);
      resolver.setSuffix(«.html»);
      return resolver;
      }

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

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: