Всем привет!
В этой статье вы познакомитесь с таким базовым понятием веб-разработки, как сервлеты, и сможете написать простое приложение с их использованием.
Во избежание лишних действий, мы не будем начинать с нуля, и продолжим работу над нашим приложением из моей предыдущей статьи про Hibernate.
Однако, поскольку мы только начинаем знакомиться с сервлетами, я убрал из приложения все, что связано с классом Auto, и оставил только класс User и действия с ним.
Структура проекта будет выглядеть так:
Итак, сервлеты!
Википедия гласит:
"Сервлет является интерфейсом Java, реализация которого расширяет функциональные возможности сервера. Сервлет взаимодействует с клиентами посредством принципа запрос-ответ."
И это действительно так. Здесь мы впервые сталкиваемся с понятием "клиент-серверная архитектура приложения". Суть ее вполне проста и умещается на одной картинке (взята отсюда).
Клиент обращается к серверу посредством отправки HTTP-запроса. Сервер формирует необходимые данные (например, получает их из базы данных) и возвращает клиенту.
Самый простой пример: в некоей социальной сети вы нажимаете на кнопку "Друзья" и отправляете таким образом запрос серверу. Сервер уточняет в базе данных список ваших друзей, и возвращает его вам (клиенту).
Список HTTP-запросов довольно велик, но если вы никогда с ними не сталкивались, то для лучшего понимания лучше прочитать о них, например, здесь.
Наша задача состоит в следующем:
Создать CRUD-приложение с использованием сервлетов. Приложение должно уметь создавать, изменять и удалять пользователей из базы данных, используя для этого сервлет, обрабатывающий HTTP-запросы.
Наше приложение из статьи про Hibernate уже умело делать это, однако оно управлялось прямо из Java-кода, точнее - из метода main().
Здесь же запросы будет отправлять именно клиент, то есть Вы :)
Первое что нам нужно сделать, это добавить новые зависимости в наш файл 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>com.itis4</groupId>
<artifactId>UsersDaoProject</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</build>
<dependencies>
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212.jre7</version>
</dependency>
<!-- Hibernate 5.2.6 Final -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.6.Final</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
</dependencies>
</project>
Мы добавили 3 зависимости:
- Сама библиотека javax.servlet-api;
- Библиотека тегов JSTL. Она нужна будет для создания клиентской стороны, а именно страниц JSP;
- Spring-WebMVC. Нам понадобится один класс Spring'a, о котором мы поговорим чуть позднее.
- В случае отсутствия сервлета в контейнере.
- Класс сервлета загружается контейнером.
- Контейнер создает экземпляр класса сервлета.
- Контейнер вызывает метод init(). Этот метод инициализирует сервлет и вызывается в первую очередь, до того, как сервлет сможет обслуживать запросы. За весь жизненный цикл метод init() вызывается только один раз.
- Обслуживание клиентского запроса. Каждый запрос обрабатывается в своем отдельном потоке. Контейнер вызывает метод service() для каждого запроса. Этот метод определяет тип пришедшего запроса и распределяет его в соответствующий этому типу метод для обработки запроса. Разработчик сервлета должен предоставить реализацию для этих методов. Если поступил запрос, метод для которого не реализован, вызывается метод родительского класса и обычно завершается возвращением ошибки инициатору запроса.
- В случае если контейнеру необходимо удалить сервлет, он вызывает метод destroy(), который снимает сервлет из эксплуатации. Подобно методу init(), этот метод тоже вызывается единожды за весь цикл сервлета.
package servlets;
import models.User;
import services.UserService;
import javax.servlet.RequestDispatcher;
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;
import java.util.List;
public class UserSimpleServlet extends HttpServlet {
private UserService service = new UserService();
public void init(ServletConfig servletConfig) {
try {
super.init(servletConfig);
} catch (ServletException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<User> users = service.findAllUsers();
req.setAttribute("users", users);
RequestDispatcher dispatcher = req.getRequestDispatcher("/showUsers.jsp");
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
int age = Integer.parseInt(req.getParameter("age"));
User user = new User(name, age);
service.saveUser(user);
resp.sendRedirect("/users");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int id = Integer.parseInt(req.getParameter("id"));
User user = service.findUser(id);
user.setName(req.getParameter("name"));
user.setAge(Integer.parseInt(req.getParameter("age")));
service.updateUser(user);
resp.sendRedirect("/users");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
int id = Integer.parseInt(req.getParameter("id"));
service.deleteUser(service.findUser(id));
resp.sendRedirect("/users");
}
}
Как видите, он содержит в себе метод init(), о котором писалось выше, и реализует 4 метода, совпадающих с четырьмя HTTP-запросами - doGet(), doPost(), doPut() и doDelete(). Каждый из них позволит нам, соответственно, получать, создавать, редактировать и удалять пользоваталей.
Методы принимают на вход объекты классов javax.servlet.http.HttpServletRequest и javax.servlet.http.HttpServletResponse - то есть, запрос, отправляемый на сервер, и ответ, который получит клиент.
Внутри методов выполняются необходимые методы класса UserService, формируется ответ для клиента, после чего осуществляется перенаправление на адрес /users.
Например, в методе doGet() мы получаем список всех пользователей. Далее мы создаем объект класса RequestDispatcher, который позволяет включать объекты в Http-запрос, а также перенаправлять его к определенному ресурсу (например, к клиентской JSP-странице).
В методе doPut() (обновление данных пользователя) мы обрабатываем HTTP-запрос, вытаскиваем из него параметры id, name и age, находим юзера с указанным id, присваиваем ему те name и age, которые пришли вместе с ним в запросе, и возвращаемся на страницу /users.
Однако, чтобы все эти методы корректно работали, нам необходимо осуществить настройку сервлета. Для этого мы используем файл web.xml в папке WEB-INF.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
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">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>UserSimpleServlet</servlet-name>
<servlet-class>servlets.UserSimpleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserSimpleServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<servlet-name>UserSimpleServlet</servlet-name>
</filter-mapping>
</web-app>
Все теги в этом файле, в принципе, интуитивно понятны, но давайте пройдемся по ним последовательно.
<welcome-file-list> - указана стартовая JSP-страница, которая будет открываться первой при запуске приложения. В нашем случае это страница index.jsp.
<servlet> - регистрация нашего класса UserSimpleServlet в качестве сервлета.
<servlet-mapping> - очень важный тег. Он определяет URL-лы, которые будут обрабатываться сервлетом. В нашем случае это вообще все URL'ы, поэтому мы указываем просто "/".
Но, например, если бы у нас было приложение с юзерами и их машинами, то можно было бы создать второй сервлет - SimpleAutoServlet. Тогда маппингом для юзер-сервлета было бы "/users" (то есть запросы, касающиеся обработки пользователей), а для авто-сервлета - "/autos".
И, наконец, <filter>.
Он определяет внутри себя объект класса org.springframework.web.filter.HiddenHttpMethodFilter.
Статья не касается Spring'a, поэтому не буду рассказывать о нем в подробностях. Скажу лишь, что к нашему приложению он прикручен только в качестве дополнительной фичи.
Дело в том, что для создания клиентской стороны мы будем использовать JSP-страницы. Наши данные будут отображаться на странице как таблица со списком пользователей. Внутри JSP-страниц будут использоваться HTML-теги <form/>. А для отправки данных из <form/> могут быть использованы только HTTP-запросы GET и POST. То есть для для всех трех операций - обновления, удаления и создания пользователя - нам пришлось бы использовать только POST-запросы. Использование PUT и DELETE запросов было бы нам недоступно.
И, в принципе, это вполне нормально и несложно реализовать, но класс HiddenHttpMethodFilter позволяет нам их использовать. Таким образом, читателю будет более явно будет видна разница между операциями в приложении.
Наконец, переходим к клиентской части.
Она представлена пятью JSP-страницами.
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Здравствуйте!</title>
</head>
<body>
Если вы хотите начать работу с базой данных пользователей - <br>
нажмите кнопку ниже:
<form action = "users" method="get">
<input type="submit" value="Начать работу с базой данных">
</form>
</body>
</html>
addUser.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Добавить нового пользователя</title>
</head>
<body>
<form action = "/users" method="post">
<input required type="text" name="name" placeholder="Имя">
<input required type="text" name="age" placeholder="Возраст">
<input type="submit" value="Сохранить">
</form>
</body>
</html>
deleteUser.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Удалить пользователя</title>
</head>
<body>
Вы действительно хотите удалить пользователя ${param.id}?
&lform action="/users/${param.id}" method="post">
<input type="hidden" name="id" value="${param.id}">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="Удалить">
</form>
</body>
</html>
showUsers.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Список пользователей</title>
</head>
<body>
<table border="2">
<tr>
<td>ID</td>
<td>Имя</td>
<td>Возраст</td>
<td>Действия</td>
</tr>
<c:forEach items="${users}" var = "user">
<tr>
<td>${user.getId()}</td>
<td>${user.getName()}</td>
<td>${user.getAge()}</td>
<td>
<form action = "updateUser.jsp" method="post">
<input type="hidden" name="id" value="${user.getId()}">
<input type="hidden" name="name" value="${user.getName()}">
<input type="hidden" name="age" value="${user.getAge()}">
<input type="submit" value="Изменить" style="float:left">
</form>
<form action="deleteUser.jsp" method="post">
<input type="hidden" name="id" value="${user.getId()}">
<input type="submit" value="Удалить" style="float:left">
</form></td>
</tr>
</c:forEach>
</table>
<form action = "addUser.jsp">
<input type="submit" value="Добавить нового пользователя">
</form>
</body>
</html>
updateUser.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Изменить данные пользователя</title>
</head>
<body>
Редактировать пользователя
<form action="/users/${param.id}" method="post">
<input type="hidden" name = "id" value="${param.id}">
<input type="text" name="name" value="${param.name}" placeholder=${param.name}>
<input type="text" name="age" value="${param.age}" placeholder=${param.age}>
<input type="hidden" name="_method" value="put">
<input type="submit" value="Обновить">
</form>
</body>
</html>
Страница JSP (Java Server Page) содержит текст двух типов: статические исходные данные, которые могут быть оформлены в одном из текстовых форматов (HTML, SVG, WML, или XML), и JSP-элементы, которые конструируют динамическое содержимое.
Для понимания того, что такое JSP я позволю себе скопипастить кусочек очень хорошей статьи одного автора (отсюда).
"По сути JSP при первом обращении преобразуется в сервлет и работает уже как сервлет. Это очень важно понять. JSP НЕ ЯВЛЯЕТСЯ страницей наподобие HTML-страницы — начинающему программисту важно четко осознавать, что это еще один сервлет — просто его вывод не надо программировать. Его можно просто нарисовать. И в нужные места подставить данные. Но т.к. JSP-страница хоть как-то напоминает HTML, то дизайнеру явно будет проще. И я еще раз НАСТОЙЧИВО говорю начинающим — JSP является СЕРВЛЕТОМ. Ее подготовка со всеми данными происходит на сервере. Именно там подставляются все данные. А пользователю в браузер приходит уже готовая HTML-страница, на которой никаких признаков JAVA нет."
Вы можете сами убедиться в том, что JSP-страница действительно является сервлетом, ведь на каждой мз страниц указан метод, который необходимо выполнить. Например, на стартовой странице index.jsp указано, что при нажатии на кнопку "Начать работу с базой данных" будет выполнен method="get". На странице addUser.jsp, отвечающей за создание нового пользователя, при нажатии на кнопку сохранить, будет выполнен method="post".
Оставшуюся часть JSP составляет обычная статическая HTML-разметка, поэтому подробно останавливаться на них не будем - это тема отдельной статьи, которых немало на просторах Интернета.
Итак, мы создали наше приложение, осталось испытать его в деле!
Для этого нам понадобится упомянутый выше контейнер сервлетов Apache Tomcat. Скачать кота можно с официального сайта (я использую 8-ую версию).
Далее нам нужно создать в IDEA конфигурацию для запуска нашего приложения через Tomcat. Для этого открываем вкладку "Edit Configurations",
создаем новую конфигурацию
и выбираем Tomcat Server Local.
Во вкладке Application Server указываем путь к папке, где лежит Tomcat
Далее переходим на вкладку Deployment.
Здесь осуществляется настройка развертывания нашего приложения на локальном сервере.
Наживаем "+", выбираем "Artifact"-> ИмяВашегоПроекта:war (мы будем собирать приложение в war-файл).
Вот, в общем-то, и все! На странице "Server" вы можете увидеть, что наже приложение будет работать по адресу "http://localhost:8080/".
Сохраните эту конфигурацию и как-нибудь назовите (у меня название конфига - "Tommy").
Далее, на вкладке Maven в IDEA (с правой стороны) воспользуемся war-плагином для сборки нашего проекта в war-файл (Plugins -> war -> war:war).
После того, как сборка была осуществлена, запускаем приложение и ждем.
Успех! Стартовая страница запустилась.
Теперь нажмем на кнопку "Начать работу с базой данных". Наша JSP страница index.jsp сформирует GET-запрос, который будет обработан сервером. Сервер сформирует ответ и вернет его нам в виде списка всех существующих пользователей (если они, конечно, есть в БД).
А вот и они!
Попробуем удалить одного из пользователей:
Тоже работает!
Вот мы и написали наше первое приложение с использованием сервлетов.
Как видите, все оказалось не так уж и сложно :)
В качестве домашнего задания Вы можете, например, вернуть в приложение функционал работы с автомобилями из предыдущей статьи. Т.е. создать для автомобилей отдельный сервлет и jsp-страницы и научить наше приложение выводить список автомобилей пользователя, добавлять ему новые машины, а также редактировать и удалять их.
P.S. Сервлеты и JSP - технологии достаточно древние, и на просторах интернета часто можно встретить комментарии в духе "кому нужно это старье?".
Ответ достаточно прост - оно нужно прежде всего тем, кто будет работать на настоящих проектах, в которых вполне возможно куча написанного с их использованием кода. И перепиливать "старье" на что-то новое, не понимая как оно работает - то еще удовольствие :)
Для более основательного изучения темы JSP и сервлетов вы можете использовать книгу "Head First Servlets and JSP" (только на английском языке).
Ее писали те же авторы, что и знаменитую супер-книгу "Head First Java", что для многих может быть гарантом качества:)
Надеюсь, эта статья была полезна для читателей!
Если Вы хотели бы увидеть новые статьи - не забудьте поддержать автора в конкурсе, поставив "Нравится". А лучше - "Очень нравится" :)
Спасибо за внимание, и успехов в обучении!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ