Почему при создании иерархии контекста Spring Boot с помощью Fluent API каждый контекст требует отдельный порт, в то время как при использовании ServletRegistrationBean оба DispatcherServlet работают на одном порту?
Spring Boot создает разные порты для контекстов в иерархии Fluent API, потому что каждый дочерний контекст запускает собственный экземпляр встраиваемого сервлета-контейнера, в то время как ServletRegistrationBean регистрирует несколько сервлетов в рамках одного и того же контекста, который использует единственный экземпляр сервлета-контейнера.
- Иерархия контекстов SpringApplicationBuilder
- Подход с ServletRegistrationBean
- Техническое сравнение подходов
- Практические примеры конфигурации
- Рекомендации по выбору подхода
Иерархия контекстов SpringApplicationBuilder
SpringApplicationBuilder предоставляет fluent API для создания иерархии контекстов с помощью методов parent(), child() и sibling(). Ключевая особенность этого подхода заключается в том, что каждый дочерний контекст создает собственный независимый экземпляр веб-сервера.
Когда вы создаете контекстную иерархию с помощью SpringApplicationBuilder:
SpringApplicationBuilder appBuilder = new SpringApplicationBuilder()
.parent(RootContext.class)
.child(ChildContext1.class)
.sibling(ChildContext2.class);
ConfigurableApplicationContext applicationContext = appBuilder.run();
Каждый дочерний контекст (ChildContext1 и ChildContext2):
- Запускает собственный встраиваемый сервлет-контейнер (Tomcat, Jetty и т.д.)
- Требует отдельной конфигурации порта через
server.port - Работает как отдельное веб-приложение
Как указано в документации Spring, при использовании SpringApplicationBuilder для создания иерархии контекстов, каждый контекc “ожидает прослушивать на разных портах”, что требует явной настройки портов для каждого дочернего контекста.
Подход с SpringApplicationBuilder создает разные виртуальные приложения, каждое со своим портом. Это полезно для полной изоляции контекстов, но увеличивает потребление ресурсов.
Подход с ServletRegistrationBean
ServletRegistrationBean предоставляет альтернативный способ конфигурации нескольких DispatcherServlet в рамках единого контекста приложения. В этом подходе все сервлеты работают в рамках одного и того же веб-сервера.
Пример конфигурации нескольких DispatcherServlet:
@Configuration
public class ServletConfig {
@Bean(name = "apiDispatcherServlet")
public DispatcherServlet apiDispatcherServlet(WebApplicationContext webApplicationContext) {
return new DispatcherServlet(webApplicationContext);
}
@Bean
public ServletRegistrationBean<DispatcherServlet> apiDispatcherServletRegistration() {
ServletRegistrationBean<DispatcherServlet> registration =
new ServletRegistrationBean<>(apiDispatcherServlet(), "/api/*");
registration.setName("apiDispatcherServlet");
return registration;
}
@Bean(name = "webDispatcherServlet")
public DispatcherServlet webDispatcherServlet(WebApplicationContext webApplicationContext) {
return new DispatcherServlet(webApplicationContext);
}
@Bean
public ServletRegistrationBean<DispatcherServlet> webDispatcherServletRegistration() {
ServletRegistrationBean<DispatcherServlet> registration =
new ServletRegistrationBean<>(webDispatcherServlet(), "/web/*");
registration.setName("webDispatcherServlet");
return registration;
}
}
При использовании ServletRegistrationBean:
- Все сервлеты работают на одном порте (обычно 8080)
- Различия достигаются через URL-маппинг (
/api/*,/web/*) - Сервлеты разделяют один и тот жеServletContext и ресурсы
Как объясняется в официальной документации Spring Boot: “Если в контексте содержится только один сервлет, он будет отображен на /. В случае нескольких бинов сервлетов имя бина будет использоваться как префикс пути”. Это позволяет нескольким сервлетам сосуществовать на одном порте.
Техническое сравнение подходов
Архитектурные различия
| Характеристика | SpringApplicationBuilder Иерархия | ServletRegistrationBean |
|---|---|---|
| Количество серверов | Несколько независимых серверов | Один сервер |
| Порты | Разные порты для каждого контекста | Один общий порт |
| Ресурсы | Высокое потребление | Оптимальное использование |
| Изоляция | Полная изоляция контекстов | Частичная изоляция |
| Конфигурация | Отдельные свойства для каждого контекста | Общие свойства |
Почему разные порты в SpringApplicationBuilder?
Причина, по которой SpringApplicationBuilder требует разных портов, заключается в архитектурных различиях:
- Независимые контексты = независимые серверы: Каждый дочерний контекст в иерархии SpringBootApplicationre создается как отдельное веб-приложение со своим собственным экземпляром сервера
- Отсутствие общего ServletContext: Контексты не разделяют ServletContext, который необходим для работы нескольких сервлетов на одном порту
- Конфликт портов: Попытка запустить несколько контекстов на одном порту вызовет конфликт привязки порта
Как отмечено в документации Spring: “DispatcherServlet ожидает WebApplicationContext для своей конфигурации. WebApplicationContext имеет связь с ServletContext и сервлетом, с которым он ассоциирован”.
Почему ServletRegistrationBean работает на одном порту?
ServletRegistrationBean успешно работает на одном порту, потому что:
- Единый контекст: Все сервлеты регистрируются в рамках одного и того же WebApplicationContext
- Общий ServletContext: Сервлеты разделяют один ServletContext, который управляет жизненным циклом сервлетов
- URL-маршрутизация: Разные URL-паттерны (
/api/*,/web/*) позволяют направлять запросы соответствующим сервлетам
Практические примеры конфигурации
Конфигурация портов для SpringApplicationBuilder
Для настройки разных портов в иерархии SpringApplicationBuilder:
public class MultiPortApplication {
public static void main(String[] args) {
// Родительский контекст без веб-компонентов
ApplicationContext parentContext = new SpringApplicationBuilder(ParentConfig.class)
.web(WebApplicationType.NONE)
.run();
// Дочерний контекст 1 на порту 8080
ApplicationContext child1Context = new SpringApplicationBuilder(Child1Config.class)
.parent(parentContext)
.properties("server.port=8080")
.run();
// Дочерний контекст 2 на порту 8081
ApplicationContext child2Context = new SpringApplicationBuilder(Child2Config.class)
.parent(parentContext)
.properties("server.port=8081")
.run();
}
}
Конфигурация ServletRegistrationBean с одним портом
@Configuration
public class MultiServletConfig {
@Bean
public ServletRegistrationBean<DispatcherServlet> apiServletRegistration() {
DispatcherServlet apiServlet = new DispatcherServlet();
ServletRegistrationBean<DispatcherServlet> registration =
new ServletRegistrationBean<>(apiServlet, "/api/*");
registration.setName("apiServlet");
registration.setLoadOnStartup(1);
return registration;
}
@Bean
public ServletRegistrationBean<DispatcherServlet> webServletRegistration() {
DispatcherServlet webServlet = new DispatcherServlet();
ServletRegistrationBean<DispatcherServlet> registration =
new ServletRegistrationBean<>(webServlet, "/web/*");
registration.setName("webServlet");
registration.setLoadOnStartup(1);
return registration;
}
}
Рекомендации по выбору подхода
Когда использовать SpringApplicationBuilder иерархию:
- Полная изоляция контекстов: Когда требуется изолировать бизнес-логику, конфигурацию или зависимости
- Разные технологии стека: Когда разные контексты используют разные версии библиотек или разные фреймворки
- Микросервисная архитектура: Когда каждый контекст представляет собой логический микросервис
- Тестирование: Когда нужно тестировать контексты независимо
Когда использовать ServletRegistrationBean:
- Оптимизация ресурсов: Когда важно минимизировать потребление памяти и процессорных ресурсов
- Единая аутентификация: Когда все сервлеты должны использовать общую систему аутентификации
- Общие сервисы: Когда сервлеты должны разделять общие сервисы и компоненты
- REST+UI приложение: Когда нужно разделить REST API и веб-интерфейс в рамках одного приложения
Гибридный подход
В некоторых случаях можно комбинировать оба подхода:
@SpringBootConfiguration
public class HybridConfig {
// Корневой контекст с общими сервисами
@Bean
public SpringApplicationBuilder rootContext() {
return new SpringApplicationBuilder(RootConfig.class)
.web(WebApplicationType.NONE);
}
// Дочерний контекст с ServletRegistrationBean
@Bean
public SpringApplicationBuilder childContext(SpringApplicationBuilder root) {
return root.child(WebConfig.class)
.properties("server.port=8080");
}
}
Заключение
Ключевое различие между SpringApplicationBuilder и ServletRegistrationBean заключается в архитектурном подходе к управлению контекстами:
- SpringApplicationBuilder создает полноценные, изолированные веб-приложения, каждое со своим сервером и портом
- ServletRegistrationBean регистрирует несколько сервлетов в рамках одного веб-приложения, работающего на едином порту
Выбор подхода зависит от требований к изоляции, ресурсам и архитектуре приложения. Для большинства стандартных веб-приложений ServletRegistrationBean предоставляет более эффективное решение, в то время как SpringApplicationBuilder иерархия подходит для сложных сценариев с полной изоляцией контекстов.
Источники
- Spring Boot And Context Handling - ccbill.com
- Context Hierarchy :: Spring Framework - docs.spring.io
- Spring Web Contexts | Baeldung
- SpringApplicationBuilder tutorial - zetcode.com
- Context Hierarchy with the Spring Boot Fluent Builder API | Baeldung
- Servlet Web Applications :: Spring Boot - docs.spring.io
- Spring Boot Features - docs.spring.io
- How to configure port for a Spring Boot application - Stack Overflow