Этот материал — часть цикла “Введение в Enterprise-разработку”. Предыдущие статьи: Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 1Ты уже умеешь писать Java-приложения, которые выводят текст на консоль, но еще толком не знаешь, как создать свое первое веб-приложение? Отлично, устраивайся поудобнее. В этой статье мы познакомимся с сервлетами и напишем приложение, которым ты сможешь похвастать перед друзьями, не отправляя им джарник и не заставляя их качать джаву. Напишем веб-приложение. Если ты еще не знаком с подходами, которые используются в веб-программировании, советую начать чтение с первой статьи цикла “Введение в Enterprise-разработку”.

Что такое сервлет

Для начала разберемся, что такое сервлет и почему ты так часто слышишь о нем. Java Servlet API — стандартизированный API, предназначенный для реализации на сервере и работе с клиентом по схеме запрос-ответ. Сервлет — это класс, который умеет получать запросы от клиента и возвращать ему ответы. Да, сервлеты в Java — именно те элементы, с помощью которых строится клиент-серверная архитектура. Если помнишь, о ней мы уже говорили в одной из статей цикла. Не будем ходить вокруг да около: давай сразу напишем немного кода.

Что нужно для создания веб-приложения

Для комфортной работы с сервлетами в Java тебе понадобится Intellij IDEA Ultimate Edition. Она платная, но можно активировать 30 дней пробного периода или же пользоваться early access версией — она всегда бесплатная. Также установи сервер нашего приложения — Apache Tomcat. Tomcat — это контейнер сервлетов: именно он обрабатывает входящие запросы извне и передает их нашему приложению. Скачать Tomcat можно по этой ссылке.

Создаем первое веб-приложение

Если все готово, создадим Maven-проект. Если ты не знаком с Мавеном, обрати внимание на предыдущую статью. Ну что, начнем!
  1. В pom.xml добавим зависимость javax.servlet-api и установим packaging war:

    <?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>org.example</groupId>
       <artifactId>servlets</artifactId>
       <version>1.0-SNAPSHOT</version>
       <packaging>war</packaging>
    
       <dependencies>
           <dependency>
               <groupId>javax.servlet</groupId>
               <artifactId>javax.servlet-api</artifactId>
               <version>4.0.1</version>
           </dependency>
       </dependencies>
    </project>

    Класс простого сервлета:

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebServlet("/hello")
    public class MainServlet extends HttpServlet {
    
       @Override
       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           resp.setContentType("text/html");
           PrintWriter printWriter = resp.getWriter();
           printWriter.write("Hello!");
           printWriter.close();
       }
    }
  2. Для запуска приложения нужно создать Tomcat-конфигурацию:

    Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 2 Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 3

  3. Далее указываем, какую версию Tomcat мы будем использовать, URL, по которому можно обращаться к серверу и порт. У тебя должно получиться примерно так:

    Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 4
  4. Осталось указать артефакт (собранный проект в jar-архив), который развернется в контейнере. Можно нажать кнопку Fix и выбрать war exploded: это значит, что после пересборки проекта артефакт будет автоматически помещаться в контейнер сервлетов. Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 5

  5. Application context по умолчанию установлен servlets_war_exploded, а это значит, что к приложению нужно обращаться по адресу: http://localhost:8080/servlets_war_exploded.

    Зачем нам лишний текст? Удалим ненужное. Теперь адрес приложения у нас такой: http://localhost:8080.

    Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 6

  6. Нажимаем ОК. Видим, что у нас появилась возможность запуска приложения:

    Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 7

    Теперь при запуске приложения должен открыться браузер и выдать 404-ю ошибку. Это логично, ведь по адресу http://localhost:8080/ должен находиться сервлет с мапингом “/”, а у нашего единственного сервлета мапинг "/hello".

  7. Обращаемся к нему по адресу http://localhost:8080/hello, и получаем ожидаемый ответ — строку “Hello”!

Часть 5. Сервлеты, Java servlet API. Пишем простое веб-приложение - 8Если все работает, давай разберем код. Чтобы из обычного класса сделать http-сервлет, его нужно унаследовать от класса HttpServlet. Над классом указываем аннотацию @WebServlet(), в которой привязываем (мапим) сервлет к конкретному пути (“/hello”). Эта аннотация появилась только в Java Servlet API 3.0, поэтому в интернете очень много примеров, где мапинг сервлетов происходит через XML-файл. Сейчас это не обязательно. Чтобы обрабатывать GET-запросы, переопределяем метод doGet(). Обрати внимание на аргументы метода — HttpServletRequest и HttpServletResponse. С объекта HttpServletRequest мы можем взять всю необходимую информацию о запросе, в HttpServletResponse можем записать наш ответ и установить необходимые заголовки.

Работа с параметрами и сессией

Усовершенствуем наш сервлет, чтобы он мог обрабатывать параметры запроса и работать с сессией:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")
public class MainServlet extends HttpServlet {

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       HttpSession session = req.getSession();
       Integer visitCounter = (Integer) session.getAttribute("visitCounter");
       if (visitCounter == null) {
           visitCounter = 1;
       } else {
           visitCounter++;
       }
       session.setAttribute("visitCounter", visitCounter);
       String username = req.getParameter("username");
       resp.setContentType("text/html");
       PrintWriter printWriter = resp.getWriter();
       if (username == null) {
           printWriter.write("Hello, Anonymous" + "<br>");
       } else {
           printWriter.write("Hello, " + username + "<br>");
       }
       printWriter.write("Page was visited " + visitCounter + " times.");
       printWriter.close();
   }
}
Сейчас сервлет работает с сессией, увеличивая счетчик visitCounter при каждом посещении страницы. Если атрибут visitCounter еще не создан (при первом посещении страницы), метод getAttribute() вернет null, поэтому нужно проводить проверку на null. То же касается и параметров запроса. Если пользователь не передал параметр username, его значение будет null. В таком случае поприветствуем пользователя как анонимного. Чтобы передать параметр в GET-запросе, используются path-variables, то есть нужно обратиться по ссылке http://localhost:8080/hello?username=Pavel. Подробней об http-запросах можно почитать в предыдущей статье цикла. Теперь у нашего приложения есть минимальная логика, но немного раздражает 404-я ошибка в root-пути. Чтобы исправить ее, создадим еще один сервлет и замапим его на начальную страницу @WebServlet("/"). Задача этого сервлета — перенаправлять запросы на путь “/hello”. Сделать это можно двумя способами: с помощью forward или redirect. Пожалуй, стоит разобраться, в чем между ними разница. forward — делегирует обработку запроса другому сервлету на сервере, клиент при этом не задействуется. Для этого в метод doGet() нового сервлета нужно добавить такой код:
getServletContext().getRequestDispatcher("/hello").forward(req, resp);
В этом коде мы обращаемся к контексту сервлетов, из него достаем диспетчер запросов нужного сервлета и просим его обработать конкретный запрос с указанными параметрами (req, resp). redirect — возвращает клиенту адрес, по которому нужно обратиться для обработки его запроса. Большинство браузеров переходит на переданную ссылку автоматически. Для реализации редиректа нужно добавить этот код:
resp.sendRedirect(req.getContextPath() + "/hello");
Мы в HttpServletResponse вызываем метод redirect() и передаем ему адрес, на который клиенту нужно обратиться. Важная деталь: http-параметры нужно также добавить в конце полного пути редиректа, что не очень удобно. В нашей ситуации предпочтительнее использовать forward, а бывает так, что лучше — redirect. Если будешь понимать разницу в их работе, не ошибешься с выбором. Код нового сервлета выглядит так:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/")
public class IndexServlet extends HttpServlet {

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        getServletContext().getRequestDispatcher("/hello").forward(req, resp);
       resp.sendRedirect(req.getContextPath() + "/hello");
   }
}

Итог

Твое первое веб-приложение готово. В следующей статье ты узнаешь, как развернуть его без использования Intellij IDEA. Мы написали приложение, которое обрабатывает только GET-запросы. Остальные http-методы обрабатываются аналогичным образом — переопределяя соответствующие методы родительского класса. Используя такие простые сервлеты, можно строить сложные многофункциональные веб-приложения. Конечно, используя большие фреймворки типа Spring это делать намного проще. Но если очень хочется вникнуть подробнее во все возможности сервлетов, можешь почитать официальную спецификацию. Часть 6. Контейнеры сервлетов Часть 7. Знакомство с паттерном MVC (Model-View-Controller) Часть 8. Пишем небольшое приложение на spring-boot