• ,

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

Эта статья является продолжением моей статьи Создание простейшего web проекта в intellij idea enterprise edition. Пошагово, с картинками, в которой я показал, как создать работающий шаблон веб-проекта.

В этот раз я покажу как создать довольно простое, но вполне симпатичное веб-приложение, используя технологии Java Servlet API и JavaServer Pages API.

Наше приложение будет иметь главную страницу с двумя ссылками:
  1. на страницу добавления пользователя
  2. на страницу просмотра списка пользователей

Так же, буду использовать Intellij Idea Enterprise Edition, Apache Maven (просто подключим несколько зависимостей) и Apache Tomcat. В конце «украсим» наше приложение используя W3.CSS CSS фреймворк.

Я не буду тут рассказывать как создать шаблон веб-приложения, так как вполне подробно описал это в первой статье. Будем считать, что на момент начала прочтения этой статьи у вас уже есть пустой проект, который мы и будем тут развивать. Если же нету — пробегитесь по первой статье и сделайте его, у вас это займет всего лишь несколько минут :)

Приступим.

Для начала немного о структуре будущего приложения.
Главная страница (/) у нас будет самый обычный статический html с шапкой и двумя ссылками/кнопками:
  • добавить нового пользователя (будет отправлять на адрес /add)
  • просмотреть список пользователей (отправляет на адрес /list)
Запросы по этим адресам томкат будет ловить и отправлять на какой-то из двух сервлетов, которые мы сделаем (маппинг мы распишем в файле web.xml). А сервлеты, в свою очередь, будут обрабатывать запросы, готовить данные (ну или сохранять их в случае добавления пользователя), и передавать управление в соответствующие jsp файлы, которые уже будут «отрисовывть» результат.
Данные будем хранить в самом обычном списке (List).

Создадим статическую главную страницу.
Если у вас в папке web лежит index.jsp — удаляйте его. Вместо него в этой папке создадим простой html файл с именем index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My super project!</title>
</head>
<body>
    <!-- header -->
    <div>
        <h1>Super app!</h1>
    </div>

    <div>       <!-- content -->
        <div>   <!-- buttons holder -->
            <button onclick="location.href='/list'">List users</button>
            <button onclick="location.href='/add'">Add user</button>
        </div>
    </div>
</body>
</html>

Тут ничего сложного. В title указываем заголовок нашей страницы. В теле страницы у нас два основных дива: хедер/шапка и контент. В контенте у нас холдер для наших кнопок, ну и собственно две кнопки, которые при нажатии на себя отправляют на соответствующие адреса.
Можете запустить проект и посмотреть как он сейчас выглядит. Если нажимать на кнопки — то открываются страницы с 404й ошибкой, потому что у нас их пока нет. Но это говорит о том, что кнопки работают.
Сразу оговорюсь, это не самый универсальный вариант, так как с отключенным джаваскриптом в браузере от этих кнопок толку никакого, но будем считать, что никто себе джаваскрипт не отключал :) Можно было бы в принципе и обойтись простыми ссылками, но мне в этом случае больше нравятся кнопки. Вы же можете делать как хотите.
И не смотрите, что в моих примерах будет много div-ов. Потом мы их наполним стилями чтоб все выглядело покрасивее :)

Создаем jsp файлы, которые будут отрисовывать результат.
В той же папочке web создадим папку, куда будем складывать наши jsp. Можете назвать ее как угодно, я ее назову views.
В этой папке создадим два jsp файла:
  1. add.jsp — страничка для добавления пользователей
  2. list.jsp — страничка для показа списка пользователей
Проставим им соответствующие заголовки страницы. Что-нибудь типа «Add new user» и «Users list» и пока так и оставим.

Создадим два сервлета.
Сервлеты будут принимать и обрабатывать запросы, которые им будет передавать томкат.
В папке src/main/java создадим пакет app, в котором будут лежать наши исходники.
В нем у нас будет еще 3 разных пакета. Поэтому чтобы эти пакеты не создавались внутри друг-друга — предлагаю прямо сейчас создать в пакете app какой-нибудь класс (потом удалим). Теперь создадим в пакете app три разных пакета:
  1. entities — тут будут лежать наши сущности (сам класс, который будет описывать объекты пользователей)
  2. model — тут будет наша модель (об этом чуть позже)
  3. servlets — ну а тут будут наши сервлеты
После этого тот класс из пакета app можно спокойно удалять (если вы его, конечно, создавали).

В пакете servlets создадим два класса:
AddServlet — будет обрабатывать запросы, поступившие по адресу /add
ListServlet — будет обрабатывать запросы, поступившие по адресу /list

Подключение зависимостей в мавене.
Если вы, как и я, используете томкат версии 9.* — то он реализует спецификации Servlet версии 4.0 и JavaServer Pages версии 2.3. Об этом написано в официальной документации 9го томката в первом же абзаце во второй строке.
Это значит, что наш код, который мы напишем и отправим выполняться на томкат будет использовать указанные версии. Но нам бы хотелось иметь эти спецификации и в нашем проекте, чтоб наш код, который будет их использовать, хотя бы успешно компилировался :) А для этого нам надо их подгрузить к себе в проект. Вот тут-то и приходит на помощь мавен.
Общее правило такое: если вам надо подключить что-то к вашему проекту используя мавен
  • идете на сайт репозитория от мавена;
  • ищите там нужную вам библиотеку нужной версии;
  • получаете код зависимости, который надо вставить в ваш pom.xml
  • вставляете :)
Начнем.
Для начала подготовим помник. Где-то после записи /version, но до /project вставляете следующее:
<dependencies>

</dependencies>

Таким образом мы указали, что внутри этих «тегов» мы перечислим наши зависимости которые нужны для нашего проекта.
Теперь заходите на mvnrepository.com и там вверху будет поле поиска.
Начнем, пожалуй, с servlet (вбиваете в поиск). Первый же результат, с более 7 тысяч использований, нам и подходит. Помним, что нам нужна версия 4.0 (для 9го томката, для других версий томката возможно подойдут и более старые реализации). Это довольно свежая версия, поэтому использований не так уж и много, но нам нужна именно она. Откроется страница, откуда можно взять код этой зависимости для разнообразных менеджеров пакетов и даже можно просто скачать. Но поскольку мы хотим подключить ее мавеном — то и код выбираем на вкладке Maven. Копируем и вставляем в наш помник внутрь раздела с зависимостями.

Если в правом нижнем углу идеи вылезет уведомление где спросят хотим ли мы включить автоимпорт — соглашаемся :) Если случайно отказались — можно зайти в настройки и включить автоимпорт вручную: Settings (Ctrl + Alt + S) -> Build, Execution, Deployment -> Maven -> Importing
Это позволит держать мавеновский помник и файлы настройки идеи для этого проекта синхронизированными.

Теперь по тому же принципу найдем и подключим JavaServer Pages версии 2.3 (в поиске вбиваете jsp просто).

Ну и раз уж мы взялись за мавен — сразу ему скажем, что у нас исходники будут по синтаксису 8й джавы и что компилить их надо в байткод той же версии.

В общем, после всех этих манипуляций ваш pom.xml будет выглядеть примерно вот так:
<?xml version="1.0" encoding="UTF-8"?>
<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>ru.javarush.info.fatfaggy</groupId>
    <artifactId>my-super-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compile.source>1.8</maven.compile.source>
        <maven.compile.target>1.8</maven.compile.target>
    </properties>

    <dependencies>
        <!-- Servlet API 4.0 for tomcat 9 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- JavaServer Pages API 2.3 for tomcat 9 -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>


Делаем из наших сервлетов настоящие сервлеты.
В данный момент те два наших сервлета — это просто самые обычные классы. У них нет никакой функциональности. Но теперь то мы подключили к нашему проекту Servlet API, а значит можем использовать классы оттуда.
Чтоб сделать наши сервлеты «настоящими» сервлетами — достаточно просто унаследовать их от класса HttpServlet.

Маппинг.
Теперь было бы неплохо как-то рассказать томкату, что мы хотим чтоб запросы с адреса /add обрабатывались нашим сервлетом AddServlet, ну и соответственно запросы по адресу /list обрабатывались сервлетом ListServlet. Именно этот процесс и называется маппингом(разметкой).
Делается это в файле web.xml.
Принцип такой:
  • сначала описываем сервлет (даем какое-то имя и указываем путь к самому классу)
  • потом привязываем этот сервлет к конкретному адресу (указываем имя сервлета, которое мы ему только-что дали и указываем адрес, запросы с которого стоит отправлять на этот сервлет)
Описываем сервлет:
<servlet>
    <servlet-name>add</servlet-name>
    <servlet-class>app.servlets.AddServlet</servlet-class>
</servlet>


Теперь привязываем его к адресу:
<servlet-mapping>
    <servlet-name>add</servlet-name>
    <url-pattern>/add</url-pattern>
</servlet-mapping>


Как видно, servlet-name в обоих случаях одинаковое. Благодаря этому томкат будет знать, что если пришел запрос на адрес /add — то надо его передать в сервлет app.servlets.AddServlet.

Со вторым сервлетом проделываем то же самое. В итоге, имеем примерно такой web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- add servlet -->
    <servlet>
        <servlet-name>add</servlet-name>
        <servlet-class>app.servlets.AddServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>add</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>

    <!-- list servlet -->
    <servlet>
        <servlet-name>list</servlet-name>
        <servlet-class>app.servlets.ListServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>list</servlet-name>
        <url-pattern>/list</url-pattern>
    </servlet-mapping>
</web-app>

Кстати, мы тут не создали маппинг для главной страницы (по адресу /). Дело в том, что в данном случае он нам и не нужен. Наша главная страница — это простой html файл, который просто отображает две кнопки. На нем нет динамического контента поэтому нам и нет смысла заводить под него отдельный сервлет, на который будут передаваться запросы с адреса /, и который не будет ничего делать, а будет просто передавать выполнение на какую-нибудь jsp (которую тоже пришлось бы завести), которая и рисовала бы нам две кнопки. Нам это все не нужно, нас устраивает статичный ресурс. Томкат когда получит запрос он проверит, что нет ни одного сервлета, который смог бы обработать запрос по такому адресу, а потом увидит что по этому адресу собственно лежит уже готовый html файл, который он успешно и отдаст.

Можем запустить наше приложение снова (перезапустить сервер или задеплоить повторно, как вам больше хочется) и убедиться, что главная страничка отрисовывается, ничего не сломалось, когда нажимаем на кнопки — то переходы происходят, но пока тоже пишется ошибка. Кстати, если до этого у нас была ошибка 404, то теперь 405. Значит маппинг сработал, сервлеты нашлись, да вот только не нашлось в них подходящих методов чтоб обработать запрос.

Что вообще происходит «под капотом». Краткое лирическое отступление.
Как вообще работает наше приложение в томкате? Что там вообще происходит? И где метод main()?
Как только вы вбиваете в браузере localhost:8080 и переходите по этому адресу — ваш браузер отправляет на этот адрес запрос по протоколу http. Надеюсь вы уже знаете, что запросы могут быть разных «типов», самый популярные — GET и POST. На каждый запрос должен быть ответ. GET запрос ожидает, что в ответ ему отдадут готовый html код, который вернется в браузер, а браузер уже этот код красиво заменит на всякие буковки, кнопочки, формочки. POST запрос — чуть интересней, так как он с собой еще несет некую информацию. Например, в форме регистрации или авторизации пользователя вы ввели свои данные и нажали «отправить» — в этот момент полетел на сервер POST запрос с вашей личной информацией внутри. Сервер эту информацию принял, обработал и вернул какой-нибудь ответ (например html страничку с вашим профилем). Принципиальное отличие между ними, что GET запросы предназначены только для получения данных с сервера, а POST запросы несут с собой какую-то информацию и данные на сервере могут измениться (например, когда вы заливаете свою фотку на сервер она полетит в POST запросе и сервер добавит ее в базу данных, то-есть произойдет какое-то изменение).
Теперь вернемся к томкату.
Когда он получает от клиента какой-то запрос он смотрит на адрес. Ищет по своим данным есть ли подходящий сервлет, который бы обрабатывал запросы по такому адресу (ну или готовый ресурс, который можно прям сразу и вернуть). Если он не нашел что вернуть — он кидает в ответ не html-страничку, а 404 ответ.
Если же он нашел подходящий сервлет, который «сидит» на этом адресе — он смотрит какой тип запроса он получил (GET, POST, или какой-то другой), а потом спрашивает у сервлета есть ли у него метод, который умел бы обрабатывать такой тип запросов. Если сервлет говорит, что не умеет обрабатывать такой тип — тогда томкат кидает клиенту в ответ 405й код. Что и произошло только-что у нас.
Но если же нашелся и подходящий сервлет, и у него есть подходящий метод — тогда томкат создает объект этого сервлета, запускает этот сервлет в новом треде (thread), что позволяет сервлету работать в отдельном потоке, а томкат продолжает работать и дальше в своем, принимать и отправлять запросы. Кроме того, томкат еще создает два объекта: один типа HttpServletRequest (коротко я его буду называть дальше риквест или запрос), а второй типа HttpServletResponse (буду называть респонс/ответ). В первый объект она помещает все данные, что ему пришли в запросе от клиента, таким образом из этого объекта все те данные можно будет вытащить.
Ну и после всего этого передает два эти объекта в подходящий метод того сервлета, который запущен в отдельном потоке. Как только сервлет закончит работу и у него будет готов ответ, который надо отправить клиенту — он просто поднимает флаг томкату, мол «я закончил, все готово», томкат берет ответ и отправляет его клиенту.
Это позволяет томкату не отвлекаясь принимать запросы и отправлять ответы, а всю работу делают сервлеты, которые крутятся в отдельных потоках. Соответственно, когда мы пишем код сервлета — мы и определяем ту работу, которая будет выполняться.
И да, можете считать, что метод main() находится в самом томкате (да, он написан на java), и когда мы «запускаем» томкат — запускается метод main().

Ловим сервлетами методы GET и отправляем простейшие ответы.
В данный момент, в наших сервлетах нет подходящих методов (GET), поэтому томкат нам возвращает 405ю ошибку. Сделаем их!
В классе HttpServlet, от которого мы унаследовали наши сервлеты, определены разные методы.
Для того, чтобы задать какой-то код для методов — мы просто переопредеяем их.
В данном случае нам надо переопределить метод doGet() в обоих сервлетах.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}

Как видим, этот метод принимает два обьекта: req (запрос) и resp (ответ). Это те два объекта, которые создаст и наполнит нам томкат, когда вызовет этот метод в этом сервлете.
Для начала давайте сделаем простейшие ответы. Для этого возьмем объект resp и получим из него объект PrintWriter-а, которым можно составлять ответы. Ну и при помощи него выведем какую-нибудь простую строку.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    PrintWriter writer = resp.getWriter();
    writer.println("Method GET from AddServlet");
}

Что-то подобное сделаем и в сервлете ListServlet.
И запустим наш сервер снова.
Как видим, все работает! При нажатии на кнопки открываются странички с тем текстом, который мы «записали» PrintWriter-ом.
Вот только те наши jsp, которые мы подготовили для формирования страничек с ответами никак не используются. Это потому, что выполнение до них просто не доходит. Сервелет сам у нас сейчас формирует ответ и заканчивает работу, сигнализируя томкату, что у него готов ответ клиенту. Томкат же просто берет этот ответ и отправляет его назад клиенту.

Передаем управление из сервлетов в jsp.
Изменим код наших методов таким образом:
  • получаем из объекта запроса объект диспетчера запросов, куда передаем адрес jsp странички, которой мы хотим передать управление
  • используя полученный объект — передаем управление в указанную jsp страницу, и не забываем вложить туда те объекты запроса и ответа, которые мы получили от томката.

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


Можно в теле jsp страниц (внутри тега body) чего-нибудь написать, чтоб мы могли четко видеть, какая из страниц отображается.
После чего перезапускаем сервер и проверяем.
Кнопочки на главной странице нажимаются, странички открываются, а значит запросы в сервлеты передаются, после чего управление передается в jsp страницы, которые уже и отрисовываются.

Пришло время заняться самим функционалом нашего приложения.

Продолжение статьи. Там же можете оставить ваши комментарии.

Комментариев нет

Автор топика запретил добавлять комментарии