НейроАгент

Почему SpringApplicationBuilder требует отдельные порты

Узнайте, почему SpringApplicationBuilder Fluent API создает отдельные порты для каждого контекста, в отличие от ServletRegistrationBean. Полное руководство по различиям в архитектуре и настройке.

Вопрос

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

Добрый день!

Когда я создаю иерархию контекста с помощью ServletRegistrationBean, оба DispatcherServlet работают на одном порту:

java
@SpringBootApplication(exclude = {PropertyPlaceholderAutoConfiguration.class, 
		DispatcherServletAutoConfiguration.class,
		WebMvcAutoConfiguration.class,
		ErrorMvcAutoConfiguration.class})
@ConfigurationPropertiesScan
public class SocialAppApplication {

	public static void main(String[] args) {
		
		SpringApplication app = new SpringApplication(SocialAppApplication.class);	
		app.run(args);				
	}
		
	@Bean
	public ServletRegistrationBean<DispatcherServlet> secured()
	{
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		context.register(SecuredConfig.class);
		DispatcherServlet servlet = new DispatcherServlet();
		servlet.setApplicationContext(context);
		ServletRegistrationBean<DispatcherServlet> bean = new ServletRegistrationBean<>(servlet);
		bean.setName("secured");
		bean.addUrlMappings("/secured/*");
		bean.setLoadOnStartup(1);
		return bean;
		
	}
	
	@Bean
	public ServletRegistrationBean<DispatcherServlet> unsecured()
	{
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		context.register(UnsecuredConfig.class);
		DispatcherServlet servlet = new DispatcherServlet();
		servlet.setApplicationContext(context);
		ServletRegistrationBean<DispatcherServlet> bean = new ServletRegistrationBean<>(servlet);
		bean.setName("unsecured");
		bean.addUrlMappings("/unsecured/*");
		return bean;
		
	}
}

Но если создавать иерархию контекста с помощью Fluent API:

java
new SpringApplicationBuilder(SocialAppApplication.class).web(WebApplicationType.NONE)
		.child(SecuredConfig.class).web(WebApplicationType.SERVLET)
		.child(UnsecuredConfig.class).web(WebApplicationType.SERVLET)
		.run(args);

то для каждого контекста требуется свой порт. Почему?

Вопросы для рассмотрения:

  1. Почему SpringApplicationBuilder Fluent API создает отдельные порты для каждого дочернего контекста?
  2. Как настроить SpringApplicationBuilder для работы нескольких сервлетов на одном порту?
  3. В чем принципиальное различие между подходами ServletRegistrationBean и SpringApplicationBuilder в контексте портов?
НейроАгент

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

Содержание


Основное различие между подходами

Ключевое различие заключается в том, что SpringApplicationBuilder создает отдельные Spring Boot приложения, а ServletRegistrationBean создает несколько сервлетов в рамках одного приложения.

Когда вы используете ServletRegistrationBean, вы работаете в рамках одного ApplicationContext и одного встроенного сервера (Tomcat, Jetty и т.д.). Все сервлеты регистрируются в одном контейнере сервлетов и слушают один и тот же порт, но с разными URL-паттернами.

При использовании SpringApplicationBuilder вы создаете полноценные, независимые Spring Boot приложения, каждое со своим:

  • ApplicationContext
  • Встроенным веб-сервером
  • Конфигурацией порта
  • Набором бинов
  • Жизненным циклом

Как отмечено в документации Spring Boot, SpringApplicationBuilder предназначен для создания иерархий ApplicationContext с отношениями “родитель-дочерний”, что делает каждое дочернее контекстом полноценного приложения.


Почему SpringApplicationBuilder требует отдельные порты

SpringApplicationBuilder создает отдельные порты по нескольким фундаментальным причинам:

1. Независимые приложения

Каждый дочерний контекст в иерархии является полноценным Spring Boot приложением со своим встроенным сервером. Как объясняется в статье о иерархии контекстов, каждое这样的 приложение должно запускать собственный сервер на выделенном порту.

2. Конфликт портов по умолчанию

По умолчанию все Spring Boot приложения пытаются запуститься на одном и том же порту (8080). Если бы несколько контекстов пытались использовать один порт, возник бы конфликт. Как указано в исследовании Spring контекстов, разные контексты требуют разных портов для предотвращения конфликтов.

3. Изоляция конфигурации

Каждый контекст имеет свою изолированную конфигурацию. Как упоминается в статье о Spring Boot и контекстах, размещение бинов в отдельных контекстах предотвращает их переопределение и обеспечивает изоляцию.

4. Архитектурная модель

SpringApplicationBuilder следует модели нескольких приложений, а не нескольких сервлетов. Как описано в статье о множественных иерархических контекстах, этот подход позволяет создавать полноценные микросервисы внутри одного приложения.


Настройка SpringApplicationBuilder для работы на одном порту

Хотя SpringApplicationBuilder по умолчанию создает отдельные порты, существуют подходы для работы на одном порту:

1. Явное указание портов через properties

Вы можете настроить разные порты для каждого контекста через системные свойства:

java
public static void main(String[] args) {
    new SpringApplicationBuilder(SocialAppApplication.class)
        .web(WebApplicationType.NONE)
        .child(SecuredConfig.class)
            .web(WebApplicationType.SERVLET)
            .properties("server.port=8081")
        .child(UnsecuredConfig.class)
            .web(WebApplicationType.SERVLET)
            .properties("server.port=8082")
        .run(args);
}

2. Использование одного контекста с несколькими сервлетами

Если вам действительно нужно работать на одном порту, лучше использовать подход с ServletRegistrationBean как в вашем примере:

java
@Bean
public ServletRegistrationBean<DispatcherServlet> secured() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(SecuredConfig.class);
    DispatcherServlet servlet = new DispatcherServlet(context);
    ServletRegistrationBean<DispatcherServlet> bean = new ServletRegistrationBean<>(servlet);
    bean.setName("secured");
    bean.addUrlMappings("/secured/*");
    return bean;
}

@Bean
public ServletRegistrationBean<DispatcherServlet> unsecured() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(UnsecuredConfig.class);
    DispatcherServlet servlet = new DispatcherServlet(context);
    ServletRegistrationBean<DispatcherServlet> bean = new ServletRegistrationBean<>(servlet);
    bean.setName("unsecured");
    bean.addUrlMappings("/unsecured/*");
    return bean;
}

3. Продвинутый подход с условной конфигурацией

Можно использовать условную конфигурацию для определения, какой контекст использовать:

java
public static void main(String[] args) {
    String contextType = System.getProperty("context.type", "secured");
    
    new SpringApplicationBuilder(SocialAppApplication.class)
        .web(WebApplicationType.NONE)
        .child(contextType.equals("secured") ? SecuredConfig.class : UnsecuredConfig.class)
            .web(WebApplicationType.SERVLET)
            .properties("server.port=8080")
        .run(args);
}

Принципиальные различия в архитектуре

Таблица сравнения подходов

Характеристика SpringApplicationBuilder ServletRegistrationBean
Количество контекстов Несколько независимых Один контекст с несколькими сервлетами
Количество серверов Несколько встроенных серверов Один встроенный сервер
Порты Отдельные порты для каждого контекста Один порт для всех сервлетов
Изоляция бинов Полная изоляция между контекстами Бины доступны всем сервлетам
Жизненный цикл Независимые жизненные циклы Общий жизненный цикл
Сложность конфигурации Высокая (требует настройки портов) Низкая
Производительность Несколько экземпляров сервера Один экземпляр сервера

Архитектурные последствия

SpringApplicationBuilder подход:

  • Плюсы: Полная изоляция, возможность масштабирования, микросервисная архитектура
  • Минусы: Высокое потребление ресурсов, сложность конфигурации, несколько портов

ServletRegistrationBean подход:

  • Плюсы: Простота конфигурации, один порт, меньшие ресурсы
  • Минусы: Сложнее обеспечить изоляцию бинов, общие зависимости

Как отмечено в статье о Fluent API, SpringApplicationBuilder предназначен для создания иерархий контекстов, а не для замены традиционного подхода с несколькими сервлетами.


Рекомендации по выбору подхода

Используйте SpringApplicationBuilder когда:

  • Вам нужна полная изоляция между компонентами
  • Вы создаете микросервисную архитектуру в рамках одного приложения
  • Вам независимое развертывание частей приложения
  • Вы работаете с разными профилями для разных контекстов

Используйте ServletRegistrationBean когда:

  • Вам нужно несколько сервлетов на одном порту
  • Вы хотите минимизировать потребление ресурсов
  • Вам простота конфигурации
  • Вам совместное использование бинов между сервлетами

Комбинированный подход

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

java
public static void main(String[] args) {
    new SpringApplicationBuilder(MainApplication.class)
        .web(WebApplicationType.SERVLET)
        .properties("server.port=8080")
        .child(MicroserviceConfig.class)
            .web(WebApplicationType.SERVLET)
            .properties("server.port=8081")
        .run(args);
}

В этом случае основной контекст работает на порту 8080, а микросервис - на порту 8081, что обеспечивает баланс между изоляцией и эффективностью использования ресурсов.

Заключение

  1. SpringApplicationBuilder создает отдельные порты потому, что каждое дочернее контекст является полноценным Spring Boot приложением со своим встроенным сервером
  2. ServletRegistrationBean позволяет использовать один порт потому что он создает несколько сервлетов в рамках одного приложения и одного сервера
  3. Выбор подхода зависит от требований к изоляции - если нужна полная изоляция, используйте SpringApplicationBuilder; если важна простота и эффективность - используйте ServletRegistrationBean
  4. Для работы на одном порте с SpringApplicationBuilder потребуется либо ручная настройка, либо переход к подходу с ServletRegistrationBean
  5. Оба подхода имеют свои преимущества - выбор зависит от конкретных задач архитектуры вашего приложения