НейроАгент

Почему Spring Boot требует разные порты для иерархии контекстов

Объяснение различий между SpringApplicationBuilder и ServletRegistrationBean в Spring Boot. Узнайте, почему иерархия контекстов требует разных портов, а ServletRegistrationBean работает на одном порте.

Вопрос

Почему при создании иерархии контекста Spring Boot с помощью Fluent API каждый контекст требует отдельный порт, в то время как при использовании ServletRegistrationBean оба DispatcherServlet работают на одном порту?

НейроАгент

Spring Boot создает разные порты для контекстов в иерархии Fluent API, потому что каждый дочерний контекст запускает собственный экземпляр встраиваемого сервлета-контейнера, в то время как ServletRegistrationBean регистрирует несколько сервлетов в рамках одного и того же контекста, который использует единственный экземпляр сервлета-контейнера.

Иерархия контекстов SpringApplicationBuilder

SpringApplicationBuilder предоставляет fluent API для создания иерархии контекстов с помощью методов parent(), child() и sibling(). Ключевая особенность этого подхода заключается в том, что каждый дочерний контекст создает собственный независимый экземпляр веб-сервера.

Когда вы создаете контекстную иерархию с помощью SpringApplicationBuilder:

java
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:

java
@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 требует разных портов, заключается в архитектурных различиях:

  1. Независимые контексты = независимые серверы: Каждый дочерний контекст в иерархии SpringBootApplicationre создается как отдельное веб-приложение со своим собственным экземпляром сервера
  2. Отсутствие общего ServletContext: Контексты не разделяют ServletContext, который необходим для работы нескольких сервлетов на одном порту
  3. Конфликт портов: Попытка запустить несколько контекстов на одном порту вызовет конфликт привязки порта

Как отмечено в документации Spring: “DispatcherServlet ожидает WebApplicationContext для своей конфигурации. WebApplicationContext имеет связь с ServletContext и сервлетом, с которым он ассоциирован”.

Почему ServletRegistrationBean работает на одном порту?

ServletRegistrationBean успешно работает на одном порту, потому что:

  1. Единый контекст: Все сервлеты регистрируются в рамках одного и того же WebApplicationContext
  2. Общий ServletContext: Сервлеты разделяют один ServletContext, который управляет жизненным циклом сервлетов
  3. URL-маршрутизация: Разные URL-паттерны (/api/*, /web/*) позволяют направлять запросы соответствующим сервлетам

Практические примеры конфигурации

Конфигурация портов для SpringApplicationBuilder

Для настройки разных портов в иерархии SpringApplicationBuilder:

java
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 с одним портом

java
@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 и веб-интерфейс в рамках одного приложения

Гибридный подход

В некоторых случаях можно комбинировать оба подхода:

java
@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 заключается в архитектурном подходе к управлению контекстами:

  1. SpringApplicationBuilder создает полноценные, изолированные веб-приложения, каждое со своим сервером и портом
  2. ServletRegistrationBean регистрирует несколько сервлетов в рамках одного веб-приложения, работающего на едином порту

Выбор подхода зависит от требований к изоляции, ресурсам и архитектуре приложения. Для большинства стандартных веб-приложений ServletRegistrationBean предоставляет более эффективное решение, в то время как SpringApplicationBuilder иерархия подходит для сложных сценариев с полной изоляцией контекстов.

Источники

  1. Spring Boot And Context Handling - ccbill.com
  2. Context Hierarchy :: Spring Framework - docs.spring.io
  3. Spring Web Contexts | Baeldung
  4. SpringApplicationBuilder tutorial - zetcode.com
  5. Context Hierarchy with the Spring Boot Fluent Builder API | Baeldung
  6. Servlet Web Applications :: Spring Boot - docs.spring.io
  7. Spring Boot Features - docs.spring.io
  8. How to configure port for a Spring Boot application - Stack Overflow