Javalin — это не единственный фреймворк на Java, вместе со Spring Boot их десятки. Несмотря на это многообразие, все они базируются на механизме сервлетов (servlets). В этом уроке мы обсудим, что это такое и почему об этом нужно знать.
Клей между фреймворками и веб-серверами
Любой веб-фреймворк работает не сам по себе. Для запуска написанного на нем приложения нужен веб-сервер. Такой веб-сервер загружает приложение внутрь себя и запускает. Из этого следует два вывода:
- Такой веб-сервер должен быть написан на том же языке — например, в Java среди основных веб-серверов выделяют Tomcat и Jetty
- Веб-сервер и фреймворк должны знать друг о друге, чтобы они могли работать совместно
Теперь представьте, что у нас есть десятки фреймворков и десятки веб-серверов. Как им всем знать друг о друге? В худшем случае нам пришлось бы писать код для совместимости каждого с каждым. Это было бы пустой тратой ресурсов команд разработчиков, а создание нового фреймворка было бы очень затруднено.
К счастью, эта проблема решилась еще в конце девяностых, когда появилась спецификация сервлетов. С тех пор все веб-сервера ориентируются только на сервлеты, а фреймворки, используют сервлеты для своей работы «под капотом». Все, что мы писали на Javalin, внутри фреймворка превращается в сервлеты:
Эта спецификация описывает способ написания универсального веб-приложения под Java, в котором код состоит из сервлетов.
Каждый сервлет – это класс, отвечающий за определенный маршрут. Во время работы веб-сервер принимает входящие запросы и передает их сервлетам, отвечающим за запрошенные адреса. Затем сервлеты возвращают ответы, которые отправляются клиентам.
Можно ли сделать вид, что для работы с фреймворками мы не обязаны ничего знать про сервлеты? С одной стороны, да, ведь сервлеты существуют только где-то внутри. На каком-то уровне их можно не замечать. С другой стороны, игнорировать сервлеты не стоит. Они встречаются везде: то в выводах в логах, то в настройках. Даже в Spring Boot настройки часто работают с механизмом сервлетов напрямую. Кроме того, о них могут спросить на собеседовании.
Первый сервлет
Рассмотрим создание полноценного приложения с помощью сервлетов на примере репозитория java-servlet-gradle. Начнем с класса сервлета:
package io.hexlet.servlet;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(name = "MainServlet", urlPatterns = "/about")
public class MainServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setContentType("text/plain");
res.getWriter().write("Hello, Simple Servlet!");
}
}
Здесь мы видим переопределенный метод doGet()
. Он вызывается на Get-запрос по адресу /about, указанному в аннотации. Внутри есть доступ к объектам запроса и ответа. В нашем примере данные запроса не используются, а вот в ответе отдается строчка текста. Перечислим моменты, на которые нужно обратить внимание:
- Jakarta (Jakarta EE) — это набор спецификаций и компонентов, предоставляющих решения для различных прикладных приложений. Туда входят и сервлеты и многое другое, что встречается в реальных проектах
- Сервлет наследуется от класса
HttpServlet
, импортированного из Jakarta - Сервлет нужно пометить аннотацией
@WebServlet
с указанием имени и маршрута, за который этот сервлет отвечает
Так можно добавить любое количество сервлетов:
src/main/java
└── io
└── hexlet
└── servlet
├── MainServlet.java
└── UsersServlet.java
Чтобы запустить веб-сервер с нашим приложением, понадобится кое-что еще установить и настроить. Ниже урезанная версия файла build.gradle.kts:
plugins {
id("java")
// Плагин, который устанавливает и автоматически настраивает веб-сервер
id("org.gretty") version "4.1.0"
// И другие плагины
}
dependencies {
// Необходимые классы
implementation("jakarta.servlet:jakarta.servlet-api:6.0.0")
}
gretty {
// Эта настройка веб-сервера указывает, что нужно работать от корня
// По умолчанию базовый путь равен названию проекта
contextPath = '/'
}
После этого можно запустить приложение:
./gradlew appRun
2023-08-19 21:03:52.194:INFO :oejs.Server:main: jetty-11.0.15; built: 2023-04-11T18:37:53.775Z; git: 5bc5e562c8d05c5862505aebe5cf83a61bdbcb96; jvm 20.0.2+9-78
2023-08-19 21:03:52.224:INFO :oejs.AbstractConnector:main: Started ServerConnector@7af1cd63{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2023-08-19 21:03:52.243:INFO :oejs.Server:main: Started Server@5cf8edcf{STARTING}[11.0.15,sto=0] @1461ms
2023-08-19 21:03:52.639:INFO :oejss.DefaultSessionIdManager:main: Session workerName=node0
2023-08-19 21:03:52.664:INFO :oejsh.ContextHandler:main: Started o.a.g.JettyWebAppContext@186cb891{/,/,file:///Users/kirillmokevnin/repos/java-servlet-gradle/build/inplaceWebapp/,AVAILABLE}
2023-08-19 21:03:52.673:INFO :oag.JettyConfigurerImpl:main: Jetty 11.0.15 started and listening on port 8080
2023-08-19 21:03:52.689:INFO :oag.JettyConfigurerImpl:main: runs at:
2023-08-19 21:03:52.690:INFO :oag.JettyConfigurerImpl:main: http://localhost:8080/
> Task :appRun
Press any key to stop the server.
<===========--> 87% EXECUTING [25s]
> :appRun
После этого приложение станет доступно в браузере по адресу http://localhost:8080. Если открыть страницу https://localhost:8080/about, то мы увидим текст Hello, Simple Servlet!.
Шаблонизатор Java Server Pages (JSP)
Кроме сервлетов, Jakarta содержит в себе JSP. Это простой шаблонизатор, который можно использовать для генерации HTML-страниц на сервере. Для примера создадим другой сервлет и посмотрим, как использовать JSP:
package io.hexlet.servlet;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(name = "UsersServlet", urlPatterns = "/users")
public class UsersServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String message = "Hello from Jakarta Servlet!";
String[] users = {"Mike", "Nina"};
req.setAttribute("message", message);
req.setAttribute("users", users);
req.getRequestDispatcher("/WEB-INF/users.jsp").forward(req, resp);
}
}
Общая структура сервлета не поменялась, но добавилось несколько деталей:
- Данные, которые мы хотим передать в шаблон, нужно записывать в объект запроса через метод
setAttribute()
- Нужно явно указать, какой шаблон использовать
Для хранения шаблонов используется стандартная директория src/main/webapp/WEB-INF:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP Example</title>
</head>
<body>
<h1><%= request.getAttribute("message") %></h1>
<ul>
<c:forEach items="${users}" var="user">
<li>${user}</li>
</c:forEach>
</ul>
</body>
</html>
В шаблоне можно отметить четыре важных элемента:
- Первой строчкой идет
c:forEach
— директива для работы JSP-тегов (они начинаются с префиксаc
) - Запрос и ответ доступны в шаблоне напрямую через переменные
request
иresponse
- Для вывода данных используется синтаксис
<%= %>
, внутри которого можно вызывать Java-код - Для управляющих конструкций используется синтаксис на нестандартных тегах, которые превращаются в обычный HTML после обработки
Для работы JSP нужно добавить несколько зависимостей:
implementation("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0")
implementation("org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.1")
Самостоятельная работа
- Склонируйте приложение java-servlet-gradle на свой компьютер
- Изучите склонированное приложение
- Добавьте в него еще один сервлет, который будет выводить список всех автомобилей из массива при GET-запросе на адрес /cars. Для генерации HTML используйте jsp-шаблон
- Запустите приложение и попробуйте выполнить несколько запросов
Дополнительные материалы
- Класс HttpServlet
- Класс HttpServletRequest, представляющий HTTP-запрос
- Класс HttpServletResponse, представляющий HTTP-ответ
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.