1. Смысл разборки skeleton до кода
Когда новичок впервые открывает Spring Boot-проект, он чаще видит не «аккуратный каркас», а «лес папок». Это нормальная реакция: мозг честно признаёт, что пока не умеет отличать важное от второстепенного. В этой лекции мы сделаем проект читаемым, чтобы вы перестали бояться структуры и начали использовать её как подсказку, а не как испытание на веру.
После build.gradle.kts картина всё ещё неполная: правила сборки уже понятны, но пока не видно, во что они превращаются на диске. Поэтому сейчас буквально «раскроем коробку» сгенерированного каркаса: папки, классы, ресурсы и стартеры.
Самая полезная мысль: Initializr генерирует проект не потому, что «так принято», а потому, что Boot и Gradle опираются на конвенции. Если проект соответствует этим конвенциям, инструменты понимают, где искать код, где искать ресурсы, где лежат тесты, как запускать сборку и как упаковывать результат.
Удобно держать в голове простую схему: в любом нормальном Gradle-проекте (и Boot-проекте тоже) файлы делятся на несколько зон ответственности.
flowchart TD A[Проект catalog-service] --> B[Build-инфраструктура] A --> C[Код приложения] A --> D[Ресурсы] A --> E[Тесты] B --> B1[gradlew / gradle/] B --> B2[build.gradle.kts / settings.gradle.kts] C --> C1[src/main/java] D --> D1[src/main/resources] E --> E1[src/test/java]
Пока вы не научились «читать» эту карту, IDE выглядит как шкаф, где всё перемешано: носки лежат рядом с кастрюлями, а паспорт — между пакетами из супермаркета. Как только вы запомните зоны, проект превращается в понятную квартиру: кухня — на кухне, спальня — в спальне, а кот… ну, кот всё равно где-то на клавиатуре, но хотя бы вы знаете планировку.
2. Карта проекта: корень репозитория
Корень проекта — место, где у новичка обычно начинается паника: «Почему тут столько файлов, а я ещё ни строчки бизнес-кода не написал?» Спокойно: большинство файлов в корне — это не код приложения, а инструкция, как собирать и запускать проект. Это как коробка от техники: вы ещё не включили устройство, но уже есть паспорт, гарантия и зарядка.
В типичном проекте catalog-service, созданном через Initializr под Gradle, вы увидите примерно такую структуру:
catalog-service/
├─ build.gradle.kts
├─ settings.gradle.kts
├─ gradlew
├─ gradlew.bat
├─ gradle/
│ └─ wrapper/
│ ├─ gradle-wrapper.jar
│ └─ gradle-wrapper.properties
└─ src/
├─ main/
└─ test/
Давайте разложим это по смыслу. Таблица ниже нужна не для того, чтобы «всё выучить», а чтобы вы привыкли: любой файл в корне играет свою роль.
| Что вы видите | Что это такое | Почему это важно |
|---|---|---|
| build.gradle.kts | главный build-скрипт Gradle | описывает зависимости, плагины и сборку проекта |
| settings.gradle.kts | настройки Gradle-проекта | минимум: имя проекта (root project name) |
gradlew / |
Gradle Wrapper-скрипты | запуск Gradle правильной версии из проекта |
| gradle/wrapper/* | «начинка» Wrapper | здесь зафиксирована версия Gradle и способ её скачать |
| src/ | исходники | это уже «территория приложения»: код, ресурсы, тесты |
Важно психологически: корень проекта — это в первую очередь инфраструктура, а не бизнес-логика. Это не значит, что файлы «неважные». Это значит, что вы не обязаны прямо сейчас понимать их на уровне архитектора Gradle-плагинов. Достаточно понимать, что без них проект теряет воспроизводимость.
Для ориентира вот пример минимального settings.gradle.kts, который обычно генерируется:
// Имя корневого проекта: его будет показывать IDE, и оно часто используется в сборке
rootProject.name = "catalog-service"
Один файл, одна строчка — и всё равно полезно: из неё получится имя модуля в IDE и базовое имя артефакта (а ещё это просто приятно читается).
3. Папка src: главный разделитель «код / ресурсы / тесты»
Папка src — это место, где начинающий программист обычно облегчённо вздыхает: «О, наконец-то что-то похоже на код». Но даже здесь важно не превращать проект в свалку. Главный смысл src в том, что Gradle и Java-проекты уже много лет договорились о стандартной раскладке: где лежит код, где лежат ресурсы, где лежат тесты. Эта договорённость экономит вам массу ручных настроек.
Внутри src вы почти всегда увидите два верхних уровня: main и test. Это не «каприз Spring», а общий инженерный стандарт: приложение и тесты разделяются физически.
src/
├─ main/
│ ├─ java/
│ └─ resources/
└─ test/
├─ java/
└─ resources/
src/test/resources может появиться позже — когда тестам понадобятся файлы и данные (JSON, SQL-скрипты, фикстуры и т.д.).
Смысл такой: всё, что в src/main, относится к «продуктовому» приложению, то есть к тому, что в итоге запускается. Всё, что в src/test, относится к тестовому коду, то есть к тому, что помогает это приложение проверять.
Ещё одна полезная «приземлённая» мысль: Gradle не пытается угадывать, где у вас Java-код. Он просто смотрит в стандартные папки. Поэтому если вы вдруг положите Java-класс в src/main/resources, он не станет «ресурсом с интеллектом», а станет проблемой.
Чтобы проще запомнить, можно держать в голове короткую таблицу:
| Путь | Что там лежит | Как думать |
|---|---|---|
| src/main/java | исходники приложения (Java) | «то, что компилируется» |
| src/main/resources | ресурсы (не Java) | «то, что кладётся рядом с приложением» |
| src/test/java | тесты на Java | «то, что проверяет приложение» |
| src/test/resources | ресурсы для тестов | «данные/файлы, нужные тестам» |
Смысл этого разделения станет особенно приятным, когда проект подрастёт. Сейчас это кажется бюрократией. Потом ощущается как порядок на рабочем столе: когда вы знаете, где лежит зарядка, вы не чувствуете себя археологом каждый вечер.
4. src/main/java: базовый пакет и главный класс приложения
src/main/java — самая «живая» часть проекта: здесь будет ваш прикладной код. Но и тут Initializr не создаёт хаос, а сразу задаёт правильный старт: создаёт базовый пакет и главный класс. На этом этапе особенно важно понять, что пакет в Java — это не просто строка в package ...;, а ещё и структура папок, и «границы видимости» для сканирования компонентов.
Если вы выбрали, например, group = com.example и artifact = catalog-service, то Initializr обычно собирает базовый пакет как:
com.example + catalogservice → com.example.catalogservice
И тогда путь до главного класса будет таким:
src/main/java/com/example/catalogservice/CatalogServiceApplication.java
Внутри — совсем минимальный Boot entry point: класс с main() и аннотацией, которая говорит Boot: «Вот отсюда начинаем».
package com.example.catalogservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// Главный класс приложения: именно отсюда Spring Boot стартует и поднимает контекст
@SpringBootApplication
public class CatalogServiceApplication {
public static void main(String[] args) {
// Запуск Spring Boot-приложения (поднимается контекст, автонастройка, embedded-сервер и т.д.)
SpringApplication.run(CatalogServiceApplication.class, args);
}
}
Сейчас нам важно не «разобрать магию @SpringBootApplication по косточкам» (это будет отдельная тема курса), а увидеть назначение файла: это точка входа приложения, откуда оно стартует. В терминах обычной Java это похоже на главный метод консольной программы, только здесь он поднимает целый runtime и контейнер.
Ещё один важный момент из нашего повторения Spring Core: положение этого класса в пакете имеет значение. Очень грубо (и пока без деталей) можно думать так: Spring Boot смотрит на пакет главного класса и считает его «корнем» для поиска ваших компонентов. Поэтому хорошая привычка — держать главный класс в аккуратном верхнем пакете проекта, а не прятать его где-то в глубине вроде com.example.catalogservice.utils.startup.launcher.finalversion2.
Если вы потом добавите подпакеты (например, catalog, config и так далее), они логично окажутся «ниже» базового пакета, и проект будет естественно читаться.
5. src/test/java: тестовый каркас, который появляется сразу
Папка src/test/java у многих новичков вызывает удивление: «Я же ещё ничего не тестирую, почему мне уже выдали тест?» Это на самом деле очень правильный инженерный намёк от Initializr: хороший сервис — это не только код, но и минимальная проверка того, что он вообще способен стартовать. Даже если вы пока не готовы думать про тестовую стратегию, полезно знать, что каркас уже есть.
Initializr обычно создаёт тест в том же пакете, что и main-класс:
src/test/java/com/example/catalogservice/CatalogServiceApplicationTests.java
Вот типичный «пустой на вид» тест, который вам сгенерируют:
package com.example.catalogservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
// Интеграционный тест: поднимает Spring-контекст и проверяет, что приложение вообще стартует
@SpringBootTest
class CatalogServiceApplicationTests {
@Test
void contextLoads() {
// Тело может быть пустым: если контекст не поднимется, тест упадёт ещё до выполнения этого метода
// В курсе считаем, что это запускается на JUnit 6 (аннотация @Test по смыслу та же)
}
}
С точки зрения новичка это выглядит почти как шутка: метод пустой, а файл есть. Но смысл как раз в том, что если контекст приложения не смог подняться, тест упадёт ещё до того, как начнёт выполнять ваш код внутри contextLoads(). То есть этот тест отвечает на вопрос: «Проект вообще живой? Он собирается? Он стартует хотя бы на уровне контекста?»
Мы сегодня не уходим в детали @SpringBootTest, JUnit 6 и тестовые подходы — просто фиксируем: тесты появляются не потому, что «надо по методичке», а потому, что проектный скелет должен иметь минимальный страховочный трос. Даже если вы пока не альпинист, трос уже пристёгнут — и это хорошо.
6. src/main/resources: ресурсы и дисциплина
src/main/resources — это место, где лежит всё, что нужно приложению, но не является Java-кодом. У начинающих тут две крайности: одни игнорируют эту папку («там какая-то магия»), другие начинают складывать туда всё подряд («ну это же просто файлы»). Реальность посередине: resources — это важный слой приложения, но со своей дисциплиной.
Типичный сгенерированный проект содержит конфигурационный файл приложения. В сгенерированном проекте вы можете увидеть здесь application.properties — это обычный вариант по умолчанию в Initializr, а не вторая равноправная линия проекта. В catalog-service мы держимся YAML-first, поэтому рабочим форматом считаем application.yaml, а не смесь двух форматов.
src/main/resources/
└─ application.yaml
Минимальное содержимое может выглядеть так:
spring:
application:
name: catalog-service
Если у вас после генерации лежит application.properties, это не проблема: важна сама папка resources и место, где живут настройки.
Идея папки resources в том, что её содержимое попадает в classpath приложения. Проще говоря, это файлы, которые приложение сможет «видеть» во время работы. Позже здесь могут лежать, например, статические файлы, конфиги, шаблоны, локализации — но сегодня важно просто научиться отличать код от ресурсов и не путать папки местами.
Самая частая ошибка новичка здесь выглядит так: он создаёт Java-класс в resources, потому что «в IDE так удобнее». Потом Gradle его не компилирует, Spring его не видит, а студент думает, что Spring сломался. На самом деле Spring не сломался — он просто не обязан искать Java-код там, где по стандарту лежат ресурсы.
7. Starter-зависимости в skeleton
После карты папок естественно возникает вопрос: почему этот каркас уже выглядит живым, хотя строк в dependencies мало? Ответ как раз в стартерах.
Когда вы впервые смотрите на build.gradle.kts, можно испытать странное чувство: «У меня всего две строки зависимостей, а проект выглядит как полноценный сервер. Где подвох?» Подвоха нет: это философия Spring Boot. Вместо того чтобы заставлять новичка собирать инфраструктуру по деталям, Boot предлагает вход через стартеры — готовые наборы зависимостей под типовой сценарий.
В форме Initializr такая зависимость может прятаться за именем уровня Spring Web, но в build.gradle.kts канонической строкой для курса остаётся org.springframework.boot:spring-boot-starter-webmvc.
dependencies {
// Базовый web-starter курса: приносит Spring MVC, embedded-сервер и всё типовое для HTTP-приложения
implementation("org.springframework.boot:spring-boot-starter-webmvc")
// Тестовый starter: тестовая инфраструктура Spring + JUnit 6
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Сейчас нам важно только понять смысл, не залезая в детали внутреннего состава. spring-boot-starter-webmvc — это «входной билет» в web-мир (серверная часть, MVC-инфраструктура и всё, что нужно, чтобы приложение вообще могло работать как HTTP-сервис). spring-boot-starter-test — аналогичный «входной билет» в тестовый мир (JUnit 6 и полезная тестовая инфраструктура).
Обратите внимание на психологически важную вещь: вы подключили стартер, а не «одну библиотеку». Поэтому и строк мало, и эффект большой. Это не магия, а заранее подобранный набор зависимостей. И это одна из причин, почему Boot-проекты так приятно стартуют: вы описываете сценарий («мне нужен web»), а платформа подтягивает базовый набор деталей.
Ещё вы можете заметить, что у зависимостей обычно не прописана версия. Это нормально для Boot-подхода: Initializr и Boot-плагин настроены так, чтобы проект держался на единой согласованной линии библиотек. Сегодня нам достаточно просто зафиксировать: «версия не всегда нужна в строке зависимости», и не пытаться лечить это добавлением случайных :1.2.3 «на всякий случай».
8. Мини-сверка: что уже готово
В этот момент полезно немного выдохнуть и признать: у вас уже есть проект, хотя вы почти не писали код. Но проект пока намеренно пустой по функциональности. Это как новый блокнот: страницы есть, ручка есть, оглавление есть, но роман ещё не написан. И это хорошо — мы хотим начать с понятного каркаса, а не с хаоса.
Если описать состояние catalog-service после Initializr одним абзацем, то получится так: в корне лежит инфраструктура сборки (Gradle и Wrapper), в src/main/java лежит точка входа приложения, в src/test/java лежит минимальный тест «контекст поднимается», а в src/main/resources есть место для ресурсов и конфигурации. Этого достаточно, чтобы проект синхронизировался с IDE, собирался и мог запускаться через стандартные Gradle-команды.
На этом этапе самая правильная стратегия — не «улучшать» структуру руками. Не нужно переносить файлы Wrapper, «потому что они мешают». Не нужно переименовывать пакеты в десяти местах, «потому что захотелось». Не нужно создавать папку my_code рядом с src, «потому что так проще». Каркас ценен тем, что он стандартный: стандартность — это ваша будущая экономия времени.
9. Типичные ошибки при разборе skeleton проекта
Ошибка №1: воспринимать сгенерированный проект как «набор мусора, который можно смело подчистить».
Часто рука тянется удалить gradlew, папку gradle/ и половину файлов в корне, потому что «они не похожи на код». Это ломает воспроизводимость сборки и превращает проект в «работает только у меня на ноутбуке». Если вы не уверены, зачем файл нужен, лучше сначала понять его роль, а не удалять.
Ошибка №2: путать src/main/java и src/main/resources.
Эта ошибка выглядит смешно ровно до момента, пока вы не потратите час на вопрос «почему Spring не видит мой класс», который лежит в resources. Java-код должен жить в src/main/java, ресурсы — в src/main/resources. Это не прихоть, это конвенция, на которой стоят Gradle и типичная Java-экосистема.
Ошибка №3: хаотично менять базовый пакет и имя главного класса.
Пакет главного класса — это не только «красота в имени», но и важная структурная точка проекта. Если вы переименовываете com.example.catalogservice в com.qwerty.myapp2.final, вы потом удивитесь, что всё вокруг тоже приходится переименовывать: папки, импорты, тесты. В учебном проекте лучше держать стабильные, читаемые имена и не устраивать ребрендинг каждые полчаса.
Ошибка №4: игнорировать тестовый каркас, потому что «я же не про тесты».
Тест contextLoads() выглядит пустым, и его хочется удалить как бесполезный. Но он служит первым сигналом: проект стартует как приложение, контекст собирается. Даже если вы пока не пишете тесты, иметь минимальную проверку — это как иметь аптечку в машине: пусть лучше лежит и не пригодится.
Ошибка №5: думать, что стартер — это «одна библиотека», и пытаться заменить его на ручной набор модулей.
Иногда новички видят «стартер» и решают: «О, я умный, сейчас заменю starter на три-четыре модуля, чтобы всё было под контролем». Обычно это заканчивается тем, что что-то не подтянулось, версии разъехались и проект перестаёт стартовать. Стартер — удобная входная точка. В рамках курса мы держимся starter-подхода, чтобы проект оставался простым и предсказуемым.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ