JavaRush /Курсы /Spring Boot /WebApplicationType и...

WebApplicationType и выбор режима запуска

Spring Boot
5 уровень , 2 лекция
Открыта

1. Роль WebApplicationType: не только web

Мы уже увидели, что SpringApplication.run(...) проходит несколько фаз. Одна из самых ранних — понять, какой тип приложения Boot вообще поднимает. Пока этот выбор не сделан, непонятно, нужен ли веб-сервер, какой контекст создавать и почему в логах потом либо появляется порт, либо нет.

Если вы приходите в Spring Boot из мира «бэкенд = HTTP», легко попасть в ловушку: кажется, что любое Boot-приложение обязано быть веб-сервисом. На практике Spring Boot — это платформа, которая умеет запускать разные типы приложений: от консольных утилит до веб-сервисов. И чтобы старт не превращался в угадайку, Boot принимает раннее решение: какой «тип» приложения мы сейчас поднимаем. Это решение влияет на то, какой контекст будет создан, будет ли стартовать веб-сервер, появится ли сетевой порт и какой набор веб-инфраструктуры вообще уместен.

Представьте, что Boot — это менеджер в аэропорту, который видит ваш билет и решает, куда вас вести. Если вы летите «без багажа и без стюардессы» (условный NONE), вам не нужен огромный терминал с лентой выдачи и паспортным контролем. Если вы летите «обычным рейсом» (условный SERVLET), терминал нужен, иначе вы просто не попадёте в самолёт. А если вы летите «другим типом рейса» (условный REACTIVE), вам нужен другой выход и другой регламент. Важно не то, какой из вариантов «лучше», а то, что они разные, и Boot должен выбрать один, иначе он соберёт приложение «из несовместимых деталей».

Практическая причина понимать WebApplicationType очень простая: вы будете добавлять зависимости — стартеры, — и иногда от одной строки в build.gradle.kts приложение внезапно меняет характер. Вчера это была тихая консольная утилита, сегодня — веб-сервис с портом. Или наоборот: вы думали, что у вас сервис, а он стартовал без сервера, потому что веб-стека на classpath нет или вы случайно его исключили. Понимание WebApplicationType делает такие ситуации не мистикой, а читаемой инженерной причинно-следственной связкой.

2. Значения WebApplicationType

Когда Boot говорит «тип web-приложения», он не пытается философствовать. Он буквально выбирает значение enum WebApplicationType, у которого на нашем уровне важны три варианта. И вот тут полезно сразу зафиксировать главное: это не «настройка контроллеров» и не «аннотация на классе». Это режим всего приложения, который влияет на запуск в целом.

Для ясности сведём различия в небольшую таблицу. Не как справочник на все случаи жизни, а как «карту местности», чтобы не потеряться.

WebApplicationType Что это означает на практике Будет ли слушать порт Типичный стек зависимостей (идея) Где встречается чаще всего
NONE Приложение не поднимает веб-сервер и не ждёт HTTP-запросов Нет «обычный Boot без веб-стартеров» консольные утилиты, batch, миграции, сервисы без HTTP
SERVLET Приложение работает как классическое servlet-веб-приложение (наш курс) Да spring-boot-starter-webmvc обычные REST/HTTP-сервисы, MVC, классический бэкенд
REACTIVE Приложение работает в реактивном веб-режиме Да spring-boot-starter-webflux проекты на WebFlux (в этом курсе — только знать, что существует)

Теперь важный практический момент. Для catalog-service мы держимся режима SERVLET и приносим на classpath spring-boot-starter-webmvc. Реактивный режим (REACTIVE) здесь нужен только как третье имя в списке — чтобы вы не думали, что Spring Boot «иногда случайно пишет REACTIVE в логах». Нет, это не случайность: это выбранный режим.

А NONE — это вообще отдельная полезная идея: Spring Boot как платформа не требует web. Если вы однажды захотите сделать, например, маленькую утилиту для подготовки данных, проверки конфигурации или генерации отчёта, вы сможете запускать Spring-контекст и DI — всё наше любимое, — но без сервера и порта.

И последнее, чтобы не было неправильных ассоциаций. REACTIVE — это не «ускоренный режим», а другая модель веб-рантайма. Если вы увидите это значение в приложении, это означает не «Boot решил ускориться», а «вы или classpath принесли другой веб-стек».

3. Выбор WebApplicationType по classpath

Секрет выбора WebApplicationType одновременно скучный и прекрасный: Spring Boot принимает решение, опираясь на classpath, то есть на набор библиотек, которые реально доступны приложению. Это та же философия, что и у Boot в целом: «зависимости определяют возможности, возможности определяют умолчания». И это очень логично, потому что на старте у Boot ещё нет ваших контроллеров, нет ваших сервисов, нет вашего «внутреннего мира» — есть только стартовый класс и то, что лежит в зависимостях.

Проще всего увидеть это на уже знакомой точке влияния — на build.gradle.kts. Даже без глубокого понимания Gradle полезно держать в голове правило: стартеры — это не просто «библиотеки для компиляции», а переключатели режима жизни приложения.

Вот пример (кусочек), который хорошо иллюстрирует идею. Он не для того, чтобы вы сейчас всё перестраивали, а чтобы вы увидели причинно-следственную связь.

dependencies {
    // Базовый стартер: автоконфигурация, логирование и прочая «база» Spring Boot
    implementation("org.springframework.boot:spring-boot-starter")

    // Servlet-стек (как правило, приводит Boot к WebApplicationType.SERVLET)
    implementation("org.springframework.boot:spring-boot-starter-webmvc")

    // Reactive-стек (как правило, приводит Boot к WebApplicationType.REACTIVE) — в курсе не используем
    // implementation("org.springframework.boot:spring-boot-starter-webflux")
}

Когда на classpath есть servlet-веб-стек — в частности, стартер spring-boot-starter-webmvc и его транзитивные зависимости, — Boot почти всегда выбирает SERVLET. Когда webmvc нет, но есть reactive-веб-стек (webflux), он выбирает REACTIVE. Когда нет ни того, ни другого — NONE.

Для начинающего разработчика это очень важная мысль: вы меняете поведение приложения не только кодом, но и списком зависимостей. И Spring Boot делает это намеренно. Это не «хаос», а платформенная логика.

Чтобы это стало совсем осязаемым, вот маленькая блок-схема в стиле «Boot на собеседовании задаёт вопросы вашему classpath». Детали классов-проверок нам сейчас не важны; важна сама структура решения.

flowchart TD
    A["SpringApplication стартует"] --> B{"Есть servlet-web стек на classpath?"}
    B -- да --> S["SERVLET"]
    B -- нет --> C{"Есть reactive-web стек на classpath?"}
    C -- да --> R["REACTIVE"]
    C -- нет --> N["NONE"]

Есть тонкий нюанс, который полезно знать заранее, чтобы потом не удивляться: если вы каким-то образом притащили оба стека — и servlet, и reactive, — Boot должен выбрать что-то одно. Во многих случаях он выбирает SERVLET как более «классический» вариант по умолчанию. Но воспринимайте это не как приглашение тащить всё подряд, а как предупреждение: смешение стеков без понимания легко даёт неожиданные эффекты. В нашем учебном проекте мы этого избегаем и держимся одного веб-стека.

4. Контекст и сервер

Когда вы слышите «тип приложения», легко спросить: «Ну и что? Какая разница, если у меня всё равно @SpringBootApplication?» Разница есть, и она вполне практическая: выбранный режим определяет, какой ApplicationContext будет создан и будет ли вообще запускаться веб-сервер.

Порядок здесь принципиален: сначала SpringApplication определяет WebApplicationType, потом по нему выбирает конкретный ApplicationContext, и только в веб-ветке вообще появляется смысл поднимать embedded server. То есть сервер — это уже след выбранного режима, а не отдельное решение где-то в конце старта.

Даже если не запоминать точные названия классов, полезно понимать идею. В режиме SERVLET Spring Boot создаёт веб-ориентированный контекст, который умеет работать с servlet-средой и поднимать embedded server. В режиме NONE создаётся обычный не-веб-контекст, который прекрасно умеет DI и конфигурацию, но не строит веб-инфраструктуру. В режиме REACTIVE — реактивный веб-контекст, рассчитанный на другой стек.

На уровне ощущений это проявляется очень просто. В SERVLET-режиме вы обычно видите в логах признаки того, что сервер поднялся и слушает порт — например, упоминание Tomcat и порта. В NONE-режиме таких строк нет: приложение стартует, контекст собирается, но порт никто не открывает, потому что открывать нечему и незачем.

Ещё один полезный взгляд: WebApplicationType — это раннее решение Boot о том, в какой «экосистеме» он будет собирать приложение. Если выбран NONE, то веб-инфраструктура будет не просто «неиспользована», а зачастую вообще не будет создана или будет отключена. И это нормально: Boot не любит таскать лишнее.

Иногда начинающие пытаются рассуждать так: «Я же добавил класс контроллера, значит приложение web». Но в Boot причинность обратная: сначала определяется режим запуска — в том числе по зависимостям, — потом под этот режим поднимается инфраструктура, и уже в неё «вкручиваются» ваши компоненты. Контроллеры сами по себе не заставляют Boot внезапно стать веб-приложением, если веб-стека нет.

5. Явный режим через setWebApplicationType(...)

Автоматическое определение режима удобно, но иногда нужен явный контроль. И Spring Boot даёт его очень простым способом: вы можете создать объект SpringApplication, выставить WebApplicationType, а потом вызвать run(...). Это ровно та «явная форма запуска», которую мы уже видели в прошлой лекции, только теперь используем её не ради красоты, а ради конкретной настройки старта.

Сам CatalogServiceApplication при этом остаётся тем же. Для учебных экспериментов меняется только то, как мы собираем и запускаем SpringApplication внутри main().

Сначала — пример, который заставляет приложение стартовать без веб-сервера, даже если в зависимостях есть веб-стек. Это полезно как учебная демонстрация: один и тот же проект может жить по-разному.

SpringApplication app = new SpringApplication(CatalogServiceApplication.class);

// Принудительно отключаем web-сервер (порт слушать не будем), даже если web-стек есть в зависимостях
app.setWebApplicationType(WebApplicationType.NONE);

// Запускаем приложение: контекст поднимется, но веб-инфраструктура создаваться не будет
app.run(args);

Это не новый штатный вид catalog-service, а временное явное переопределение, которое позволяет почувствовать разницу между режимами на одном и том же приложении.

Если вы так запустите catalog-service, он стартует как Spring-приложение — контекст будет, — но как веб-сервис не поднимется: порта не будет. И вот это ощущение «Boot без web» очень полезно держать в голове: Boot — это не «обёртка вокруг Tomcat», а платформа сборки приложения.

Теперь — канонический вариант для нашего проекта: режим SERVLET.

SpringApplication app = new SpringApplication(CatalogServiceApplication.class);

// Явно указываем servlet-based режим (обычный веб-сервис в нашем курсе)
app.setWebApplicationType(WebApplicationType.SERVLET);

// Запускаем приложение: будет поднят embedded server, появится порт, активируется web-инфраструктура
app.run(args);

Этот код, строго говоря, чаще всего избыточен: если у вас есть spring-boot-starter-webmvc, Boot и так выберет SERVLET. Но как учебная демонстрация он полезен: вы видите, что режим — это отдельный параметр, а не «магия вокруг контроллеров».

И наконец — просто для общей картины, не для практики в этом курсе, — третий вариант:

SpringApplication app = new SpringApplication(CatalogServiceApplication.class);

// Если поставить REACTIVE, но не принести reactive-зависимости (например, webflux), старт будет проблемным
app.setWebApplicationType(WebApplicationType.REACTIVE);

// Пытаемся стартовать в reactive-режиме только как демонстрацию: так видно, что это реальный переключатель режима
app.run(args);

Смысл этого примера не в том, чтобы вы сейчас запускали reactive-режим, а в том, чтобы вы понимали: REACTIVE — это реальный переключатель режима, а не «слово из модного доклада». Если поставить REACTIVE, а нужного reactive-стека на classpath нет, приложение, скорее всего, не будет счастливо. Boot не умеет «притворяться» веб-приложением без веб-зависимостей.

6. Эксперимент: режим NONE без web

Очень легко сделать неверный вывод: «Если я выключил web, значит Spring “не работает”». Нет. В режиме NONE у вас по-прежнему есть ApplicationContext, DI, component scan, @Configuration, @Bean и все базовые механики. Просто нет веб-сервера и всего, что связано с обслуживанием HTTP-запросов.

Чтобы это почувствовать руками и глазами, добавим к проекту крошечный компонент-маркер. Он не про бизнес-логику, он просто даёт нам простой факт: компонент найден, бин создан.

package com.example.catalogservice.catalog;

import org.springframework.stereotype.Component;

@Component
public class CatalogMarker {

    // Простой «маркерный» метод: нужен только чтобы увидеть, что бин реально создан и доступен
    public String name() {
        return "catalog-service";
    }
}

Теперь временно изменим main() того же CatalogServiceApplication так, чтобы мы запускали приложение в режиме NONE, забирали контекст и доставали бин. В реальном проекте мы так делать не будем: обычно бины вручную из main() не вытаскивают. Но как «рентген» для понимания старта это отличный приём.

SpringApplication app = new SpringApplication(CatalogServiceApplication.class);
app.setWebApplicationType(WebApplicationType.NONE);

// Запускаем Spring и получаем контекст (он есть даже без web-сервера)
ConfigurableApplicationContext context = app.run(args);

// Достаём бин из контекста вручную — как диагностический приём для учебного примера
CatalogMarker marker = context.getBean(CatalogMarker.class);

// Печатаем результат: значит, component scan сработал и бин создан
System.out.println(marker.name()); // catalog-service

Что вы этим доказываете себе — и своему внутреннему скептику, который уверен, что Spring живёт только ради web? Что Spring-контейнер поднялся, component scan сработал, бин создан и доступен. То есть всё «ядро» приложения на месте. Просто Boot не включил веб-режим, и поэтому у приложения нет сетевого входа через HTTP.

А теперь представьте, насколько это помогает выстроить мысли. Если вы запускаете приложение и видите, что ваши бины создаются, но порта нет, это уже не паника «ничего не работает». Это вопрос: «в каком режиме я запустился и почему?». И WebApplicationType — как раз ответ на этот вопрос.

7. Симптомы режима запуска

После этой лекции вам будет проще объяснять целый класс странных ситуаций, которые обычно выглядят как «Spring опять магичит». На деле там почти всегда один из нескольких сценариев, связанных с режимом запуска.

Самый частый симптом — вы ждёте, что приложение откроет порт, например 8080, но порт не появляется. Причина часто не в том, что «Tomcat сломался», а в том, что приложение стартовало как NONE — либо из-за зависимостей, либо потому что кто-то принудительно выставил режим. В таком случае сервер не обязан подниматься, и логика «проверю controller» просто не применима: нет веб-рантайма — нет запросов.

Обратный симптом тоже встречается: вы хотели маленькую утилиту, а приложение вдруг стартует как веб-сервис и «шумит» логами про сервер. Это часто происходит, когда в проект по привычке добавили веб-стартер «на всякий случай». Boot честно увидел веб-стек на classpath и выбрал SERVLET. Это не ошибка Boot — это его логичное поведение.

Ещё один симптом — путаница между servlet и reactive режимом. Иногда разработчики случайно тянут в проект и webmvc, и webflux — через разные стартеры, иногда транзитивно. Boot должен выбрать один режим, и выбор может не совпасть с ожиданиями. На уровне начинающего это выглядит как «оно запускается, но как-то не так». На уровне понимания WebApplicationType это уже другой разговор: «какой стек реально на classpath и какой режим в итоге выбран».

Можно даже сформулировать короткое правило диагностики. Когда поведение старта не совпадает с ожиданиями, прежде чем копать глубже, полезно задать себе три вопроса. Есть ли вообще веб-стек в зависимостях? Не выставлен ли WebApplicationType явно? И совпадает ли режим запуска с тем, как вы вообще думаете о проекте — как об утилите или как о сервисе? Эти вопросы часто экономят часы, потому что вы начинаете искать причину в правильном месте: в «типе приложения», а не в случайных деталях.

8. Типичные ошибки при работе с WebApplicationType

Ошибка №1: думать, что «Boot = web», и игнорировать режим запуска.
Это приводит к очень странной отладке: вы ищете проблему в контроллерах и портах, хотя приложение стартовало как NONE. Лучше держать в голове простую мысль: Boot — это платформа, а web — это один из режимов. Если порта нет, это может быть не «поломка», а выбранный режим.

Ошибка №2: считать, что режим определяется наличием @RestController — или вообще «моим кодом».
В Boot первичен classpath и ранние настройки старта, а не то, какие классы вы написали. Контроллер без веб-стека — это просто класс с аннотацией, которая в данном режиме может оказаться неуместной. Если вы хотите web, убедитесь, что веб-стек реально присутствует.

Ошибка №3: принудительно ставить WebApplicationType.REACTIVE, не понимая, что это другой стек.
Новички иногда читают «reactive» как «продвинутый» или «быстрый» и пытаются включить его везде. На практике это другой рантайм и другая экосистема зависимостей. Включать REACTIVE без соответствующих зависимостей — примерно как пытаться завести машину, заливая бензин в USB-порт: и то и другое «в отверстие», но эффект не тот.

Ошибка №4: случайно притащить два web-стека и удивляться «непредсказуемости» запуска.
Когда в зависимостях одновременно есть servlet и reactive web, Boot вынужден выбрать один режим. Без понимания WebApplicationType это ощущается как хаос. Лекарство простое: держать веб-стек проекта чистым и осознанно выбирать стартеры, а не собирать их «на всякий случай».

Ошибка №5: использовать setWebApplicationType(...) как «постоянный костыль», а не как осознанный инструмент.
Иногда разработчик не понимает, почему приложение запускается «не так», и начинает принудительно выставлять режим в main() как магическую заплатку. Это работает, но скрывает причину. Гораздо полезнее сначала понять, почему Boot выбрал тот или иной режим — classpath и зависимости, — и уже потом решать, нужно ли явное переопределение.

1
Задача
Spring Boot, 5 уровень, 2 лекция
Недоступна
Явный режим `NONE` даже с web starter
Явный режим `NONE` даже с web starter
1
Задача
Spring Boot, 5 уровень, 2 лекция
Недоступна
Явный режим `SERVLET`
Явный режим `SERVLET`
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ