1. Ручная сборка стека — плохой старт
Если раньше вы писали в основном консольные Java-приложения, зависимость обычно выглядела просто: «мне нужна библиотека — я её подключил». С web-приложениями, даже маленькими, это быстро перестаёт работать: для одной «фичи» вам нужны десятки jar-ов, причём часть из них нужна не вам напрямую, а инфраструктуре. И вот вы уже не пишете код, а собираете лего без картинки на коробке.
Представьте, что вам нужно «просто поднять HTTP-сервис и отдавать JSON». Вручную это значит выбрать web-фреймворк, сервер (Tomcat/Jetty/Undertow), JSON-библиотеку, логирование, набор базовых Spring-модулей и ещё аккуратно согласовать версии так, чтобы всё это вообще дружило. В этот момент многие новички начинают думать, что «Spring сложный». Хотя усложняется здесь не программирование, а ручная сборка стека.
Spring Boot решает эту боль не тем, что «прячет зависимости», а тем, что даёт удобные и осмысленные точки входа. Первая такая точка входа — это starter.
2. Что такое starter в Spring Boot
Слово starter в названии зависимости звучит так, будто вам выдают «готовое приложение в коробке». На деле всё проще и надёжнее: starter — это зависимость, которая тянет за собой набор других зависимостей, подобранных под конкретный сценарий. Обычно сам starter почти не содержит прикладного кода. Его задача — сказать сборке: «мне нужен вот такой стек».
В Gradle это выглядит максимально буднично — как обычная строка в dependencies:
dependencies {
// Подключаем не “одну библиотеку”, а целый сценарий (web MVC на servlet-стеке)
implementation("org.springframework.boot:spring-boot-starter-webmvc")
}
И здесь происходит важный психологический поворот. Вы не «подключили одну библиотеку webmvc». Вы объявили намерение: «моё приложение будет servlet-based MVC web-приложением на Spring». В ответ Gradle скачает не один артефакт, а целый граф транзитивных зависимостей. Пока нам достаточно просто держать в голове: за одной строкой скрывается целая ветка библиотек.
Здесь легко перепутать starter и BOM из прошлой лекции. BOM отвечает на вопрос «какие версии совместимы», а starter — на вопрос «какие библиотеки обычно нужны для этого сценария». Это разные роли, и вместе они дают очень мощный эффект: вы выбираете сценарий одной строкой и получаете согласованный набор библиотек без ручного подбора версий.
3. Curated dependency set и «комплект деталей»
Слово curated в IT обычно означает «подобранное и проверенное». В нашем контексте curated dependency set — это набор зависимостей, который уже собрали за вас так, чтобы он был логичным и в большинстве проектов работал «по умолчанию». Это похоже на комплект для сборки стола: вам не нужно отдельно покупать 32 шурупа, 4 ножки и шестигранник — вы берёте коробку, где всё это уже есть и подходит друг к другу.
Если перевести это на язык Spring Boot-проектов, то starter — это «коробка под сценарий». Несколько примеров сценариев, которые мы будем встречать в catalog-service по мере роста проекта, выглядят так:
| Сценарий (человеческим языком) | Как это выражается в Gradle | Что вы на самом деле “просите” у платформы |
|---|---|---|
| «Мне нужен web-сервис на MVC» | spring-boot-starter-webmvc | MVC, JSON-конвертация, встроенный сервер и базовая web-инфраструктура |
| «Мне нужна диагностика в runtime» | spring-boot-starter-actuator | Actuator-эндпоинты, интеграции для health/info и диагностический каркас |
| «Мне нужен нормальный test-стек» | spring-boot-starter-test | JUnit 6 + Spring Test + удобные тестовые утилиты в согласованных версиях |
Заметьте: в этих строках нет перечисления низкоуровневых модулей. Это нормально. Хороший build.gradle.kts — это не список всех болтов, а список того, какие возможности нужны проекту.
И ещё одно: starter — это не «магия». Это просто зависимость, которая раскрывается в другие зависимости. Магия начинается только тогда, когда вы перестаёте смотреть, что именно подключили, и ждёте, что оно «само всё придумает». Мы так делать не будем: мы будем понимать, что происходит, просто на понятном уровне.
4. Starter и поведение приложения
Когда новички слышат «подключил starter — и всё заработало», кажется, будто тут есть какая-то мистика. На самом деле логика простая: starter приносит на classpath нужный набор библиотек, а Spring Boot уже умеет поднять разумную инфраструктуру поверх такого classpath. То есть starter не «включает фичу» волшебной кнопкой, а меняет доступный стек приложения.
Например, spring-boot-starter-webmvc — это не «один jar с web-кодом», а выбранный web-сценарий на servlet-стеке. Поэтому вместе с ним в проект приезжают Spring MVC-модули, поддержка JSON, встроенный сервер и другие части, без которых такой сервис обычно не живёт.
Пока здесь достаточно удержать одну мысль: starter влияет на поведение через classpath. А как этот граф реально читать, откуда именно берутся Tomcat, Jackson и другие библиотеки, уже выясняют через dependency tree.
5. Прямые зависимости в catalog-service
catalog-service в этом курсе — учебный проект, но он должен быть похож на нормальный, живой сервис. И одна из главных привычек, которые стоит вырастить рано: не превращать build.gradle.kts в склад случайных jar-ов. Список прямых зависимостей должен быть коротким, потому что каждая строка — это решение, которое меняет classpath и поведение приложения.
Если на минуту отвлечься от текущего catalog-service и взять абстрактный минимальный Boot-пример, набор прямых зависимостей может выглядеть так. Это именно минимальный пример, а не текущий snapshot дня:
dependencies {
// Базовый каркас Spring Boot (без привязки к web/DB и т.п.)
implementation("org.springframework.boot:spring-boot-starter")
// Тестовый “комплект”: JUnit + Spring Test + утилиты
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Такой пример полезен именно как абстракция: видно, что прямых зависимостей мало, и каждая строка выражает роль, а не внутренности classpath.
Рабочий snapshot catalog-service, от которого мы сейчас отталкиваемся, уже конкретнее: сервис собран через web-сценарий, а не через базовый starter. Его ядро — spring-boot-starter-webmvc плюс spring-boot-starter-test. Если позже поверх этого snapshot понадобится ещё и диагностический слой, build дорастает буквально на одну понятную строку:
dependencies {
// Web MVC на servlet-стеке: контроллеры, JSON, embedded server и базовая web-инфраструктура
implementation("org.springframework.boot:spring-boot-starter-webmvc")
// Диагностика: health/info/metrics и прочие actuator-возможности
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Тестовый starter — обычно остаётся один, без ручной сборки тест-стека
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Это уже расширение текущего baseline, а не его скрытая замена. Даже с Actuator build по-прежнему читается как список возможностей: web, диагностика, тесты. Мы не перечисляем вручную Tomcat, Jackson и logback — это делает выбранный набор starter’ов.
Сравните это с «ручным» стилем, который часто встречается у новичков после пары гугл-запросов. Он выглядит «серьёзно», но на практике почти всегда хуже:
dependencies {
// “Ручной” набор модулей под web-слой: выглядит контролируемо, но легко промахнуться и получить конфликт версий
implementation("org.springframework:spring-webmvc")
implementation("org.apache.tomcat.embed:tomcat-embed-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("ch.qos.logback:logback-classic")
implementation("org.slf4j:slf4j-api")
}
Проблема здесь не в том, что эти библиотеки «плохие». Проблема в другом: вы взяли на себя роль команды Spring Boot. Теперь вы отвечаете за совместимость, за правильный набор модулей, за выравнивание версий и за то, чтобы ничего не забыть. Для учебного проекта это гарантированная когнитивная перегрузка. Для продакшена такой путь иногда оправдан, но только если вы уже хорошо понимаете, что именно делаете и зачем.
В рамках курса мы хотим, чтобы build.gradle.kts был похож на список того, что нужно проекту, — web, actuator, tests, — а не на перечень всех деталей внутреннего устройства.
6. Стартеры и обычные зависимости
Очень легко уехать в крайность: «раз в Boot есть starter’ы, значит всё подключается starter’ами». Это не так. Starter — это хороший формат для сценариев. Но иногда вам нужен не сценарий, а небольшой инструмент. Например, аннотационный процессор для генерации метаданных конфигурации — это не runtime-фича, а build-time поддержка. И тут starter не нужен.
В catalog-service у нас будет зависимость, которую часто путают со starter’ом только потому, что она тоже из Boot. Но роль у неё другая: spring-boot-configuration-processor. Она помогает IDE и сборке понимать ваши кастомные @ConfigurationProperties и подключается как annotation processor.
В Gradle это обычно выглядит так:
dependencies {
// Нужен только на этапе компиляции: генерирует метаданные для IDE/подсказок по @ConfigurationProperties
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
// Тестовый стек — отдельно, чтобы не утяжелять runtime classpath
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
И вот здесь появляется ещё одна важная мысль для новичка: не каждая зависимость должна лежать в implementation. Иногда это testImplementation — нужно только для тестов, иногда это annotationProcessor — нужно на этапе компиляции, иногда это developmentOnly — нужно для локальной разработки. Если всё подряд складывать в implementation, classpath становится тяжелее, шумнее и менее предсказуемым.
Starter’ы помогают держать основной стек в порядке, но не отменяют здравый смысл. Просто теперь базовый вопрос звучит иначе: «мне нужен сценарий — starter, или инструмент — обычная библиотека/процессор?»
7. Типичные ошибки при работе со starter’ами
С starter’ами чаще всего ошибаются не потому, что они сложные, а потому что они ломают привычную модель «одна строка = одна библиотека». На первых порах это как пересесть с велосипеда на электросамокат: вроде всё то же самое, но внезапно быстрее, а значит и в стену въехать проще. Ниже — несколько ошибок, которые стоит заранее распознавать, чтобы не превращать dependency management в лотерею.
Ошибка №1: думать, что starter — это «один жирный jar со всем внутри».
Starter — это не «fat jar» и не какая-то монолитная библиотека. Он раскрывается в набор зависимостей, и именно этот набор формирует classpath. Если держать в голове модель «там внутри магия», вы неизбежно перестанете понимать, что реально подключено и почему приложение ведёт себя именно так.
Ошибка №2: подключить starter и рядом вручную “досыпать” низкоуровневые модули того же сценария.
Например, добавить spring-boot-starter-webmvc, а рядом ещё spring-webmvc, tomcat-embed-core и пару Jackson-модулей «для надёжности». Выглядит как забота, но на деле это дублирование и потенциальные конфликты. Если у вас уже есть starter, сначала считайте, что он привёз базовый набор, и только потом добавляйте то, чего действительно не хватает — и что вы можете объяснить.
Ошибка №3: выбирать starter по “похожему названию”, а не по задаче.
В экосистеме Boot есть зависимости, которые звучат почти одинаково. Новичок часто выбирает по принципу «в названии есть web — значит оно». Правильнее сначала сформулировать сценарий словами: «я хочу servlet MVC» или «я хочу диагностику», — и уже под него выбирать starter. Название — это подсказка, но не гарантия попадания в цель.
Ошибка №4: ожидать, что starter решит архитектуру приложения.
Starter решает проблему слоя зависимостей: он приносит согласованный набор библиотек. Он не решает, как вам организовать пакеты, где держать бизнес-логику, как назвать сервисы и какие endpoint’ы делать. Если вы ждёте, что «подключил starter — и всё стало правильно», вы очень быстро начнёте строить приложение по принципу «ну Boot же умный».
Ошибка №5: пытаться «оптимизировать» build и удалить “лишние” транзитивные зависимости, не понимая их роли.
Когда вы впервые увидите дерево зависимостей, оно покажется огромным. И это нормальная реакция. Но желание «почистить» граф без понимания быстро приводит к ситуации, когда приложение перестаёт стартовать, а причина — в одной случайно исключённой библиотеке. Starter’ы существуют как раз потому, что в сложном стеке «просто убрать лишнее» обычно нельзя без последствий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ