arrow-left

All pages
gitbookPowered by GitBook
1 of 1

Loading...

5. Работа со Spring MVC, часть 2

hashtag
1. Валидация данных формы

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

index.html
<form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post" class="needs-validation">
    <div class="row">
        <div class="col-md-12">
            <h4 class="mb-3">Поля для заполнения</h4>
            <div class="row">
                <div class="col-md-6 mb-3">
                    <label for="lastName">Фамилия</label>
                    <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                </div>

Изначально, форма не имеет средств валидации, то есть мы не можем отследить корректность заполнения формы.

Spring предоставляет несколько инструментов для реализации валидации формы, воспользуемся библиотекой Bean Validation API

circle-info

Информацию по поводу использования библиотеки можно найти (см. раздел 8 мануала)

Добавим библиотеку в список зависимостей в файле pom.xml

Будем использовать механизм встроенных ограничений. Алгоритм использования встроенных ограничений следующий - с помощью аннотаций необходимо указать над полем класса-сущности требуемые параметры валидации и другие параметры. В нашем случае, сущностью выступает класс Student. Добавим необходимые ограничения для полей сущности.

Как мы видим, все достаточно просто и наглядно.

Далее, нам необходимо модифицировать контроллеры и реализовать следующий функционал:

  • указать, что объект типа Student должен пройти валидацию;

  • получить результаты валидации объекта;

  • если объект не прошел валидацию - не добавлять объект в хранилище, выдать сообщение об ошибке в консоль.

Нам необходимо модифицировать метод контроллера, который обрабатывает данные формы. Указываем аннотацию @Valid, которая говорит о том, что полученный объект необходимо подвергнуть валидации. Далее указываем аргумент типа BindingResult, который хранит информацию о результате валидации. С помощью метода hasErrors() получаем результат валидации объекта.

При попытке отправить пустую форму, получаем сообщение в консоли

Последний шаг - необходимо предоставить пользователю информацию о том, что то или иное поле формы не прошло валидацию.

Самый простой способ проинформировать пользователь - показать сообщение об ошибке около поля, которое не прошло валидацию. Чтобы реализовать данный функционал, перейдем в шаблон index.html.

Рассмотрим поле "Фамилия". Сообщение об ошибке мы разместим снизу поля. Добавим соответствующий элемент <small> в HTML-макет.

Используем тег th:if. Если выражение внутри тега равно true, то элемент <small> будет показан на экране, если false - будет скрыт.

Выражение ${fields.hasErrors('lastName)} означает, есть ли ошибки валидации для поля lastName? Если ошибки есть - поле будет показано. Текст ошибки выводим с помощью атрибута th:errors.

Добавляем элементы для вывода ошибок для остальных полей формы. Проверяем результат

Ниже представлен листинг классов и файлов

hashtag
2. Загрузка файлов на сервер

<div class="col-md-6 mb-3">
<label for="firstName">Имя</label>
<input th:field="*{firstName}" type="text" class="form-control" id="firstName">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email">Электронная почта</label>
<input th:field="*{email}" type="text" class="form-control" id="email">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="age">Возраст</label>
<input th:field="*{age}" type="number" class="form-control" id="age">
</div>
</div>
<hr class="mb-4">
<button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
</div>
</div>
</form>
здесьarrow-up-right
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>

    ...

    <dependencies>
    
    ...

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
    </dependencies>

...

</project>
Student.java
public class Student {

    // Имя должно быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "First name should be from 2 to 50 characters")
    private String firstName;
    
    // Фамилия должна быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "Last name should be from 2 to 50 characters")
    private String lastName;
    
    // Возраст должен быть целым числом от 13 до 65
    @Range(min = 13, max = 65, message = "Student age should be from 13 to 65 years")
    private int age;
    
    // Для валидации электронной почты используем регулярное выражение
    @Pattern(regexp = "^[\\\\w!#$%&’*+/=?`{|}~^-]+(?:\\\\.[\\\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\\\.)+[a-zA-Z]{2,6}$",
            message = "Invalid email format")
    private String email;
    
    ...
}
    @GetMapping("/")
    public String addStudent(Model model) {
        model.addAttribute("student", new Student());
        return "index";
    }

    @PostMapping("/")
    public String processAddStudentForm(@Valid Student student, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            System.out.println("Validation has been failed!");
            return "index";
        }

        System.out.println(student);

        list.add(student);
        return "redirect:/";
    }
index.html
<div class="col-md-6 mb-3">
    <label for="lastName">Фамилия</label>
    <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
    <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
</div>
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <title>Добавление студента</title>
</head>
<body class="bg-light">
<div class="container">
    <div class="py-5 text-center">
        <h2>Добавление студента</h2>
        <p class="lead">Заполните поля и нажмите кнопку 'Добавить студента'</p>
    </div>

    <form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post">
        <div class="row">
            <div class="col-md-12">
                <h4 class="mb-3">Поля для заполнения</h4>
                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="lastName">Фамилия</label>
                        <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                        <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
                    </div>
                    <div class="col-md-6 mb-3">
                        <label for="firstName">Имя</label>
                        <input th:field="*{firstName}" type="text" class="form-control" id="firstName">
                        <small class="text-danger" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="email">Электронная почта</label>
                        <input th:field="*{email}" type="text" class="form-control" id="email">
                        <small class="text-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="age">Возраст</label>
                        <input th:value="${student.age > 0} ? ${student.age} : ''" th:field="*{age}" type="number" class="form-control" id="age">
                        <small class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"/>
                    </div>
                </div>

                <hr class="mb-4">
                <button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
            </div>
        </div>
    </form>
</div>
</div>
</div>

<br/><br/><br/>


<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>
</body>
</html>
public class Student {

    // Имя должно быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "First name should be from 2 to 50 characters")
    private String firstName;

    // Фамилия должна быть длиной от 2 до 50 символов
    @Size(min = 2, max= 50, message = "Last name should be from 2 to 50 characters")
    private String lastName;

    // Возраст должен быть целым числом от 13 до 65
    @Range(min = 13, max = 65, message = "Student age should be from 13 to 65 years")
    private int age;

    // Для валидации электронной почты используем регулярное выражение
    @Pattern(regexp = "^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$",
            message = "Invalid email format")
    private String email;

    public Student(String firstName, String lastName, int age, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.email = email;
    }

    public Student() {
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
@Controller
public class StudentController {

    private List<Student> list = new ArrayList<>();

    @GetMapping("/")
    public String addStudent(Model model) {
        model.addAttribute("student", new Student());
        return "index";
    }

    @PostMapping("/")
    public String processAddStudentForm(@Valid Student student, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "index";
        }

        System.out.println(student);

        list.add(student);
        return "redirect:/";
    }
}
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <title>Добавление студента</title>
</head>
<body class="bg-light">
<div class="container">
    <div class="py-5 text-center">
        <h2>Добавление студента</h2>
        <p class="lead">Заполните поля и нажмите кнопку 'Добавить студента'</p>
    </div>

    <form enctype="multipart/form-data" action="#" th:action="@{/}" th:object="${student}" method="post">
        <div class="row">
            <div class="col-md-12">
                <h4 class="mb-3">Поля для заполнения</h4>
                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="lastName">Фамилия</label>
                        <input th:field="*{lastName}" type="text" class="form-control" id="lastName">
                        <small class="text-danger" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"/>
                    </div>
                    <div class="col-md-6 mb-3">
                        <label for="firstName">Имя</label>
                        <input th:field="*{firstName}" type="text" class="form-control" id="firstName">
                        <small class="text-danger" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="email">Электронная почта</label>
                        <input th:field="*{email}" type="text" class="form-control" id="email">
                        <small class="text-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"/>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6 mb-3">
                        <label for="age">Возраст</label>
                        <input th:value="${student.age > 0} ? ${student.age} : ''" th:field="*{age}" type="number" class="form-control" id="age">
                        <small class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"/>
                    </div>
                </div>

                <div class="form-group">
                    <label for="file">Choose file:</label>
                    <input type="file" name="file" class="form-control-file" id="file">
                </div>

                <hr class="mb-4">
                <button class="btn btn-primary btn-lg btn-block" type="submit" value="Submit">Добавить студента</button>
            </div>
        </div>
    </form>
</div>
</div>
</div>

<br/><br/><br/>

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>
</body>
</html>
@Controller
public class StudentController {

    private List<Student> list = new ArrayList<>();

    @GetMapping("/")
    public String addStudent(Model model) {
        model.addAttribute("student", new Student());
        return "index";
    }

    @PostMapping("/")
    public String processAddStudentForm(@Valid Student student, @RequestParam("file") MultipartFile file, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "index";
        }

        System.out.println(student);

        list.add(student);

        // normalize the file path
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

        // save the file on the local file system
        try {
            Path path = Paths.get("c:\\temp\\" + fileName);
            Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "redirect:/";
    }
}
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB

spring.servlet.multipart.enabled=true
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>