JavaRush /Java блог /Random UA /Створення простого веб-додатку на сервлетах та jsp (части...
Стас Пасинков
26 рівень
Киев

Створення простого веб-додатку на сервлетах та jsp (частина 2)

Стаття з групи Random UA
Створення простого веб-додатку на сервлетах та jsp (частина 1) Рівень знань, необхідних для розуміння статті: ви вже більш-менш розібралися з Java Core та хотіли б подивитися на JavaEE-технології та web-програмування. Логічніше, якщо ви зараз вивчаєте квест Java Collections, де розглядаються близькі статті теми.
Створення простого веб-додатку на сервлетах та jsp (частина 2) - 1

Створюємо сутності

У пакеті entities створимо клас User , ну а в ньому – дві приватні рядкові змінні name та password . Створимо конструктори (за замовчуванням і такий, який приймав обидва значення), гетери/сеттери, перевизначимо метод toString() про всяк випадок, а також методи equals() і hashCode() . Тобто зробимо все те, що робить пристойний Java-розробник під час створення класу.
public class User {
    private String name;
    private String password;

    public User() {
    }

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (name != null ? !name.equals(user.name) : user.name != null) return false;
        return password != null ? password.equals(user.password) : user.password == null;

    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }
}
Тепер можемо розпочинати створення списку користувачів. До нього будемо додавати користувачів, і звідки їх забиратимемо для відображення. Однак тут є одна проблема. Об'єкти наших сервлетів створюємо не ми, за нас це робить Tomcat . Методи, які ми перевизначаємо в них, також вже визначені за нас, і додати параметр ми не можемо. Як же тоді створити загальний список, який було б видно в обох наших сервлетах? Якщо ми просто в кожному сервлет створимо свій об'єкт списку, то вийде, що додавати користувачів ми будемо в один список, а виводити список користувачів сервлет ListServlet- в інший. Виходить, нам потрібний такий об'єкт, який був би спільним для обох сервлетів. Якщо говорити узагальнено, нам потрібний такий об'єкт, який був би спільним для всіх класів у нашій програмі; єдиний об'єкт всю програму. Сподіваюся, ви щось чули про шаблони проектування. І, можливо, для когось це перша реальна необхідність використання шаблону Singleton у своїй програмі. Можете перекрутитися і «запиляти» який-небудь крутий Singleton , з подвійними перевірками та синхронізаціями (так-так, у нас багатопотоковий додаток, оскільки сервлети Tomcat запускає в різних потоках), але я використовуватиму варіант з ранньою ініціалізацією, оскільки тут його цілком вистачає, і він підходить для наших цілей.

Створення моделі

Створимо клас (і реалізуємо в ньому шаблон Singleton ) у пакеті model і назвемо його якось незвичайно. Скажімо, Model . Створимо в класі приватний об'єкт списку користувачів, і реалізуємо два способу: один у тому, щоб можна було додати користувача, а другий повернення списку рядків (імен користувачів). Оскільки наш об'єкт користувача складається з імені та пароля, а паролі користувачів ми «світити» не хотіли б, будемо лише список імен.
public class Model {
    private static Model instance = new Model();

    private List<User> model;

    public static Model getInstance() {
        return instance;
    }

    private Model() {
        model = new ArrayList<>();
    }

    public void add(User user) {
        model.add(user);
    }

    public List<String> list() {
        return model.stream()
                .map(User::getName)
                .collect(Collectors.toList());
    }
}

Трохи про mvc

Якщо вже ви чули про singleton , значить можливо чули і про інший шаблон проектування - MVC (model-view-controller, російською модель-представлення-контролер, або прямий так як і на англійському модель-в'ю-контролер). Його суть у тому, щоб відокремлювати бізнес-логіку від вистави. Тобто відокремлювати код, який визначає, що робити від коду, який визначає, як відображати. View (подання або просто завірюхи) відповідає за те, в якому вигляді представляти якісь дані. У нашому випадку завірюхи - це наші jsp-сторінки. Саме тому я їх і поклав у татко з назвою views . Модель— це, власне, самі дані, з якими працює програма. У нашому випадку, це користувачі (список користувачів). Ну а контролери — сполучна ланка між ними. Беруть дані з моделі та передають їх у завірюхи (або отримують від Tomcat якісь дані, обробляють їх та передають моделі). Бізнес-логіку (що саме програма має робити) потрібно описувати в них, а не в моделі чи в'юсі. Таким чином, кожен займається своєю справою:
  • модель зберігає дані;
  • завірюхи малюють гарне представлення даних;
  • контролери займаються обробкою даних.
Це дозволяє програмі бути досить простим і підтримуваним, а не монструозним звалищем всього коду в одному класі. MVC підходить не лише для веб-програмування, але саме у цій сфері він зустрічається особливо часто (чи не завжди). У нашому випадку як контролери виступатимуть сервлети. Це дуже поверховий та короткий опис патерну, але MVC – не головна тема цієї статті. Хто хоче дізнатися більше – Google на допомогу! Додаємо у файл add.jsp форму, що складається з двох текстових полів введення (одне звичайне, інше - пароль) та кнопки для відправки даних на сервер.
<form method="post">
    <label>Name:
        <input type="text" name="name"><br />
    </label>

    <label>Password:
        <input type="password" name="pass"><br />
    </label>
    <button type="submit">Submit</button>
</form>
Тут у форми вказано атрибут method зі значенням post . Це говорить про те, що дані з цієї форми полетять на сервер у вигляді запиту POST. Атрибут action не вказаний, означає запит відправиться за тією ж адресаою, якою ми перейшли на цю сторінку ( /add ). Таким чином, наш сервлет, прив'язаний до цієї адресаи, при отриманні GET-запиту повертає цю JSP з формою додавання користувачів, а якщо отримає POST-запит, значить, форма відправила туди свої дані (які ми в методі doPost() витягнемо з об'єкта запиту , обробимо та передамо в модель на збереження). Варто звернути увагу, що у полів введення вказано параметр name(Для поля з ім'ям він має значення name, а для поля з паролем - pass ). Це досить важливий момент. Так як щоб отримати з запиту (всередині сервлета вже) ці дані (ім'я та пароль, які будуть введені) - ми будемо використовувати саме ці name і pass . Але про це трохи згодом. Сама кнопка відправки даних у мене зроблена знову ж таки у вигляді button , а не полем виведення, як це зазвичай прийнято. Не знаю, наскільки такий варіант універсальний, але у мене працює (браузер Chrome).

Обробка POST-запиту сервлетом

Повернемося до сервлета AddServlet . Нагадаю: щоб наш сервлет умів «ловити» GET-запити, ми перевизначабо метод doGet() із класу HttpServlet . Щоб навчити наш сервлет відловлювати ще й POST-звапроси, нам потрібно перевизначити ще метод doPost() . Він отримує аналогічні об'єкти запиту та відповіді від Tomcat , з якими ми і працюватимемо. Для початку витягнемо із запиту параметри name і pass, які відправила форма (якщо ви їх у формі назвали по-іншому - тоді саме ті назви і пишете). Після цього створимо об'єкт нашого користувача, використовуючи отримані дані. Потім отримаємо об'єкт моделі та додамо створеного користувача до моделі.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String name = req.getParameter("name");
    String password = req.getParameter("pass");
    User user = new User(name, password);
    Model model = Model.getInstance();
    model.add(user);
}

Передача даних у view

Перейдемо до сервлета ListServlet . Тут вже реалізований метод doGet() , який просто передає управління у завірюху list.jsp . Якщо у вас ще немає — зробіть за аналогією з таким же методом з сервлета AddServlet . Тепер було б непогано отримати з моделі список імен користувачів і передати їх у завірюху, яка їх отримає і гарно відобразить. Для цього знову скористаємось об'єктом запиту, який ми отримали від Tomcat . До цього об'єкту ми можемо додати атрибут, давши йому якесь ім'я, і, власне, сам об'єкт, який ми хотіли б передати у view. Завдяки тому, що при передачі процесу виконання з сервлету у завірюху ми передаємо туди ці ж об'єкти запиту та відповіді, що отримав сам сервлет, то і додавши наш список імен до об'єкта запиту ми потім з цього об'єкта запиту у завірюсі зможемо наш список імен користувачів та отримати. З класом ListServlet ми закінчабо, тому наводжу код всього класу:
package app.servlets;

import app.model.Model;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class ListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Model model = Model.getInstance();
        List<String> names = model.list();
        req.setAttribute("userNames", names);

        RequestDispatcher requestDispatcher = req.getRequestDispatcher("views/list.jsp");
        requestDispatcher.forward(req, resp);
    }
}

Виконання java-коду в jsp-файлух

Настав час зайнятися файлом list.jsp . Він виконається лише тоді, коли ListServlet передасть сюди процес виконання. Крім того, ми в тому сервлеті вже підготували список імен користувачів із моделі та передали сюди в об'єкті запиту. Оскільки ми маємо список імен, ми можемо пробігтися по ньому циклом for і вивести кожне ім'я. Як я вже казав, jsp-файли можуть виконувати java-код (чим і відрізняються від статичних html-сторінок). Для того, щоб виконати якийсь код, достатньо поставити в потрібному місці конструкцію:
<!-- html код -->
<%
    // java код
%>
<!-- html код -->
Усередині такої конструкції ми отримуємо доступ до кількох змінних:
  • request — наш об'єкт запиту, який ми передали із сервлета, де він називався просто req ;
  • responce - об'єкт відповіді, в сервлет називався resp ;
  • out - об'єкт типу JspWriter (успадковується від звичайного Writer ), за допомогою якого можемо "писати" щось прямо в саму html-сторінку. Запис out.println(«Hello world!») дуже схожий на запис System.out.println(«Hello world!») , але не плутайте їх!
    out.println() «пише» до html-сторінки, а System.out.println — до системного висновку. Якщо викликати всередині розділу з Java-кодом jsp-метод System.out.println() - результати побачите в консолі Tomcat , а не на сторінці.

Про інші доступні об'єкти всередині jsp можна знайти тут . Використовуючи об'єкт request , ми можемо отримати список імен, який передавали із сервлета (ми прикріпабо відповідний атрибут до цього об'єкта), а використовуючи об'єкт out – можемо вивести ці імена. Зробимо це (поки просто у вигляді html-списку):
<ul>
    <%
        List<String> names = (List<String>) request.getAttribute("userNames");

        if (names != null && !names.isEmpty()) {
            for (String s : names) {
                out.println("<li>" + s + "</li>");
            }
        }
    %>
</ul>
Якщо потрібно виводити список тільки в тому випадку, коли є користувачі, а інакше виводити попередження, що користувачів поки немає, можемо трохи переписати цю ділянку:
<%
    List<String> names = (List<String>) request.getAttribute("userNames");

    if (names != null && !names.isEmpty()) {
        out.println("<ui>");
        for (String s : names) {
            out.println("<li>" + s + "</li>");
        }
        out.println("</ui>");
    } else out.println("<p>There are no users yet!</p>");
%>
Тепер, коли ми вміємо передавати дані з сервлетів у завірюхи, можемо трохи покращити наш AddServlet , щоб виводилося повідомлення про успішне додавання користувача. Для цього в методі doPost() після того, як додали нового користувача в модель, можемо додати ім'я цього користувача в атрибути об'єкта req і передати керування назад у завірюху add.jsp . А в ній вже зробити ділянку з Java-кодом, в якому відбувається перевірка, чи є такий атрибут у запиті, і якщо так, то висновок повідомлення про те, що користувач успішно доданий. Після цих змін повний код сервлету AddServlet виглядатиме приблизно так:
package app.servlets;

import app.entities.User;
import app.model.Model;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AddServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("views/add.jsp");
        requestDispatcher.forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String password = req.getParameter("pass");
        User user = new User(name, password);
        Model model = Model.getInstance();
        model.add(user);

        req.setAttribute("userName", name);
        doGet(req, resp);
    }
}
Тут наприкінці методу doPost() ми встановлюємо атрибут з ім'ям доданого в модель користувача, після чого викликаємо метод doGet() , який передаємо поточні запит і відповідь. А метод doGet() вже передає управління у завірюху, куди і відправляє об'єкт запиту з прикріпленим ім'ям доданого користувача як атрибут. Залишилося підправити add.jsp , щоб він виводив таке повідомлення, якщо такий атрибут. Остаточний варіант add.jsp :
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Add new user</title>
    </head>

    <body>
        <div>
            <h1>Super app!</h1>
        </div>

        <div>
            <%
                if (request.getAttribute("userName") != null) {
                    out.println("<p>User '" + request.getAttribute("userName") + "' added!</p>");
                }
            %>
            <div>
                <div>
                    <h2>Add user</h2>
                </div>

                <form method="post">
                    <label>Name:
                        <input type="text" name="name"><br />
                    </label>
                    <label>Password:
                        <input type="password" name="pass"><br />
                    </label>
                    <button type="submit">Submit</button>
                </form>
            </div>
        </div>

        <div>
            <button onclick="location.href='/'">Back to main</button>
        </div>
    </body>
</html>
Тіло сторінки складається з:
  • div-a із шапкою;
  • div-контейнер для контенту, в ньому перевірка чи існує атрибут з ім'ям користувача;
  • div із формою додавання користувачів;
  • ну і в кінці футер з кнопкою повернення на головну сторінку.
Може здатися, що дуже багато div-ів, але ми їх потім використовуємо, коли додамо стилів. Остаточний варіант list.jsp :
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Users</title>
    </head>

    <body>
        <div>
            <h1>Super app!</h1>
        </div>

        <div>
            <div>
                <div>
                    <h2>Users</h2>
                </div>
                <%
                    List<String> names = (List<String>) request.getAttribute("userNames");

                    if (names != null && !names.isEmpty()) {
                        out.println("<ui>");
                        for (String s : names) {
                            out.println("<li>" + s + "</li>");
                        }
                        out.println("</ui>");
                    } else out.println("<p>There are no users yet!</p>");
                %>
            </div>
        </div>

        <div>
            <button onclick="location.href='/'">Back to main</button>
        </div>
    </body>
</html>
Таким чином, у нас готовий повністю робочий веб-додаток, який вміє зберігати та додавати користувачів, а також виводити список їхніх імен. Залишилося лише прикрасити… :)

Додавання стилів. Використовуємо фреймворк W3.CSS

В даний момент наш додаток робочий, але абсолютно вирвиокий. Тому додамо фон, колір тексту та кнопок, стилізуємо списки, зробимо вирівнювання, додамо відступи тощо. Якщо писати стилі вручну, це може зайняти багато часу та нервів. Тому я пропоную скористатися CSS-фреймворком W3.CSS. У ньому вже є готові класи зі стилями, залишилося лише розставити у потрібних місцях ті css-класи, які ми хочемо застосувати. Для того, щоб додати їх на наші сторінки, спочатку підключимо файл зі стилями. Це можна зробити двома способами:
  1. пройтись по наших сторінках і в розділі head вставити пряме посилання на файл зі стилями

    <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">

    Такий варіант підходить, якщо у вас є постійне підключення до інтернету. Тоді під час відкриття ваших сторінок на локальному сервері стилі підтягнуться з інтернету.


  2. Якщо ж ви хочете мати всі стилі у себе локально і не бути залежним від інтернет-з'єднання, завантажте файл зі стилями і помістіть його десь усередині папки web (наприклад, web/styles/ w3.css ) , після чого пройтися по всіх наших сторінкам ( index.html, add.jsp, list.jsp ) і вписати всередині розділу head посилання на цей файл зі стилями

    <link rel="stylesheet" href="styles/w3.css">

    Після цього просто пройтися тегами і дописати ті стилі, які вам сподобаються. Я не зупинятимуся на цьому докладно, а відразу дам свої готові варіанти трьох моїх файлів з розставленими класами стилів.

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Super app!</title>
        <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
    </head>

    <body class="w3-light-grey">
        <div class="w3-container w3-blue-grey w3-opacity w3-right-align">
            <h1>Super app!</h1>
        </div>

        <div class="w3-container w3-center">
            <div class="w3-bar w3-padding-large w3-padding-24">
                <button class="w3-btn w3-hover-light-blue w3-round-large" onclick="location.href='/list'">List users</button>
                <button class="w3-btn w3-hover-green w3-round-large" onclick="location.href='/add'">Add user</button>
            </div>
        </div>
    </body>
</html>

add.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Add new user</title>
        <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
    </head>

    <body class="w3-light-grey">
        <div class="w3-container w3-blue-grey w3-opacity w3-right-align">
            <h1>Super app!</h1>
        </div>

        <div class="w3-container w3-padding">
            <%
                if (request.getAttribute("userName") != null) {
                    out.println("<div class=\"w3-panel w3-green w3-display-container w3-card-4 w3-round\">\n" +
                            "   <span onclick=\"this.parentElement.style.display='none'\"\n" +
                            "   class=\"w3-button w3-margin-right w3-display-right w3-round-large w3-hover-green w3-border w3-border-green w3-hover-border-grey\">×</span>\n" +
                            "   <h5>User '" + request.getAttribute("userName") + "' added!</h5>\n" +
                            "</div>");
                }
            %>
            <div class="w3-card-4">
                <div class="w3-container w3-center w3-green">
                    <h2>Add user</h2>
                </div>
                <form method="post" class="w3-selection w3-light-grey w3-padding">
                    <label>Name:
                        <input type="text" name="name" class="w3-input w3-animate-input w3-border w3-round-large" style="width: 30%"><br />
                    </label>
                    <label>Password:
                        <input type="password" name="pass" class="w3-input w3-animate-input w3-border w3-round-large" style="width: 30%"><br />
                    </label>
                    <button type="submit" class="w3-btn w3-green w3-round-large w3-margin-bottom">Submit</button>
                </form>
            </div>
        </div>

        <div class="w3-container w3-grey w3-opacity w3-right-align w3-padding">
            <button class="w3-btn w3-round-large" onclick="location.href='/'">Back to main</button>
        </div>
    </body>
</html>

list.jsp

<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Users list</title>
        <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
    </head>

    <body class="w3-light-grey">
        <div class="w3-container w3-blue-grey w3-opacity w3-right-align">
            <h1>Super app!</h1>
        </div>

        <div class="w3-container w3-center w3-margin-bottom w3-padding">
            <div class="w3-card-4">
                <div class="w3-container w3-light-blue">
                    <h2>Users</h2>
                </div>
                <%
                    List<String> names = (List<String>) request.getAttribute("userNames");

                    if (names != null && !names.isEmpty()) {
                        out.println("<ul class=\"w3-ul\">");
                        for (String s : names) {
                            out.println("<li class=\"w3-hover-sand\">" + s + "</li>");
                        }
                        out.println("</ul>");

                    } else out.println("<div class=\"w3-panel w3-red w3-display-container w3-card-4 w3-round\">\n"
+
                            "   <span onclick=\"this.parentElement.style.display='none'\"\n" +
                            "   class=\"w3-button w3-margin-right w3-display-right w3-round-large w3-hover-red w3-border w3-border-red w3-hover-border-grey\">×</span>\n" +
                            "   <h5>There are no users yet!</h5>\n" +
                            "</div>");
                %>
            </div>
        </div>

        <div class="w3-container w3-grey w3-opacity w3-right-align w3-padding">
            <button class="w3-btn w3-round-large" onclick="location.href='/'">Back to main</button>
        </div>
    </body>
</html>
Ось і все :) Якщо у вас залишабося якісь питання або є якісь зауваження, або навпаки щось не виходить - залиште коментар. UPD: якщо у вас виникають проблеми з 404 помилкою при натисканні на кнопки, хоча все зроблено правильно - можливо, вам варто поправити конфігурацію деплою в ідеї. Для цього треба зайти в Edit configurations (там вгорі біля кнопки запуску), там перейти в правій частині вікна на вкладку Deployment і зробити так, щоб у Application context було вказано просто / Ну і парочку скріншотів прикладу що з цього вийшло.
Створення простого веб-додатку на сервлетах та jsp (частина 2) - 2
Створення простого веб-додатку на сервлетах та jsp (частина 2) - 3
Створення простого веб-додатку на сервлетах та jsp (частина 2) - 4
І насамкінець Якщо буде бажання попрактикуватися з цим проектом — можете спробувати:
  • зробити сервлет та jsp для видалення користувача та ще пару для зміни/редагування існуючого користувача. Вийде справжній CrUD веб-додаток :) на сервлетах));
  • замінити список (List) працювати з базою даних, щоб додані користувачі не пропадали після перезапуску сервера :)
Успіхів!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ