Сервлеты
Что такое «сервлет»?
Сервлет представляет серверный компонент для обработки запросов; жизненным циклом управляет контейнер. Технология развивается в Jakarta Servlet. Примеры ниже используют прежнее пространство имён javax.servlet из Java EE; начиная с Jakarta Servlet 5 используется jakarta.servlet.
Основные особенности сервлетов:
-
Обработка HTTP-запросов: Сервлеты могут обрабатывать различные HTTP-запросы (GET, POST и т.д.).
-
Жизненный цикл: Жизненный цикл сервлета управляется контейнером сервлетов (например, Apache Tomcat) и включает этапы инициализации (
init), обработки запросов (service) и уничтожения (destroy). -
Динамическое создание контента: Сервлеты могут генерировать динамический контент, такой как HTML, JSON, XML и другие форматы.
Пример простого сервлета:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloWorldServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.getWriter().println("<h1>Hello, World!</h1>");
}
}
В этом примере сервлет HelloWorldServlet обрабатывает HTTP GET запросы и возвращает простой HTML-ответ “Hello, World!”.
В чем заключаются преимущества технологии сервлетов над CGI (Common Gateway Interface)?
Преимущества технологии сервлетов над CGI (Common Gateway Interface):
-
Производительность:
-
Сервлеты: Загружаются в память один раз и остаются в ней, обслуживая множество запросов. Это снижает накладные расходы на создание процессов для каждого запроса.
-
CGI: Для каждого запроса создается новый процесс, что увеличивает накладные расходы и снижает производительность.
-
-
Многопоточность:
-
Сервлеты: Используют многопоточность, что позволяет обрабатывать множество запросов параллельно в одном процессе.
-
CGI: Каждый запрос запускается в отдельном процессе, что менее эффективно.
-
-
Управление памятью:
-
Сервлеты: Управляются JVM (Java Virtual Machine), что позволяет использовать механизмы управления памятью, такие как сборка мусора.
-
CGI: Управление памятью и ресурсами зависит от операционной системы, что может приводить к более высокому потреблению ресурсов.
-
-
Безопасность:
-
Сервлеты: Могут использовать мощные встроенные механизмы безопасности Java, включая контроль доступа и обработку исключений.
-
CGI: Менее гибкие и безопасные, требуют дополнительной настройки для обеспечения безопасности.
-
-
Переносимость:
-
Сервлеты: Написаны на Java, что обеспечивает переносимость между различными платформами и серверами приложений.
-
CGI: Скрипты зависят от языка программирования и платформы, на которой они выполняются, что может ограничивать переносимость.
-
-
Легкость разработки и обслуживания:
-
Сервлеты: Предоставляют богатый API и поддержку различных средств разработки, что упрощает создание и сопровождение веб-приложений.
-
CGI: Написание и обслуживание CGI-скриптов может быть более сложным из-за ограниченных возможностей и инструментов.
-
Какова структура веб-проекта?
Структура веб-проекта обычно включает следующие основные компоненты:
-
Исходный код:
-
Java-классы: Контроллеры, сервисы, DAO (Data Access Objects), модели.
-
Сервлеты: Классы, обрабатывающие запросы.
-
-
Ресурсы:
-
Статические ресурсы: HTML, CSS, JavaScript, изображения и другие статические файлы.
-
Шаблоны: Файлы шаблонов для динамического формирования HTML (например, JSP, Thymeleaf).
-
-
Конфигурация:
-
web.xml: Файл конфигурации веб-приложения (указывается маппинг сервлетов, фильтры и другие параметры).
-
application.properties или application.yml: Файлы конфигурации параметров приложения (например, для Spring Boot).
-
context.xml или server.xml: Файлы конфигурации сервера (например, для Apache Tomcat).
-
-
Библиотеки:
- JAR-файлы: Зависимости и библиотеки, используемые в проекте.
-
Тесты:
-
Unit-тесты: Тесты для проверки отдельных компонентов.
-
Integration-тесты: Тесты для проверки взаимодействия между компонентами.
-
-
Средства сборки:
- build.gradle или pom.xml: Скрипты сборки и управления зависимостями (например, для Gradle или Maven).
Пример структуры веб-проекта на Java (Maven):
my-webapp/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── dao/
│ │ │ └── model/
│ │ ├── resources/
│ │ │ ├── application.properties
│ │ │ └── messages.properties
│ │ ├── webapp/
│ │ │ ├── WEB-INF/
│ │ │ │ ├── web.xml
│ │ │ │ └── views/
│ │ │ │ └── index.jsp
│ │ │ ├── css/
│ │ │ ├── js/
│ │ │ └── images/
│ ├── test/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── dao/
│ │ └── model/
└── pom.xml
Что такое «контейнер сервлетов»?
Контейнер сервлетов — это компонент веб-сервера или сервера приложений, который управляет жизненным циклом сервлетов, обрабатывает их запросы и предоставляет необходимые ресурсы и услуги.
Основные функции контейнера сервлетов:
-
Управление жизненным циклом: Инициализация, обработка запросов и уничтожение сервлетов.
-
Обработка запросов и ответов: Прием HTTP-запросов от клиентов, передача их сервлетам и отправка HTTP-ответов обратно клиентам.
-
Безопасность: Управление безопасностью приложений, аутентификация и авторизация пользователей.
-
Управление ресурсами: Обеспечение доступа к ресурсам, таким как базы данных, файлы и внешние службы.
-
Конфигурация и деплоймент: Обработка конфигурационных файлов (например,
web.xml), управление настройками и развертывание веб-приложений.
Примеры контейнеров сервлетов:
- Apache Tomcat
- Jetty
- GlassFish
- WildFly
Контейнер сервлетов играет ключевую роль в функционировании веб-приложений, обеспечивая их стабильность, безопасность и масштабируемость.
Что такое «дескриптор развертывания»?
Дескриптор развертывания — это XML-файл, используемый для конфигурации веб-приложения в контейнере сервлетов. Он содержит метаданные, которые описывают, как веб-приложение должно быть развернуто и как оно должно функционировать. В дескрипторе развертывания указываются различные параметры, такие как маппинг сервлетов, фильтры, параметры инициализации, роли безопасности и многое другое.
Основной дескриптор развертывания для Java веб-приложений называется web.xml и находится в директории WEB-INF веб-приложения.
Пример содержимого web.xml:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>com.example.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>maxUsers</param-name>
<param-value>100</param-value>
</context-param>
<listener>
<listener-class>com.example.AppListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
</web-app>
В этом примере дескриптор развертывания web.xml описывает:
-
Сервлет
HelloServletи его маппинг на URL/hello. -
Фильтр
LoggingFilter, который применяется ко всем URL (/*). -
Параметр контекста
maxUsersс значением100. -
Листенер
AppListener. -
Список файлов приветствия (
index.htmlиindex.jsp). -
Конфигурацию безопасности для защищенной области
/admin/*, которая требует рольadmin. -
Конфигурацию формы логина и безопасности.
В каком случае требуется переопределять метод service()?
Переопределять метод service() требуется в тех случаях, когда необходимо кастомизировать обработку всех типов HTTP-запросов (GET, POST, PUT, DELETE и т.д.) в одном методе. Метод service() отвечает за распределение входящих запросов к соответствующим методам (doGet(), doPost(), doPut(), doDelete() и т.д.) сервлета.
Основные случаи, когда может потребоваться переопределение метода service():
-
Универсальная обработка логики: Когда нужно выполнить общую логику для всех типов запросов до или после их обработки.
-
Альтернативное распределение запросов: Когда требуется нестандартная маршрутизация или дополнительная логика распределения запросов.
-
Обработка специальных HTTP-методов: HttpServlet уже поддерживает OPTIONS и TRACE через doOptions и doTrace. Переопределение service может понадобиться для нестандартной диспетчеризации; для общей предобработки часто подходит Filter.
Пример переопределения метода service():
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class CustomServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Общая логика для всех типов запросов
System.out.println("Request received");
// Вызываем родительский метод для стандартной обработки
super.service(req, resp);
// Логика после обработки запроса
System.out.println("Response sent");
}
}
Почему обычно переопределяют init() без аргументов в GenericServlet?
Контейнер вызывает Servlet.init(ServletConfig). Реализация GenericServlet сохраняет конфигурацию и затем вызывает удобный hook init() без аргументов. Обычно переопределяют этот hook. Если переопределён init(ServletConfig), вызов super.init(config) сохраняет базовую инициализацию.
Основные причины переопределения метода init() без аргументов:
-
Инициализация ресурсов: Метод
init()используется для выполнения инициализации ресурсов, необходимых для работы сервлета (например, подключение к базе данных, настройка параметров). -
Упрощение кода: Переопределение метода без аргументов позволяет сосредоточиться на логике инициализации, не отвлекаясь на детали инициализации, переданные через
ServletConfig. -
Совместимость: init() без аргументов сохраняет стандартную работу GenericServlet.init(ServletConfig), включая сохранение ServletConfig.
Пример переопределения метода init():
import javax.servlet.*;
import javax.servlet.http.*;
public class MyServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// Инициализация ресурсов
System.out.println("Servlet is being initialized");
}
}
Что вы знаете о сервлетных фильтрах?
Сервлетные фильтры — это компоненты веб-приложения, которые позволяют перехватывать и изменять запросы и ответы в сервлетах. Они предоставляют механизм для выполнения предобработки и постобработки запросов и ответов.
Основные особенности сервлетных фильтров:
-
Предобработка и постобработка: Фильтры могут выполнять действия перед тем, как запрос достигнет сервлета, и после того, как сервлет обработает запрос и сформирует ответ.
-
Цепочка фильтров: Несколько фильтров могут быть связаны в цепочку, где каждый фильтр может передать управление следующему фильтру или конечному сервлету.
-
Переиспользование кода: Фильтры позволяют переиспользовать код для общих задач, таких как аутентификация, логирование, сжатие данных и т.д.
Пример фильтра, который логирует все запросы:
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
public class LoggingFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
// Инициализация фильтра
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
System.out.println("Request URI: " + httpRequest.getRequestURI());
// Передача управления следующему фильтру или сервлету
chain.doFilter(request, response);
}
public void destroy() {
// Очистка ресурсов
}
}
В этом примере фильтр LoggingFilter перехватывает каждый запрос, логирует его URI и затем передает управление следующему элементу цепочки (другому фильтру или сервлету) с помощью метода chain.doFilter().
Фильтры конфигурируются в дескрипторе развертывания web.xml или с помощью аннотаций:
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>com.example.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Или с помощью аннотаций:
import javax.servlet.annotation.WebFilter;
@WebFilter("/*")
public class LoggingFilter implements Filter {
// методы фильтра
}
Как реализовать запуск сервлета одновременно с запуском приложения?
Для реализации запуска сервлета одновременно с запуском приложения, можно использовать параметр load-on-startup в дескрипторе развертывания web.xml. Этот параметр указывает контейнеру сервлетов инициализировать и загружать сервлет при старте приложения, а не при первом запросе к нему.
Пример конфигурации web.xml:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myservlet</url-pattern>
</servlet-mapping>
В этом примере MyServlet будет загружен и инициализирован при старте приложения. Значение 1 в элементе load-on-startup задает порядок загрузки сервлетов. Сервлеты с меньшими значениями загружаются раньше. Положительное значение (например, 1) указывает на необходимость загрузки при старте, тогда как отрицательное значение или его отсутствие означает, что сервлет будет загружен при первом запросе к нему.
Если используются аннотации, можно добавить аннотацию @WebServlet и указать параметр loadOnStartup:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@WebServlet(urlPatterns = "/myservlet", loadOnStartup = 1)
public class MyServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
// Код инициализации сервлета
System.out.println("MyServlet is initialized");
}
}
В этом примере сервлет MyServlet также будет загружен при старте приложения благодаря параметру loadOnStartup = 1 в аннотации @WebServlet.