Всем привет! В этой статье вы познакомитесь с таким базовым понятием веб-разработки, как сервлеты, и сможете написать простое приложение с их использованием.
Ваше первое приложение с использованием Java-сервлетов - 1
Во избежание лишних действий, мы не будем начинать с нуля, и продолжим работу над нашим приложением из моей предыдущей статьи про Hibernate. Однако, поскольку мы только начинаем знакомиться с сервлетами, я убрал из приложения все, что связано с классом Auto, и оставил только класс User и действия с ним. Структура проекта будет выглядеть так:
Ваше первое приложение с использованием Java-сервлетов - 2
Итак, сервлеты! Википедия гласит: "Сервлет является интерфейсом Java, реализация которого расширяет функциональные возможности сервера. Сервлет взаимодействует с клиентами посредством принципа запрос-ответ." И это действительно так. Здесь мы впервые сталкиваемся с понятием "клиент-серверная архитектура приложения". Суть ее вполне проста и умещается на одной картинке (взята отсюда).
Ваше первое приложение с использованием Java-сервлетов - 3
Клиент обращается к серверу посредством отправки 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 зависимости:
  1. Сама библиотека javax.servlet-api;
  2. Библиотека тегов JSTL. Она нужна будет для создания клиентской стороны, а именно страниц JSP;
  3. Spring-WebMVC. Нам понадобится один класс Spring'a, о котором мы поговорим чуть позднее.
Управление работой сервлетов осуществляет контейнер сервлетов. В нашем случае мы будем использовать Apache Tomcat. Довольно популярная штука, и, вероятно, вы уже о нем слышали :) Жизненный цикл сервлета состоит из следующих шагов:
  1. В случае отсутствия сервлета в контейнере.
    • Класс сервлета загружается контейнером.
    • Контейнер создает экземпляр класса сервлета.
    • Контейнер вызывает метод init(). Этот метод инициализирует сервлет и вызывается в первую очередь, до того, как сервлет сможет обслуживать запросы. За весь жизненный цикл метод init() вызывается только один раз.

  2. Обслуживание клиентского запроса. Каждый запрос обрабатывается в своем отдельном потоке. Контейнер вызывает метод service() для каждого запроса. Этот метод определяет тип пришедшего запроса и распределяет его в соответствующий этому типу метод для обработки запроса. Разработчик сервлета должен предоставить реализацию для этих методов. Если поступил запрос, метод для которого не реализован, вызывается метод родительского класса и обычно завершается возвращением ошибки инициатору запроса.

  3. В случае если контейнеру необходимо удалить сервлет, он вызывает метод 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",
Ваше первое приложение с использованием Java-сервлетов - 4
создаем новую конфигурацию
Ваше первое приложение с использованием Java-сервлетов - 5
и выбираем Tomcat Server Local. Во вкладке Application Server указываем путь к папке, где лежит Tomcat
Ваше первое приложение с использованием Java-сервлетов - 6
Далее переходим на вкладку Deployment.
Ваше первое приложение с использованием Java-сервлетов - 7
Здесь осуществляется настройка развертывания нашего приложения на локальном сервере. Наживаем "+", выбираем "Artifact"-> ИмяВашегоПроекта:war (мы будем собирать приложение в war-файл).
Ваше первое приложение с использованием Java-сервлетов - 8
Вот, в общем-то, и все! На странице "Server" вы можете увидеть, что наже приложение будет работать по адресу "http://localhost:8080/". Сохраните эту конфигурацию и как-нибудь назовите (у меня название конфига - "Tommy"). Далее, на вкладке Maven в IDEA (с правой стороны) воспользуемся war-плагином для сборки нашего проекта в war-файл (Plugins -> war -> war:war).
Ваше первое приложение с использованием Java-сервлетов - 9
После того, как сборка была осуществлена, запускаем приложение и ждем.
Ваше первое приложение с использованием Java-сервлетов - 10
Успех! Стартовая страница запустилась. Теперь нажмем на кнопку "Начать работу с базой данных". Наша JSP страница index.jsp сформирует GET-запрос, который будет обработан сервером. Сервер сформирует ответ и вернет его нам в виде списка всех существующих пользователей (если они, конечно, есть в БД). А вот и они!
Ваше первое приложение с использованием Java-сервлетов - 11
Попробуем удалить одного из пользователей:
Ваше первое приложение с использованием Java-сервлетов - 12
Тоже работает! Вот мы и написали наше первое приложение с использованием сервлетов. Как видите, все оказалось не так уж и сложно :) В качестве домашнего задания Вы можете, например, вернуть в приложение функционал работы с автомобилями из предыдущей статьи. Т.е. создать для автомобилей отдельный сервлет и jsp-страницы и научить наше приложение выводить список автомобилей пользователя, добавлять ему новые машины, а также редактировать и удалять их. P.S. Сервлеты и JSP - технологии достаточно древние, и на просторах интернета часто можно встретить комментарии в духе "кому нужно это старье?". Ответ достаточно прост - оно нужно прежде всего тем, кто будет работать на настоящих проектах, в которых вполне возможно куча написанного с их использованием кода. И перепиливать "старье" на что-то новое, не понимая как оно работает - то еще удовольствие :) Для более основательного изучения темы JSP и сервлетов вы можете использовать книгу "Head First Servlets and JSP" (только на английском языке). Ее писали те же авторы, что и знаменитую супер-книгу "Head First Java", что для многих может быть гарантом качества:) Надеюсь, эта статья была полезна для читателей! Если Вы хотели бы увидеть новые статьи - не забудьте поддержать автора в конкурсе, поставив "Нравится". А лучше - "Очень нравится" :) Спасибо за внимание, и успехов в обучении!