В прошлой статье Создание сервлетов для чайников. Пошаговое руководство я описал, как создать сервлеты и попытался объяснить, как это работает. В этой статье мы научим наши сервлеты работать с UI через HTTP-запросы GET и POST.

На самом деле, этих запросов намного больше, но для чистоты эксперимента мы разберём только два основных.

  • GET — обрабатывает адресную строку, которая получается при вызове сервлета. Например, site.com/example?action=test&id=10&admin=true
  • POST — обрабатывает загруженный контент (картинку, файл, строки, объектные данные), в общем, всё, что можно передать через HTTP

В Java-сервлете запросы POST и GET обрабатывают соответствующие методы doPost(); и doGet().

Для чистоты объяснения принципов обработки запросов мы создадим:

  • простой объект класса Bot, который будет иметь имя, серийник и id
  • страницу, на которую мы будем выводить данные объекта
  • страницу, на которой мы будем менять данные объекта
  • сервлет, который будет обрабатывать соответствующие запросы

Создаём синглтон-класс Bot с тремя полями, а также, геттерами, сеттерами и перегруженным toString():

/**
 * Синглтон-класс Bot.
 * Для чистоты эксперимента это будет синглтон.
 * 
 * Created by promoscow on 26.07.17.
 */
public class Bot {
    private Integer id;
    private String name;
    private String serial;

    private static Bot ourInstance = new Bot();
    public static Bot getInstance() {
        return ourInstance;
    }

    private Bot() {
    }

    public Bot(String name, String serial, Integer id) {
        this.name = name;
        this.serial = serial;
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getSerial() {
        return serial;
    }

    public void setSerial(String serial) {
        this.serial = serial;
    }

    @Override
    public String toString() {
        return "Bot{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", serial=" + serial +
                '}';
    }
}

Пометим сервлет в xml-документе web.xml:

<servlet>
        <servlet-name>botServlet</servlet-name>
        <servlet-class>web.BotServlet</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>botServlet</servlet-name>
        <url-pattern>/bot</url-pattern>
    </servlet-mapping>

Пишем вот такую страницу bot.jsp (пока просто копируем, потом разберём):

<%--
  Created by IntelliJ IDEA.
  User: promoscow
  Date: 26.07.17
  Time: 9:28
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Bot</title>
</head>
<body>
<section>
    <h3>Bot info</h3>
    <jsp:useBean id="bot" scope="request" type="ru.javawebinar.topjava.model.Bot"/>
    <tr>
        <td>ID: ${bot.id} | Name: ${bot.name} | Serial number: ${bot.serial}</td>
        <td><a href="bot?action=update">Update</a></td>
    </tr>
</section>
</body>
</html>

Эта страница выводит нам данные бота.

Строчка

<jsp:useBean id="bot" scope="request" type="ru.javawebinar.topjava.model.Bot"/>

обрабатывает аргумент «bot», при чём мы даём понять, что это объект класса Bot. ${bot.name} и прочие подобные — это переменные, которые мы берём из полученного в аргументе объекта (это будет в сервлете).

Отметим, что ссылка Update переводит на адрес bot?action=update содержит данные для GET-запроса.

Также, у нас будет страница, которая отправляет изменённые данные бота (update.jsp):

<%--
  Created by IntelliJ IDEA.
  User: promoscow
  Date: 01.08.17
  Time: 13:43
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Update</title>
</head>
<body>
<section>
    <jsp:useBean id="bot" scope="request" type="ru.javawebinar.topjava.model.Bot"/>
    <form method="post" action="bot?action=submit">
        <dl>
            <dt>ID: </dt>
            <dd><input type="number" name="id" value="${bot.id}" placeholder="${bot.id}" /></dd>
        </dl>
        <dl>
            <dt>Name: </dt>
            <dd><input type="text" name="name" value="${bot.name}" placeholder="${bot.name}" /></dd>
        </dl>
        <dl>
            <dt>Serial number: </dt>
            <dd><input type="number" name="serial" value="${bot.serial}" placeholder="${bot.serial}" /></dd>
        </dl>
        <button type="submit">Save</button>
    </form>
</section>
</body>
</html>

Мы также получаем данные бота и вставляем их в поля, меняем их и отправляем изменённые данные в POST-запросе. Отметим, что POST-запрос инициируется через отправку формы

<form method="post" action="bot?action=submit">

где method=«post» означает, что данные формы попадут в POST, а action=«bot?action=submit» означает, что после отправки формы произойдёт переход по адресу bot?action=submit

Разберём для наглядности одно поле формы:

<input type="number" name="id" value="${bot.id}" placeholder="${bot.id}" />

По нажатию кнопки Save все введённые данные будут отправлены в метод doPost() нашего сервлета. В данной строчке, мы задаём новый id.

Имя атрибута формы: «id» (name=«id»), значение, которое мы передадим — поле id объекта bot (${bot.id}), также, мы вносим в поле имеющееся значение, полученное в атрибуте «bot» (placeholder="${bot.id}).

Поскольку задачей этой статьи является описание работы POST- и GET-запросов, я объясняю смысл кода строк вскользь. Более подробно атрибуты страниц можно изучить в Справочнике по HTML.

Ну и давайте добавим самую примитивную страницу index.html, содержащую единственную ссылку на страницу bot:

<html>
<head>
    <meta charset="UTF-8">
    <title>Bot Test Servlet Page</title>
</head>
<body>
<ul>
    <li><a href="bot">Bot</a></li>
</ul>
</body>
</html>

Мы закончили с jsp / html и можем перейти, наконец, к моему любимому Java-коду.

BotServlet.java:

import model.Bot;

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

/**
 * Bot Servlet class.
 *
 * Created by promoscow on 26.07.17.
 */
public class BotServlet extends HttpServlet {

    private Bot bot;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init();

        bot = new Bot("Amigo", "228274635", 1);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Enter doGet");

        String action = request.getParameter("action");
        request.setAttribute("bot", bot);
        switch (action == null ? "info" : action) {
            case "update":
                request.getRequestDispatcher("/update.jsp").forward(request, response);
                break;
            case "info":
            default:
                request.getRequestDispatcher("/bot.jsp").forward(request, response);
                break;
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Enter doPost");

        request.setCharacterEncoding("UTF-8");
        String action = request.getParameter("action");

        if ("submit".equals(action)) {
            bot.setId(Integer.parseInt(request.getParameter("id")));
            bot.setName(request.getParameter("name"));
            bot.setSerial(request.getParameter("serial"));
        }

        request.setAttribute("bot", bot);
        request.getRequestDispatcher("/bot.jsp").forward(request, response);
    }
}

Сервлет содержит 3 метода: init(), doGet() и doPost().

init() — вызывается при первой инициализации сервлета и единожды исполняет написанный в нём код. В нашем случае, создаётся экземпляр класса Bot.

doGet() — обрабатывает запрос GET.

doPost() — обрабатывает запрос POST.

Теперь мы по цепочке пройдём логику выполнения приложения и разберём, что как работает.

Итак, загружаем проект в Tomcat. Перед нами — одинокая ссылка «Bot». Нажимаем её.

Мы переходим в сервлет BotServlet. Метод doGet() исполняется всегда (ведь адресная строка есть всегда), метод doPost() исполняется только по запросу (у нас он есть — <form method=«post»...

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //просто выводим в консоль, что мы заходили в метод doGet
        System.out.println("Enter doGet");

        //здесь мы уже начинаем работать с адресной строкой. в ссылке <i>"bot?action=update"</i> (bot.jsp, 20 строчка) после вопросительного знака идут пары ключ-значение, разделённые знаком &. у нас есть пара action=update. Вызываем значение (параметр) по ключу action, у нас это update и заносим результат в String.
        String action = request.getParameter("action");

        //загодя вносим в запрос атрибут, при исполнении сервлета он будет отправлен в целевую строку. атрибут вносится вместе с ключом. в нашем случае, мы вносим объект bot со всеми его полями как атрибут и задаём ключ "bot", по которому мы потом вызовем данные объекта в jsp-странице
        request.setAttribute("bot", bot);

        switch (action == null ? "info" : action) {
            //если параметр имеет значение update (action=update), мы отправляемся на строку /update.jsp, где будем изменять данные бота (и отправляем туда атрибут bot)
            case "update":
                request.getRequestDispatcher("/update.jsp").forward(request, response);
                break;
            //если параметр пустой (action == null ? "info"), отправляемся на страницу bot.jsp, где мы увидим данные бота (и по дефолту тоже)
            case "info":
            default:
                request.getRequestDispatcher("/bot.jsp").forward(request, response);
                break;
        }
    }

Как видите, метод doGet() работает с адресной строкой, извлекает из неё атрибуты и обрабатывает их. Инструментарий метода doGet() велик, например, в строке может содержаться ?action=update&id=23847&admin=true (к примеру), мы можем извлечь id, отправить на доработку робота с полем id, равным 23847 и сделать его админом.

Поскольку в index.html атрибут адресной строки по ссылке имеет значение update, мы исполняем эту часть кода doGet():

request.setAttribute("bot", bot);
request.getRequestDispatcher("/update.jsp").forward(request, response);

— добавляем атрибут с объектом bot и ключом «bot» и отправляем всё это на страницу /update.jsp

В странице update.jsp мы вносим новые значения в формы, соответствующие полям класса Bot и отправляем всё по адресу «bot?action=submit». Повторю — в этой статье не ставится задача разбирать тэги jsp/html, для этого есть Справочник по HTML.

Итак, мы нажимаем кнопку «Save». Поскольку форма содержит method=«post» action=«bot?action=submit», мы обрабатываем данные, полученные в форме, в методе doPost(). В этом методе можно как обрабатывать входящие данные, так и извлекать атрибуты адресной строки.

@Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Enter doPost");

        //правилом хорошего кода будет задать кодировку UTF-8 — в метод могут приходить параметры, скажем, на кириллице (опять же, мы можем дать нашему боту кириллическое имя)
        request.setCharacterEncoding("UTF-8");

        //извлекаем значение параметра action и сохраняем в Stiring, как мы это делали в методе doGet()
        String action = request.getParameter("action");

        //если action=submit, назначаем нашему боту новые значения, которые мы получили в метод. это делается также, извлекая параметры. у каждого параметра из формы есть своё имя, по этому имени мы извлекаем значение (например, в разбираемой выше строчке формы update.jsp name="id" value=${bot.id} мы задаём нашему боту новый id, извлекая его в строчке bot.setId(Integer.parseInt(request.getParameter("id")));
        if ("submit".equals(action)) {
            bot.setId(Integer.parseInt(request.getParameter("id")));
            bot.setName(request.getParameter("name"));
            bot.setSerial(request.getParameter("serial"));
        }

        //опять запихиваем объект bot в атрибут и возвращаемся на страницу /bot.jsp, где наблюдаем изменения
        request.setAttribute("bot", bot);
        request.getRequestDispatcher("/bot.jsp").forward(request, response);
    }

Вот и вся статья. Мы отработали в сервлете запросы POST и GET через соответствующие методы doPost() и doGet().

Целью статьи является создание понимания у начинающих разработчиков JavaEE, как работают запросы в сервлетах и по какой логике выстраивается работа с этими запросами в Java-коде.

Это вторая моя статья на тему сервлетов, запуск первого сервлета мы разбирали в статье Создание сервлетов для чайников. Пошаговое руководство
Поделиться с друзьями
-->

Комментарии (6)


  1. StanislavL
    02.08.2017 09:32
    +1

    Зачем в сервлете делать state в виде хранимого bot? Зачем хардкодить строки вместо выноса их в константы?

    Может если учить чайников, так хорошему?


  1. avost
    02.08.2017 10:36
    +2

    Я бы посоветовал автору немедленно удалить статью. После чего заняться внимательным изучением servlet lifecicle и методами работы с разделяемыми состояниями в многопоточном окружении.


    1. grossws
      02.08.2017 12:49

      Кому нужна синхронизация в современном мире? Нужно же хренак-хренак-и-в-продакшн. /sarcasm


      Я вообще опасаюсь людей, которые не в состоянии прочитать спеку servlet api, там ничего страшного нет. Есть некоторое количество устаревшего барахла (типа тех же логов), но даже они не столь страшны, как логгирование в stdout.


      Самое, пожалуй, печальное, что я таких умелых вертетелей сервлетов и их код немного повидал. И даже после тыканья мордой зачастую ничего не меняется.


      1. avost
        02.08.2017 15:44

        В прадакшн ладно — это проблемы его личные и его работодателя. Но они же считают себя вправе других учить… :( За такое, вообще-то, канделябром бьют.


  1. AlexZaharow
    02.08.2017 12:44

    Статья весьма слабовата, т.к. её нельзя использовать как руководство. Если я уже знаком с сервлетами, то эта статья мне просто освежит память, но как руководство по нему нельзя ничего сделать не только чайнику, но и более опытному программисту, т.к. требуется выбрать и настроить контейнер сервлетов, о чем в статье ни слова. Надо было в привязке к контейнеру написать хотя бы.


  1. izzholtik
    02.08.2017 16:32
    +2

    О, на этой неделе ещё не было.

    что-то в последнее время зачастили кривые helloworld'ы по java web.