1. Запуск вне IDE
К этому моменту run уже настроен: Gradle знает, какой main() запускать и как собрать для него classpath. Но на этом история сборки не заканчивается. После classes, jar и run в проекте остаются вполне конкретные следы: скомпилированные классы, обработанные ресурсы и архив в build/libs.
Поэтому теперь смотрим не на сам факт запуска, а на следы сборки. Где лежат эти файлы, что именно попадает в обычный jar и чем такой архив отличается от запуска через Gradle — вот это и важно понять.
Команда ./gradlew run уже даёт воспроизводимый старт. Теперь используем её как точку сравнения: она помогает увидеть, что Gradle собирает автоматически, а что при прямом запуске JVM придётся задавать руками.
2. Содержимое build/ после ./gradlew run
Папка build/ — это место, куда Gradle складывает результаты своей работы. Представьте, что src/ — это «ингредиенты и рецепт», а build/ — «кухня после готовки»: там лежат подготовленные файлы, промежуточные результаты и готовые артефакты. Важно: кухня может быть разгромлена, но рецепт от этого не должен страдать.
Когда вы делаете ./gradlew run, Gradle обычно, если нужно, сначала компилирует код и обрабатывает ресурсы, а потом запускает приложение. И результаты этой подготовки вы увидите именно в build/.
Примерно так (упрощённо) выглядит то, что вам важно уметь «узнавать в лицо»:
build
├─ classes/java/main <- скомпилированные .class файлы
├─ resources/main <- обработанные ресурсы
└─ libs <- сюда обычно попадает jar
Полезная привычка: не пытайтесь «понять сборку по ощущениям» — проверяйте следы в build/. Если вы запустили classes, а у вас нет build/classes/java/main, значит задача не отработала. Если ресурсы лежат в src/main/resources, но не появились в build/resources/main, значит проблема где-то в обработке ресурсов.
Чтобы не держать всё в голове, удобно использовать маленькую таблицу:
| Что ищем | Где обычно лежит | Откуда берётся | Что это означает |
|---|---|---|---|
| Скомпилированные классы | build/classes/java/main | после compileJava (внутри classes) | Java-код успешно прошёл компиляцию |
| Ресурсы | build/resources/main | после processResources (внутри classes) | файлы из src/main/resources попали в classpath на запуске |
| Архив приложения | build/libs/*.jar | после jar | проект упакован в один .jar файл |
И ещё один принцип, который часто спасает нервы: папка build/ — не источник истины. Если вы отредактировали файл в build/resources/main, это почти наверняка пропадёт при следующей сборке. Править нужно исходники в src/, а build/ воспринимать как одноразовый результат.
3. Сборка JAR: файл и расположение
Слово jar пугает только первые два раза. На деле это просто архив (по сути zip), в котором лежит результат вашего проекта. Но здесь важно держать границу: обычный jar, который Gradle собирает этой задачей, — это упаковка вашего кода, ресурсов и служебных метаданных. Он не обязан быть самодостаточным архивом со всеми внешними библиотеками.
Это один из способов сказать: «вот результат сборки в упаковке, его можно передавать как файл».
Собрать jar в Gradle-проекте можно командой:
./gradlew jar
После этого загляните в папку build/libs. Там появится .jar-файл. Его имя обычно зависит от настроек проекта и часто выглядит как имяПроекта-версия.jar. Если вы ещё не задавали версию, Gradle может подставить что-то вроде unspecified — это не ошибка, просто версия пока не определена.
Чтобы не спорить с реальностью, можно сделать так: сначала собрать, потом посмотреть, что получилось:
# Собираем jar-архив приложения
./gradlew jar
# Проверяем, какой файл появился
ls build/libs
(На Windows вместо ls обычно используют dir — смысл тот же: увидеть имя файла.)
И здесь важно не перепутать два разных смысла:
- ./gradlew run — это запуск приложения через Gradle (с правильным classpath и зависимостями).
- ./gradlew jar — это упаковка результата в архив. Это не «ещё один run», а другой продукт сборки.
4. Проверяем содержимое jar и ресурсы
Когда вы впервые собираете jar, возникает естественный вопрос: «А что там внутри? Я точно собрал то самое?» Хорошая новость: проверить это можно без гаданий на кофейной гуще.
Есть простая команда, которая показывает содержимое архива:
# Смотрим содержимое jar-архива (подставьте своё имя файла)
jar tf build/libs/readlater-starter.jar
Имя файла у вас может отличаться, так что подставьте своё из build/libs. Команда jar tf выведет список файлов внутри архива. Там вы должны увидеть как минимум:
- ваш класс ReadLaterApplication в виде .class-файла;
- ваши ресурсы (например, banner.txt, если вы добавляли его в src/main/resources).
Выглядеть это будет примерно так (фрагмент):
META-INF/
META-INF/MANIFEST.MF
com/example/readlater/ReadLaterApplication.class
banner.txt
Если вы видите banner.txt внутри jar, значит поймали очень важный момент: ресурс стал частью артефакта. То есть приложение может прочитать этот файл не потому, что рядом есть папка src/, а потому что ресурс упакован в итоговый результат и доступен через classpath.
С точки зрения кода здесь важен один вызов, который у нас уже живёт в ReadLaterApplication:
ReadLaterApplication.getResourceAsStream("/banner.txt")
Для JVM не так важно, лежит banner.txt в build/resources/main во время ./gradlew run или уже внутри jar после упаковки. В обоих случаях код обращается к ресурсу через classpath, а не через путь src/main/resources/....
5. Запуск без IDE: Gradle run и java -cp
Теперь разделим две похожие, но разные вещи.
./gradlew run — это запуск в разработке. Gradle сам собирает classpath, подхватывает ресурсы, добавляет зависимости на запуске и вызывает mainClass.
java -cp ... — это прямой запуск JVM. Здесь classpath задаёте уже вы сами, и JVM знает только то, что вы ей явно передали.
Такой прямой запуск удобно показывать на самом маленьком сценарии, где приложению хватает собственного кода и ресурса banner.txt. Как только в деле появляются внешние библиотеки, одного обычного jar из build/libs уже мало: JVM должна увидеть и эти зависимости тоже.
Через Gradle запуск выглядит так:
./gradlew run
А напрямую через JVM — так:
# Запуск main-класса напрямую из jar через classpath
java -cp build/libs/readlater-starter.jar com.example.readlater.ReadLaterApplication
На таком минимальном примере оба варианта могут дать один и тот же результат. Но граница между ними важна: ./gradlew run собирает нужный classpath автоматически, а при прямом запуске ответственность за classpath уже на вас.
То же самое и с аргументами запуска. Через Gradle:
./gradlew run --args="hello world"
И напрямую через JVM:
java -cp build/libs/readlater-starter.jar com.example.readlater.ReadLaterApplication hello world
В обоих случаях аргументы прилетают в тот же String[] args. Как только различие между run, обычным jar и прямым запуском через JVM становится понятным, гораздо легче увидеть, где именно сломалась сборка: в упаковке артефакта, в classpath или уже в самом коде.
6. Типичные ошибки при работе с build/ и jar
Ошибка №1: редактировать файлы в build/, потому что «так быстрее».
Это очень соблазнительно: вы нашли build/resources/main/banner.txt, поправили текст — и он тут же изменился. А потом вы запускаете ./gradlew clean или просто следующую сборку, и изменения исчезают. Причина простая: build/ — это не исходники, а результат. Лечится привычкой править только src/main/java и src/main/resources.
Ошибка №2: считать, что jar — это «запуск», а run — это «тоже запуск, но по-другому».
На практике это две разные цели. run — это удобно запустить приложение в разработке. jar — это упаковать результат. Если держать в голове вопрос «я сейчас хочу запустить или получить артефакт?», путаница уходит почти сразу.
Ошибка №3: удивляться, что jar не содержит зависимости, и ждать от него поведения «как у одного-единственного файла на всё».
Это нормальный шок новичка: «я же подключил библиотеки в Gradle, почему их нет внутри jar?» Потому что стандартный jar — это упаковка вашего кода и ресурсов. То, что зависимости подтягиваются при run, — заслуга Gradle, который формирует classpath. Пока нам важно зафиксировать базовую картину: jar — не волшебная коробка, а конкретный формат.
Ошибка №4: искать артефакты не там, где они реально лежат.
Когда сборка прошла, но вы «не видите jar», обычно дело не в мистике, а в том, что вы смотрите не в build/libs. Точно так же с классами и ресурсами: если вы не понимаете, появились ли они, проверьте build/classes/java/main и build/resources/main. Это банально, но экономит часы.
Ошибка №5: доказывать себе, что всё воспроизводимо, только запуском из IDE.
IDE — отличный инструмент, но её конфигурация живёт на вашей машине. Воспроизводимость проверяется простой вещью: «закрываю IDE, открываю терминал, запускаю ./gradlew run — и проект стартует». Если это работает, значит проект действительно воспроизводим, а не «работает только у автора на ноутбуке».
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ