Сервлеты в Java: как устроены и как написать первый сервлет

Сервлеты в Java: как устроены и как написать первый сервлет Полезное

Сервлет — это Java-класс, который обрабатывает HTTP-запросы на стороне сервера и формирует ответ. Он работает внутри контейнера сервлетов (например, Apache Tomcat) и служит точкой входа веб-приложения: принимает запрос от браузера, выполняет логику и возвращает HTML, JSON или другой контент.

Ниже разберем, что описывает интерфейс Servlet, зачем нужен класс HttpServlet, как устроен жизненный цикл (init, service, destroy), чем отличаются методы doGet и doPost, что делает контейнер и как развернуть первый сервлет через аннотацию или web.xml.

Servlet, GenericServlet и HttpServlet: три уровня

Чтобы не путаться, разведем три близких понятия сразу:

  • Servlet — базовый интерфейс из пакета jakarta.servlet. Он объявляет методы жизненного цикла (init, service, destroy) и не привязан к протоколу.
  • GenericServlet — абстрактный класс, который реализует Servlet и убирает рутину. Он протокол-независим: у него есть один метод service(ServletRequest, ServletResponse).
  • HttpServlet — наследник GenericServlet, заточенный под HTTP. Именно его наследуют на практике: он разбирает HTTP-метод запроса и вызывает doGet, doPost и другие doXxx.

Почти всегда в реальном коде вы расширяете HttpServlet, а не реализуете Servlet напрямую. Интерфейс Servlet полезно знать, чтобы понимать, откуда берутся методы жизненного цикла.

Важное уточнение по версиям. С Jakarta EE 9 (2020 год) пакеты переименованы: раньше это был javax.servlet.*, теперь jakarta.servlet.*. Старые статьи и код с javax.servlet работают только на Tomcat 9 и ниже. Для Tomcat 10 и новее нужен именно jakarta.servlet.

Контейнер сервлетов: кто вызывает ваш код

Сервлет не запускается сам по себе, как программа с методом main. Его жизнью управляет контейнер сервлетов (он же веб-контейнер) — серверная среда, которая принимает TCP-соединения, разбирает HTTP, создает объекты запроса и ответа и вызывает нужные методы вашего класса.

Самый распространенный контейнер — Apache Tomcat. Есть и другие: Jetty, Undertow, а также полноценные серверы приложений (WildFly, Open Liberty, Payara).

Контейнер берет на себя пул потоков, разбор протокола, управление жизненным циклом и маршрутизацию по URL, а ваша задача — описать реакцию на запрос. Один экземпляр сервлета по умолчанию обслуживает много запросов параллельно, каждый в своем потоке. Поэтому поля класса сервлета — общее состояние для всех потоков: хранить в них данные конкретного пользователя нельзя, это источник гонок. Пользовательские данные держат в запросе, ответе или сессии.

Жизненный цикл сервлета: init, service, destroy

Жизненный цикл описан интерфейсом Servlet и состоит из трех методов, которые вызывает контейнер.

Метод Когда вызывается Сколько раз Зачем
init() При создании экземпляра сервлета Один раз Инициализация ресурсов (пул соединений, конфигурация)
service() На каждый входящий запрос Много раз, параллельно Диспетчеризация запроса по HTTP-методу
destroy() Перед выгрузкой сервлета Один раз Освобождение ресурсов

Как это работает по шагам:

  1. Контейнер загружает класс. По умолчанию — при первом обращении (ленивая загрузка), но можно попросить загрузку при старте через loadOnStartup.
  2. Контейнер создает единственный экземпляр и вызывает init(). Тут удобно один раз открыть, например, пул соединений с базой.
  3. На каждый запрос вызывается service(). В HttpServlet он уже реализован: смотрит HTTP-метод и вызывает doGet, doPost и так далее. Свою логику вы пишете в doXxx, а service обычно не трогаете.
  4. При остановке приложения контейнер вызывает destroy(), где нужно закрыть ресурсы.

Методы init() и destroy() вызываются ровно по одному разу за жизнь экземпляра, а service() — на каждый запрос. Уточнение про сборку мусора: сам сервлет не является сборщиком мусора JVM (это распространенная ошибка старых статей); после destroy() экземпляр просто становится недостижимым, и его убирает обычный GC.

doGet и doPost: чем отличаются

HttpServlet разбирает HTTP-метод запроса и вызывает соответствующий обработчик. Чаще всего переопределяют два.

Метод HTTP Метод сервлета Типичное назначение Где параметры
GET doGet Получение данных, переход по ссылке В строке URL (query string)
POST doPost Отправка формы, изменение данных В теле запроса

Практическое правило выбора: GET — для операций чтения, которые безопасно повторять и кэшировать; POST — когда запрос меняет состояние или передает чувствительные данные (пароль в URL светится в логах и истории браузера). Есть и другие обработчики: doPut, doDelete, doHead — под соответствующие HTTP-методы REST-стиля.

Параметры запроса в обоих случаях читаются одинаково — через request.getParameter("имя"), контейнер сам достает их из URL или из тела формы.

Минимальный рабочий сервлет

Начнем с полного примера, который можно собрать и запустить. Это сервлет, отвечающий на GET-запрос по адресу /hello.

package com.example;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws IOException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter out = response.getWriter();

        String name = request.getParameter("name");
        if (name == null || name.isBlank()) {
            name = "гость";
        }
        out.println("<h1>Привет, " + name + "!</h1>");
    }
}

Что происходит после развертывания. Открыв в браузере http://localhost:8080/app/hello, вы увидите заголовок «Привет, гость!». Если добавить параметр — http://localhost:8080/app/hello?name=Иван — ответ станет «Привет, Иван!». Часть app в пути — это контекст приложения (имя развернутого war-модуля), hello — путь из аннотации @WebServlet.

Разберем ключевые строки:

  • @WebServlet("/hello") привязывает сервлет к URL без файла web.xml.
  • extends HttpServlet дает готовую реализацию service(), которая направит GET в наш doGet.
  • response.setContentType("text/html; charset=UTF-8") вызывают до получения PrintWriter, иначе кодировка ответа может не примениться.
  • request.getParameter("name") возвращает значение или null, если параметра нет; проверка на null обязательна.

Замечание по безопасности: пример подставляет пользовательский ввод в HTML напрямую, что открывает XSS. В реальном коде значение нужно экранировать (например, библиотекой OWASP Java Encoder или средствами шаблонизатора), а не отдавать в страницу как есть.

Типичная ошибка: старый пакет javax

Разберем частый сбой при переезде на новый Tomcat. Возьмем импорт из старого туториала:

import javax.servlet.http.HttpServlet;

Фактический результат на Tomcat 10 или 11: приложение не работает, в логах видно ClassNotFoundException или сервлет просто не находится (ответ 404), потому что контейнер не содержит пакета javax.servlet — он его переименовал.

Исправление — заменить пакет на jakarta:

import jakarta.servlet.http.HttpServlet;

Если переехать сразу нельзя, оставайтесь на Tomcat 9 (там еще javax.servlet) либо прогоните код через инструмент миграции Eclipse Transformer, который переписывает импорты. Смешивать javax и jakarta в одном модуле нельзя.

Развертывание: аннотация или web.xml

Связать сервлет с URL можно двумя способами. Оба рабочие, выбор зависит от того, где удобнее держать конфигурацию.

Способ Где описано Плюс Когда выбрать
Аннотация @WebServlet В коде класса Меньше файлов, все рядом Новый код, простые правила маршрутизации
Дескриптор web.xml В WEB-INF/web.xml Конфигурация отдельно от кода, можно менять без пересборки класса Внешняя настройка, переопределение, общий обзор маршрутов

Тот же сервлет без аннотации описывается в web.xml так:

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         version="6.0">
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.example.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

Здесь servlet-name связывает объявление класса и правило URL: тег servlet регистрирует класс под именем, а servlet-mapping привязывает это имя к пути. Файл web.xml лежит в каталоге WEB-INF внутри war-архива. Если используете аннотацию, web.xml можно вовсе не создавать.

Сервлеты, JSP и Spring MVC: что где

Сервлеты редко пишут для генерации HTML вручную — на голом PrintWriter верстать страницу неудобно. Поэтому вокруг них выросли технологии представления и фреймворки.

  • JSP (Jakarta Server Pages) — страницы с HTML и вставками Java-кода, которые контейнер сам компилирует в сервлет. JSP удобнее для разметки, но сегодня в новых проектах его берут редко, предпочитая шаблонизаторы вроде Thymeleaf.
  • Spring MVC — веб-фреймворк, который работает поверх модели сервлетов. Внутри у него один центральный DispatcherServlet, а вы пишете контроллеры с аннотациями @GetMapping и @PostMapping вместо ручных doGet и doPost. Диспетчеризацию, разбор параметров, сериализацию JSON фреймворк берет на себя.

Практический вывод: понимать сервлеты нужно даже тем, кто пишет на Spring, потому что весь стек стоит на них. Но продуктовый код в 2026 году чаще пишут на Spring MVC или Spring WebFlux, а чистые сервлеты остаются фундаментом и темой для собеседований.

Выводы

  • Сервлет — это Java-класс для обработки HTTP-запросов на сервере; на практике наследуют HttpServlet, а не реализуют интерфейс Servlet напрямую.
  • Жизненным циклом (init, service, destroy) управляет контейнер сервлетов, чаще всего Apache Tomcat; init и destroy вызываются по разу, service — на каждый запрос.
  • doGet обрабатывает чтение (параметры в URL), doPost — отправку и изменение данных (параметры в теле); выбор метода влияет на кэширование и безопасность.
  • С Jakarta EE 9 пакеты переименованы из javax.servlet в jakarta.servlet; это ключевое отличие для Tomcat 10 и новее.
  • Развернуть сервлет можно аннотацией @WebServlet или через web.xml; JSP и Spring MVC надстроены над той же моделью сервлетов.

Где применяется / связь с практикой

Сервлеты — обязательная часть картины мира Java-веб-разработчика: без них не понять ни Tomcat, ни Spring, ни то, как приложение вообще принимает запросы. На собеседованиях на позиции джуна и мидла жизненный цикл, разница doGet и doPost и роль контейнера — типовые вопросы.

Освойте тему на практике

Разобраться в связке «сервлет — контейнер — Spring» системно и с практикой на реальных приложениях помогает курс Java-разработчик (Spring): там веб-слой проходят от сервлетов до Spring MVC и REST. Оценить формат и уровень занятий заранее можно на открытых уроках Otus — это бесплатные вебинары с разбором тем и ответами преподавателя.

FAQ

Нужно ли учить сервлеты, если сразу планирую писать на Spring?
Да. Spring MVC работает поверх сервлетов, а его DispatcherServlet — это обычный сервлет. Понимание модели помогает читать логи, настраивать фильтры и разбираться, когда что-то ломается на уровне контейнера.

Чем сервлет отличается от фильтра (Filter)?
Сервлет формирует ответ на запрос по конкретному URL. Фильтр (jakarta.servlet.Filter) перехватывает запросы и ответы до и после сервлета — для аутентификации, логирования, сжатия. Фильтр не отдает контент сам, а стоит в цепочке перед сервлетом.

Один сервлет обрабатывает запросы по очереди или параллельно?
Параллельно. Контейнер создает один экземпляр сервлета и вызывает его из разных потоков одновременно. Поэтому изменяемое состояние в полях класса небезопасно; пользовательские данные держат в объектах запроса, ответа или сессии.

OTUS Журнал