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" %> Создание простого веб-приложения на сервлетах и jsp (часть 2)

    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, 15:49
    Прям то что доктор прописал :)
    Осталось найти как
    заменить список (List) на работу с базой данных, чтоб добавленные пользователи не пропадали после перезапуска сервера :)
    fatfaggy
    Уровень 26
    7 января 2018, 04:15
    попробуйте для начала использовать jdbc :)

    можете начать с этих туториалов:
    — на русском: www.javenue.info/post/java-jdbc-api
    — чуть подробнее на инглише: www.tutorialspoint.com/jdbc/

    еще вам понадобится в мавене добавить нужный коннектор (библиотеку). если используете MySql — тогда mysql-connector-java. рекомендую версию 5.1.39, более новые версии у меня не завелись)
    ну и конечно же скачать и установить какую-то СУБД перед этим всем и создать в ней базу данных (также можете создать и отдельного пользователя именно под эту базу, чтобы не ходить с рута).

    если будете выкладывать код на гитхаб — рекомендую данные подключения вынести в отдельный файл, который не будет заливаться на гитхаб, иначе любой посетитель вашего репозитория увидит ваши логины/пароли к вашей бд))
    прекрасно подойдет файл типа *.properties, а в коде использовать класс Properties для работы с ним :)
    GreegAV
    Уровень 8
    7 января 2018, 23:29
    Да! Туториалспоинт я тоже зауважал, когда на него наткнулся.
    Спасибо за подсказку и идеи. С нетерпением буду ждать третьей части!
    fatfaggy
    Уровень 26
    8 января 2018, 07:39
    третьей части о чем?)
    а то я пока не в курсе))
    GreegAV
    Уровень 8
    8 января 2018, 21:49
    Создание простого веб-приложения на сервлетах и jsp (часть 2)
    Ждем, надеемся и верим, что будет и третья часть :)
    fatfaggy
    Уровень 26
    19 января 2018, 18:29
    аа, так с сервлетами тут уже все :)
    реализовать работу с базой данных — это уже самим) там про jdbc почти нечего писать даже…
    как и дописать два сервлета для редактирования/удаления пользователей — тоже можно самим написать просто по аналогии с тем, как показано в статье
    RSeropyan
    Уровень 25
    22 января 2018, 16:02
    Подумываю дополнить вашу серию статей парочкой своих:
    1. Использование БД в качестве хранилища, причем в двух вариантах:
    а) Получение Connection из DriverManager — просто, куча примеров в интернете, но типа устарело.
    б)Получение Connection из DataSource — то, что рекомендуется сейчас, но вот тут так просто не разберешься, да и толковых примеров с объяснением концепции JNDI я не нашел (кроме оф.доков по Tomcat).
    2. Использование внутри jsp-страниц не скриплетов (это просто мрак), а JSTL.
    fatfaggy
    Уровень 26
    22 января 2018, 18:03
    конечно!) почему бы и нет?)
    если до этого статей никаких не писали — будет интересный опыт как объяснить все то, что уже сам знаешь, человеку, который (гипотетически) ничего об этой теме не знает. не такая уж и простая задача)) особенно, если учесть, что написать это надо так, чтобы человек смог дочитать до конца, а не закрыть статью после двух абзацев :)

    нуу, как минимум для меня это был интересный опыт :)
    да и поговаривают, что в некоторых компаниях такая активность может расцениваться как плюс к кандидату на вакансию)
    RSeropyan
    Уровень 25
    21 ноября 2017, 16:18
    Огромное спасибо за всю серию статей. Очень помогло!
    realcorwin
    Уровень 14
    9 ноября 2017, 18:06
    Делайте и дальше подобные тексты. У вас явно есть к сему талант и желание. А остальным будет польза :).
    fatfaggy
    Уровень 26
    10 ноября 2017, 19:02
    спасибо :) постараюсь
    saudabaew
    Уровень 40
    3 ноября 2017, 10:15
    Если бы статьи были написаны на месяц раньше, то можно было бы съэкономить кучу времени при решении задачи на стажировку. Описано кратко и по делу. Спасибо!
    wolfio
    Уровень 28
    30 октября 2017, 13:22
    расписано довольно детально) правда пока не пробовал все это воспроизвести
    однако, лично мое предположение: web-сервис — это в моем понимании все же клиент-сервер.
    Я конечно понимаю, что браузер условно и есть клиент, но тогда это скорее не сервис, а веб-сайт.
    Не хватает клиентской части, для обращения к базе имен (ну чтобы мимо браузера)

    Как долго ты разбирался со всем этим направлением?
    Какие основные направления JS нужно знать, для таких вот относительно простых задач?)
    fatfaggy
    Уровень 26
    31 октября 2017, 01:11
    Не хватает клиентской части, для обращения к базе имен (ну чтобы мимо браузера)
    ты имеешь ввиду получать список имен пользователей, например, в андроид-приложение не вызывая браузер? типа, открыл приложение, отправился запрос, принялся ответ, а приложение тебе отрисовало список имен пользователей? ну это лучше делать через REST, о котором я тут не писал и не планировал :) хотел написать именно про веб вариант, с браузером :)

    Как долго ты разбирался со всем этим направлением?
    нуу, я почитал Head First Servlets&JSP немного, потом сидел экспериментировал (большую часть времени боролся с ошибками, что томкат выдавал, чаще всего из-за кривой настройки артефактов). не знаю даже сколько времени, но не меньше нескольких недель :) но это было давно

    Какие основные направления JS нужно знать, для таких вот относительно простых задач?)
    ооо, javascript — это свой отдельный мир)) конкретно в этом примере из js там только закрытие окошек уведомлений
    this.parentElement.style.display='none'

    который я честно скопипастил из туториала W3.CSS :)
    аа, ну и
    location.href='/'
    чтоб добавить кнопкам адрес перехода, который загуглился за пару секунд :)
    а вообще по javascript могу предложить вот этот курс. сначала может показаться слишком просто, потому что синтаксис во многом похож на синтаксис java, но как дойдешь до замыканий — тогда начнется))