JavaRush /Java блог /Архив info.javarush /Создание простого веб-приложения на сервлетах и jsp (част...
fatfaggy
26 уровень
Киев

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

Статья из группы Архив info.javarush
Продолжаю описывать процесс создания веб-приложения используя сервлеты, jsp, мавен и томкат. Начало статьи, если необходимо.
Создаем сущности.
Создадим в пакете entities класс User, в котором создадим две приватные строковые переменные name и password. Создадим конструкторы (по умолчанию и такой, который бы принимал оба значения), геттеры/сеттеры, переопределим метод toString() на всякий случай, а так же методы equals() и hashCode(). 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; } } Теперь можем приступить к созданию списка пользователей, куда мы будем наших пользователей добавлять, и откуда будем их забирать для отображения. Но есть проблема. Объекты наших сервлетов мы не создаем, их создает за нас томкат. Методы, которые мы переопределяем в них - тоже за нас уже определены и добавить параметр мы не можем. Как же тогда создать общий список, который бы виден был в обоих наших сервлетах? Если мы просто в каждом сервлете создадим свой объект списка - то получится, что добавлять пользователей мы будем в один список, а выводить список пользователей сервлетом ListServlet - совершенно другой. Получается, что нам нужен такой объект, который был бы общим для обоих сервлетов. Если говорить обобщенно, то нам нужен такой объект, который был бы общим для всех классов в нашей программе; единственный объект на всю программу. Надеюсь, вы что-то да слышали про шаблоны проектирования. И возможно для кого-то это первая реальная необходимость использования шаблона Singleton в своей программе. Можете поизвращаться и запилить какой-нибудь крутой синглтон, с двойными проверками и синхронизациями (да-да, у нас многопоточное приложение, так как сервлеты томкат запускает в разных потоках), но я буду использовать вариант с ранней инициализацией, так как в данном случае он вполне подходит.
Создание модели.
Создадим тогда класс (и реализуем в нем шаблон singleton) в пакете model, ну и назовем тоже вполне красочно Model. Создадим в нем приватный объект списка пользователей, и сделаем два метода: один для того, чтоб можно было добавить пользователя, а второй - пусть просто возвращает список строк (имен пользователей). Поскольку наш объект пользователя состоит из имени и пароля - то пароли пользователей мы "светить" не хотели бы, поэтому и возвращать будем только список их имен. public class Model { private static Model instance = new Model(); private List model; public static Model getInstance() { return instance; } private Model() { model = new ArrayList<>(); } public void add(User user) { model.add(user); } public List list() { return model.stream() .map(User::getName) .collect(Collectors.toList()); } }
Немного про mvc.
Раз уж вы слышали про singleton, значит возможно слышали и про другой шаблон проектирования - MVC (model-view-controller, или по-русски модель-представление-контроллер, или прям так как и на английском модель-вью-контроллер). Его суть в том, чтобы отделять бизнес-логику от представления. То-есть, отделять код, который определяет что делать от кода, который определяет как отображать. View (представление или просто вьюхи) как-раз и отвечает за то, в каком виде представлять какие-то данные. В нашем случае вьюхи - это наши jsp странички. Именно поэтому я их и положил в папочку с названием views. Модель - это собственно сами данные, с которыми работает программа. В нашем случае это пользователи (список пользователей). Ну а контроллеры - связующее звено между ними. Берут данные из модели и передают их во вьюхи, ну или получают от томката какие-то данные, обрабатывают их и передают модели. Бизнес-логика (то-есть что делать) должна быть описана в них, а не в модели или во вьюхе. Таким образом, каждый занимается своим делом:
  • модель хранит данные
  • вьюхи рисуют красивое представление данных
  • контроллеры занимаются обработкой данных
Это позволяет всем им быть достаточно простыми и поддерживаемыми. А не монструозной свалкой всего кода в одном классе. MVC подходит не только для веб-программирования, но все-же в этой сфере он встречается очень часто (если не всегда). В нашем случае в качестве контроллеров будут выступать сервлеты. Да это очень поверхностное и даже грубое описание этого паттерна, но эта статья не о шаблонах проектирования, а о том, как сделать простенькое веб-приложение :) Кто хочет узнать больше - гугл знает все! :) Вернемся к нашим вьюхам.
Создаем форму добавления пользователя.
Добавим в файл add.jsp форму, состоящую из двух текстовых инпутов (один обычный, другой типа пароль) и кнопки для отправки данных на сервер.
Здесь у формы указан атрибут method со значением post. Это говорит о том, что данные из этой формы полетят на сервер в виде POST-запроса. Атрибут action не указан, значит запрос этот полетит по тому же адресу, по которому мы перешли на эту страничку (/add). Таким образом наш сервлет, который привязан к этому адресу, при получении GET-запроса возвращает эту jsp с формой добавления пользователей, а если получит POST-запрос - значит эта форма отправила туда свои данные (которые мы в методе doPost() вытащим из объекта запроса, обработаем и передадим в модель на сохранение). Стоит обратить внимание, что у инпутов здесь указан параметр name (для поля с именем он имеет значение name, а для поля с паролем - pass). Это довольно важный момент. Так как чтобы получить из запроса (внутри сервлета уже) эти данные (имя и пароль которые будут введены) - мы будем использовать именно эти name и pass. Но об этом чуть позже. Сама кнопка отправки данных у меня сделана снова же в виде button, а не инпутом, как это обычно принято. Не знаю насколько такой вариант универсален, но у меня в хроме работает :)
Обработка POST-запроса сервлетом.
Вернемся к сервлету AddServlet. Мы уже знаем, что чтобы наш сервлет умел "ловить" GET-запросы - мы переопределили метод doGet() из класса HttpServlet. Чтобы научить наш сервлет ловить еще и POST-звапросы - мы переопределяем еще и метод doPost(). Он получает аналогичные объекты запроса и ответа от томката, с которыми мы и будем работать. Для начала вытащим из запроса параметры 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); }
Передача данных во вьюху.
Теперь перейдем к сервлету ListServlet. У нас тут уже реализован метод doGet(), который просто передает управление во вьюху list.jsp. Если у вас этого еще нет - сделайте по аналогии с таким же методом из сервлета AddServlet. Теперь было бы неплохо получить из модели список имен пользователей и передать их во вьюху, которая их там получит и красивенько отобразит. Для этого воспользуемся снова же объектом запроса, который мы получили от томката. К этому объекту мы можем добавить атрибут, дав ему какое-то имя ну и собственно сам объект, который бы мы хотели передать во вьюху. Благодаря тому, что при передаче процесса выполнения из сервлета во вьюху мы передаем туда эти же объекты запроса и ответа, что получил сам сервлет, то и добавив наш список имен к объекту запроса мы потом из этого объекта запроса во вьюхе сможем наш список имен пользователей и получить. С классом 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 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 страничек). Для того, чтобы выполнить какой-то код - достаточно поставить в нужном нам месте конструкцию <% // java код %> Внутри такой конструкции мы получаем доступ к нескольким переменным: request - наш объект запроса, который мы передали из сервлета, где он назывался просто req responce - объект ответа, в сервлете назывался resp out - объект типа JspWriter (наследуется от обычного Writer), при помощи которого можем "писать" что-либо прямо в саму html страничку. Запись out.println("Hello world!") очень похожа на запись System.out.println("Hello world!"), но не стоит их путать! out.println() "пишет" в html страничку, а System.out.println - в системный вывод. Если вызвать внутри раздела с джава кодом jsp метод System.out.println() - то результаты увидите в консоли томката, а не на страничке, как возможно хотелось бы :) Про другие доступные объекты внутри jsp можно поискать тут. Используя объект request мы можем получить список имен, который передавали из сервлета (мы прикрепили соответствующий атрибут к этому объекту), а используя объект out - можем вывести эти имена. Сделаем это пока просто в виде html списка:
    <% List names = (List) request.getAttribute("userNames"); if (names != null && !names.isEmpty()) { for (String s : names) { out.println("
  • " + s + "
  • "); } } %>
Если же хотим выводить список только в том случае, когда есть пользователи, а иначе выводить предупреждение, что пользователей пока нет - можем немного переписать этот участок: <% List names = (List) request.getAttribute("userNames"); if (names != null && !names.isEmpty()) { out.println(""); for (String s : names) { out.println("
  • " + s + "
  • "); } out.println("
    "); } else out.println("

    There are no users yet!

    "); %>
    Теперь, когда мы умеем передавать данные из сервлетов во вьюхи - можем немного улучшить наш AddServlet, чтобы выводилось уведомление об успешном добавлении пользователя. Для этого в методе doPost() после того, как добавили нового пользователя в модель - можем добавить имя этого пользователя в атрибуты объекта req и передать управление обратно во вьюху add.jsp. А в ней уже сделать участок с джава кодом, где проверять есть ли такой атрибут в запросе, и если да - то выводить сообщение, что пользователь успешно добавлен. После этих изменений полный код сервлета 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" %> Add new user

    Super app!

    <% if (request.getAttribute("userName") != null) { out.println("

    User '" + request.getAttribute("userName") + "' added!

    "); } %>

    Add user

    Тело страницы состоит из div-a с шапкой, после чего div-контейнер для контента, в нем проверка существует ли атрибут с именем пользователя, потом div с формой добавления пользователей, ну и в конце футер с кнопкой возврата на главную страницу. Может показаться, что слишком много div-ов, но мы их потом используем, когда добавим стилей :) Ну и конечный вариант list.jsp <%@ page import="java.util.List" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> Users

    Super app!

    Users

    <% List names = (List) request.getAttribute("userNames"); if (names != null && !names.isEmpty()) { out.println(""); for (String s : names) { out.println("
  • " + s + "
  • "); } out.println("
    "); } else out.println("

    There are no users yet!

    "); %>
    Таким образом, мы имеем полностью рабочее веб приложение, которое умеет хранить и добавлять пользователей, а так же выводить список их имен. Осталось лишь приукрасить... :)
    Добавление стилей. Используем фреймворк W3.CSS.
    В данный момент наше приложение рабочее, но абсолютно вырвиглазное :) Нужно добавить фон, цвета текста и кнопок, стилизировать списки, сделать выравнивание, добавить отступов, в общем много чего. Если писать стили вручную - это может занять много времени и нервов. Поэтому я предлагаю воспользоваться CSS фреймворком W3.CSS. В нем уже есть готовые классы со стилями, осталось только расставить в нужных местах те css классы, которые мы хотим в этих местах применить. Для того, чтобы добавить их к себе на страницы во-первых нам надо подключить файл со стилями. Это можно сделать двумя способами: 1. пройтись по нашим страницам и в разделе head вставить прямую ссылку на файл со стилями Такой вариант подходит, если у вас постоянное подключение к интернету. Тогда при открытии ваших страниц на локальном сервере - стили подтянутся из интернета. 2. Если же вы хотите иметь все стили у себя локально и не быть зависимым от интернет-соединения - можете просто скачать файл со стилями и поместить его где-нибудь внутри папочки web (например web/styles/w3.css), после чего пройтись по всем нашим страничкам (index.html, add.jsp, list.jsp) и вписать внутри раздела head ссылку на этот файл со стилями После этого просто пройтись по тегам и подописывать им те стили, которые вам понравятся. Я не буду останавливаться на этом подробно, а сразу дам свои готовые варианты трех моих файлов с раставленными классами стилей. index.html Super app!

    Super app!

    add.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> Add new user

    Super app!

    <% if (request.getAttribute("userName") != null) { out.println("
    \n" + " ×\n" + "
    User '" + request.getAttribute("userName") + "' added!
    \n" + "
    "); } %>

    Add user

    list.jsp <%@ page import="java.util.List" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> Users list

    Super app!

    Users

    <% List names = (List) request.getAttribute("userNames"); if (names != null && !names.isEmpty()) { out.println("
      "); for (String s : names) { out.println("
    • " + s + "
    • "); } out.println("
    "); } else out.println("
    \n" + " ×\n" + "
    There are no users yet!
    \n" + "
    "); %>
    Вот и все :) Если у вас остались какие-то вопросы или есть какие-то замечания, или же наоборот что-то не получается - оставьте комментарий. Ну и парочку скриншотов приложу что из этого всего получилось. Главная страница приложения Окно добавления пользователя Список пользователей И напоследок. Если будет желание попрактиковаться с этим проектом - можете попробовать:
    • сделать сервлет и jsp для удаления пользователя и еще пару для изменения/редактирования существующего пользователя. Получится настоящее CrUD веб приложение :) на сервлетах))
    • заменить список (List) на работу с базой данных, чтоб добавленные пользователи не пропадали после перезапуска сервера :)
    Удачи!
    Комментарии (14)
    ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
    ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
    GreegAV Уровень 8
    15 декабря 2017
    Прям то что доктор прописал :)
    Осталось найти как
    заменить список (List) на работу с базой данных, чтоб добавленные пользователи не пропадали после перезапуска сервера :)
    RSeropyan Уровень 25
    21 ноября 2017
    Огромное спасибо за всю серию статей. Очень помогло!
    realcorwin Уровень 14
    9 ноября 2017
    Делайте и дальше подобные тексты. У вас явно есть к сему талант и желание. А остальным будет польза :).
    saudabaew Уровень 40
    3 ноября 2017
    Если бы статьи были написаны на месяц раньше, то можно было бы съэкономить кучу времени при решении задачи на стажировку. Описано кратко и по делу. Спасибо!
    wolfio Уровень 28
    30 октября 2017
    расписано довольно детально) правда пока не пробовал все это воспроизвести
    однако, лично мое предположение: web-сервис — это в моем понимании все же клиент-сервер.
    Я конечно понимаю, что браузер условно и есть клиент, но тогда это скорее не сервис, а веб-сайт.
    Не хватает клиентской части, для обращения к базе имен (ну чтобы мимо браузера)

    Как долго ты разбирался со всем этим направлением?
    Какие основные направления JS нужно знать, для таких вот относительно простых задач?)