В прошлой статье Создание сервлетов для чайников. Пошаговое руководство я описал, как создать сервлеты и попытался объяснить, как это работает. В этой статье мы научим наши сервлеты работать с 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)
avost
02.08.2017 10:36+2Я бы посоветовал автору немедленно удалить статью. После чего заняться внимательным изучением servlet lifecicle и методами работы с разделяемыми состояниями в многопоточном окружении.
grossws
02.08.2017 12:49Кому нужна синхронизация в современном мире? Нужно же хренак-хренак-и-в-продакшн. /sarcasm
Я вообще опасаюсь людей, которые не в состоянии прочитать спеку servlet api, там ничего страшного нет. Есть некоторое количество устаревшего барахла (типа тех же логов), но даже они не столь страшны, как логгирование в stdout.
Самое, пожалуй, печальное, что я таких умелых вертетелей сервлетов и их код немного повидал. И даже после тыканья мордой зачастую ничего не меняется.
avost
02.08.2017 15:44В прадакшн ладно — это проблемы его личные и его работодателя. Но они же считают себя вправе других учить… :( За такое, вообще-то, канделябром бьют.
AlexZaharow
02.08.2017 12:44Статья весьма слабовата, т.к. её нельзя использовать как руководство. Если я уже знаком с сервлетами, то эта статья мне просто освежит память, но как руководство по нему нельзя ничего сделать не только чайнику, но и более опытному программисту, т.к. требуется выбрать и настроить контейнер сервлетов, о чем в статье ни слова. Надо было в привязке к контейнеру написать хотя бы.
izzholtik
02.08.2017 16:32+2О, на этой неделе ещё не было.
что-то в последнее время зачастили кривые helloworld'ы по java web.
StanislavL
Зачем в сервлете делать state в виде хранимого bot? Зачем хардкодить строки вместо выноса их в константы?
Может если учить чайников, так хорошему?