Перейти к основному содержимому

5.03. Структуры проекта

Разработчику Архитектору

Структуры проекта

Что такое пакет?

Пакетом (пространством имен) в Java называется структура вложенных по какому-то признаку папок с размещенными в них классами (интерфейсами, перечислениями, аннотациями), необходимыми проекту.

В Java используется пакетная структура – способ организации классов в папках, который помогает избежать конфликтов имён и группировать код логически. Это обязательная структура. Чтобы система понимала, что означает команда, указанная программистом в исходном коде в виде слова, нужно сообщить системе об использовании пакета, который содержит код-содержание этого слова:

«Дружище, налей мне молока!»
«А где мне взять молоко?».

Мы, люди, многие элементарные вещи уже изначально автоматизировали в себе и понимаем подсознательно. Мы прекрасно знаем, что молоко в холодильнике. Но программе нужно показать, что нам понадобится холодильник. Поэтому общение с программой будет как-то так:

«Дружище, запомни – вот холодильник. Там есть молоко».
«Дружище, налей мне молока!»
«Ок, вижу, молоко в холодильнике. Сейчас сделаю».

Вот поэтому код библиотеки или проекта формируется в составе пакета (package).

Пакетная структура решает две задачи: предотвращает конфликты имён между классами из разных источников и группирует связанный код для удобства навигации и поддержки.


Структура пакетов

Каждый Java-файл начинается с директивы package, указывающей полный путь к пакету. Компилятор использует этот путь для размещения скомпилированных .class файлов в соответствующих подкаталогах. Без указания пакета класс попадает в безымянное пространство имён, что допустимо только для простых учебных программ.

Физическая структура каталогов соответствует логическому имени пакета. Для класса с объявлением package com.example.app; компилятор ожидает размещение исходного файла по пути com/example/app/ClassName.java. Обратный слеш в имени пакета заменяется на разделитель каталогов операционной системы.

Имена пакетов строятся по обратному порядку домена организации, и хранятся в строгой структуре:

  • com/mycompany/project/Main.java – физический путь;
  • com.mycompany.project – путь в коде.

com – корень пакета. Только строчные буквы. Это префикс домена, обычно обратный интернет-домен компании или проекта. Нужен для уникальности пакетов в мире. Можно указывать своё, но рекомендуется использовать стандарт:

  • com – для коммерческих проектов;
  • org – для открытых проектов;
  • net, io, ru – тоже допустимы.

mycompany – название компании или автора, допустим, имя (если проект личный). Желательно делать уникальным. Только строчные буквы, несмотря на то что это будет имя или название – допустим, не Timur, а timur.

project – название проекта или модуля, библиотеки, подсистемы. Допустим, calculator, utils или как-то так. Только строчные буквы, название может быть любым.

Такой подход гарантирует глобальную уникальность имён пакетов. Два разработчика из разных компаний могут создать класс User, но их полные имена com.companyA.app.User и org.companyB.tool.User никогда не пересекутся.

Современные Java-проекты используют системы сборки Maven и Gradle. Это инструменты, которые автоматизируют компиляцию кода, управление зависимостями (библиотеками), запуск тестов, создание готовых приложений и развёртывание (деплой).


Maven

Как работает Maven?

Maven строится вокруг концепции Project Object Model (POM) — описания проекта в XML-формате. Файл pom.xml содержит метаданные, зависимости, плагины и настройки сборки. Проект следует стандартной структуре каталогов, что позволяет инструменту автоматизировать все этапы разработки без дополнительной конфигурации.

pom.xml содержит:

  • метаданные (название, версия, автор);
  • зависимости (библиотеки);
  • плагины (для сборки, тестирования);
  • настройки профилей (dev, prod).

POM является базовым модулем Maven. Это специальный XML-файл, который всегда хранится в базовой директории проекта и называется pom.xml. Файл POM содержит информацию о проекте и различных деталях конфигурации, которые используются Maven для создания проекта.

Maven делегирует компиляцию плагину maven-compiler-plugin. При выполнении фазы compile плагин:

  1. Сканирует каталог src/main/java рекурсивно
  2. Формирует список .java файлов
  3. Вызывает компилятор javac с параметрами:
    • -source и -target из свойств проекта
    • -encoding из project.build.sourceEncoding
    • -classpath на основе разрешённых зависимостей
  4. Размещает .class файлы в target/classes с сохранением структуры пакетов

Компиляция тестов происходит отдельно через фазу test-compile с использованием каталога src/test/java и размещением результатов в target/test-classes. Класспат для тестов включает основные классы и зависимости с областью test.


pom.xml

Базовый файл pom.xml содержит следующие секции:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">

<!-- Версия модели POM -->
<modelVersion>4.0.0</modelVersion>

<!-- Координаты проекта -->
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<!-- Общие свойства -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<!-- Зависимости -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<!-- Плагины для расширения функционала -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Секция <properties> задаёт переменные, используемые в других частях файла. Плагины в секции <build> расширяют стандартный жизненный цикл: компилятор настраивает версию Java, Surefire запускает тесты, Assembly создаёт исполняемые JAR-файлы.


Фазы Maven

Maven работает по жизненному циклу сборки проекта, который состоит из фаз. Каждая фаза представляет собой этап в процессе создания артефакта:

  • validate - проверяет корректность проекта и наличия необходимых данных;
  • compile - компилирует исходный код проекта;
  • test – запускает юнит-тесты;
  • package - упаковывает скомпилированный код в JAR/WAR/SO и т.д.;
  • verify - выполняет проверки после упаковки перед установкой;
  • install - устанавливает артефакт в локальное хранилище Maven;
  • deploy - передаёт артефакт в удалённый репозиторий (например, Nexus, Artifactory).

Можно вызвать одну из этих фаз через команды, к примеру:

mvn compile        # компиляция
mvn package # сборка артефакта
mvn install # установка в локальный репозиторий

Выполнение команды mvn package запускает все предшествующие фазы в порядке их следования. Пропустить тесты можно флагом -DskipTests.

Файлы проекта распределены по строгой стандартной структуре:

ПутьНазначение
/ПроектОбщая папка проекта
/Проект/pom.xmlГлавный конфигурационный файл (Maven)
/Проект/srcДиректория, где хранится весь исходный код Java-проекта
/Проект/src/mainОсновной код проекта
/Проект/src/main/javaИсходные файлы Java, организованные в пакетной структуре
/Проект/src/main/java/com/mycompany/projectПример пакета в проекте
/Проект/src/main/resourcesРесурсы: конфигурационные файлы, локализация, изображения, CSS, JS и другие не-кодовые файлы
/Проект/src/testТестовый код, не включаемый в финальную сборку
/Проект/src/test/javaТестовые классы (например, с использованием JUnit, TestNG)
/Проект/src/test/resourcesКонфигурация и ресурсы, используемые при тестировании
/Проект/targetКаталог для автоматически генерируемых файлов (сборка, артефакты, временные файлы)

Структура каталогов Maven

my-app/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── app/
│ │ │ └── Main.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── app/
│ │ └── MainTest.java
│ └── resources/
│ └── test-config.properties
└── target/
├── classes/
├── test-classes/
└── my-app-1.0.0.jar

Каталог target создаётся автоматически при сборке и содержит все промежуточные и финальные артефакты. Его содержимое можно удалить командой mvn clean.


Управление зависимостями в Maven

Maven автоматизирует загрузку библиотек через централизованную систему координат. Каждая зависимость описывается тремя атрибутами:

  • groupId — организация или домен владельца библиотеки
  • artifactId — имя библиотеки
  • version — конкретная версия

Пример объявления зависимости в pom.xml:

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>

При первой сборке Maven проверяет локальный репозиторий ~/.m2/repository. Если артефакт отсутствует, инструмент обращается к центральному репозиторию по адресу https://repo.maven.apache.org/maven2, скачивает библиотеку и все её транзитивные зависимости, сохраняя их локально. Последующие сборки используют кэшированные файлы без сетевых запросов.

Транзитивные зависимости разрешаются автоматически. Если библиотека A зависит от B версии 2.0, а проект напрямую использует B версии 1.5, Maven применяет правила разрешения конфликтов для выбора единой версии.

Maven разрешает зависимости в три этапа:

  1. Анализ секции <dependencies> в pom.xml
  2. Рекурсивное извлечение транзитивных зависимостей из метаданных артефактов
  3. Применение правил разрешения конфликтов версий:
    • Ближайший путь в дереве зависимостей
    • Явное указание версии в <dependencyManagement>
    • Порядок объявления в родительском POM

Зависимости классифицируются по области действия (scope):

  • compile — доступны во время компиляции и выполнения (значение по умолчанию)
  • test — только для тестов, не попадают в финальный артефакт
  • runtime — не нужны для компиляции, требуются при запуске
  • provided — предоставляются окружением выполнения (например, сервлеты в веб-контейнере)

Артефакт Maven

Артефакт в Maven – это результат сборки проекта, например, JAR-файл, который содержит код, библиотеки, ресурсы и метаданные.

Каждый артефакт имеет уникальный идентификатор, состоящий из элементов:

  • groupId – com.example – организация или домен;
  • artifactId – my-app – имя проекта или библиотеки;
  • version – 1.0.0., 2.1.3-SNAPSHOT – версия.

Пример:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>

Это будет ссылка на артефакт:

org.springframework.boot:spring-boot-starter-web:2.7.0

Артефакты бывают следующих видов:

  • .jar – обычные Java-библиотеки;
  • .war – веб-приложения;
  • .pom – описание проекта;
  • .aar – Android-библиотеки..

Репозиторий

Репозиторий – это место, где хранятся артефакты. Они могут храниться локально (локальный репозиторий), в общедоступном репозитории Maven (центральный), и в удалённом (частном), для приватных артефактов.

При первом использовании зависимости Maven скачивает её из центрального репозитория, и сохраняет в локальный. При повторном использовании уже берёт из локального. То есть, добавляя зависимость в pom.xml, мы скачиваем и сохраняем локально в папку ~/.m2/repository.


Пошаговое создание проекта Maven

  1. Установите Apache Maven и убедитесь, что команда mvn доступна в терминале.
  2. Выполните генерацию шаблона проекта:
    mvn archetype:generate \
    -DgroupId=com.example \
    -DartifactId=my-app \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DinteractiveMode=false
  3. Перейдите в созданную директорию:
    cd my-app
  4. Откройте файл src/main/java/com/example/App.java и замените содержимое:
    package com.example;

    public class App {
    public static void main(String[] args) {
    System.out.println("Привет из Maven!");
    }
    }
  5. Соберите проект:
    mvn package
  6. Запустите приложение:
    java -cp target/my-app-1.0.0.jar com.example.App

Команда mvn package компилирует исходный код, запускает тесты и формирует JAR-файл в каталоге target. Maven автоматически вызывает компилятор javac с правильными путями к исходникам и зависимостям, управляет класспатом и генерирует манифест для артефакта.


Gradle

Как работает Gradle?

Gradle – система автоматической сборки, построенная с учетом принципов Maven, но предоставляющая дополнительные возможности на языках Groovy и Kotlin вместо традиционной XML-образной формы представления конфигурации проекта. Это позволяет писать логику сборки как обычный код: использовать переменные, циклы, условия и функции.

Основные компоненты проекта Gradle:

  • build.gradle или build.gradle.kts — основной файл сборки
  • settings.gradle — настройки мультипроектной структуры
  • gradle/ — обёртка Gradle для кроссплатформенного запуска без глобальной установки

Gradle вводит концепцию задач (tasks) как атомарных единиц работы. Задачи могут зависеть друг от друга, формируя направленный ациклический граф выполнения. Инкрементальная сборка анализирует изменения входных файлов и пересобирает только затронутые модули, что ускоряет повторные сборки.

Принцип разделения на src/main и src/test аналогичен Maven, но конфигурация хранится в build.gradle, а не в pom.xml. Отличие есть в папке для автоматически сгенерированных файлов – в Maven они хранятся по пути /target, а в Gradle - /build.

Gradle использует задачу compileJava из плагина java. Процесс аналогичен Maven, но с дополнительными оптимизациями:

  1. Инкрементальная компиляция — анализ изменений на уровне классов
  2. Демон компиляции — фоновый процесс, сохраняющий состояние между запусками
  3. Параллельная компиляция — использование нескольких ядер процессора

Настройка компилятора происходит через блок toolchain, который автоматически выбирает установленную JDK подходящей версии или загружает её при отсутствии. Это устраняет зависимость от переменной окружения JAVA_HOME.


build.gradle

Конфигурация на Groovy DSL:

plugins {
id 'java'
id 'application'
}

group = 'com.example'
version = '1.0.0'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

application {
mainClass = 'com.example.App'
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
testImplementation 'junit:junit:4.13.2'
}

Конфигурация на Kotlin DSL (build.gradle.kts):

plugins {
java
application
}

group = "com.example"
version = "1.0.0"

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

application {
mainClass.set("com.example.App")
}

repositories {
mavenCentral()
}

dependencies {
implementation("org.apache.commons:commons-lang3:3.12.0")
testImplementation("junit:junit:4.13.2")
}

Оба формата эквивалентны по функционалу. Kotlin DSL обеспечивает строгую типизацию и автодополнение в IDE, Groovy DSL — более лаконичный синтаксис.


Сборка в Gradle

Команда gradle build запускает стандартный жизненный цикл:

  1. classes — компиляция основного кода
  2. testClasses — компиляция тестов
  3. test — выполнение тестов
  4. jar — создание JAR-артефакта
  5. assemble — агрегация всех артефактов

Результаты сохраняются в каталоге build:

build/
├── classes/
│ ├── java/main/ # скомпилированные классы
│ └── java/test/ # скомпилированные тесты
├── libs/
│ └── my-app-1.0.0.jar
├── reports/tests/ # отчёты о тестировании
└── tmp/ # временные файлы

Gradle отслеживает состояние входных и выходных файлов каждой задачи. При повторной сборке без изменений исходного кода задачи помечаются как актуальные (UP-TO-DATE) и пропускаются.

Gradle использует конфигурации вместо областей действия:

  • implementation — зависимости для компиляции и выполнения, скрыты от потребителей библиотеки
  • api — зависимости, видимые потребителям (для библиотек)
  • testImplementation — зависимости только для тестов
  • runtimeOnly — зависимости только для выполнения

Преимущество подхода — контроль видимости транзитивных зависимостей. При использовании implementation зависимости не «утекают» в проекты, использующие вашу библиотеку, что снижает риск конфликтов версий.

Разрешение зависимостей происходит лениво — только при выполнении задач, требующих класспат. Это ускоряет запуск Gradle для операций, не связанных со сборкой (например, gradle tasks).


Запуск проекта Gradle

Gradle предоставляет несколько способов запуска приложения:

  • Через задачу run плагина application:
    gradle run
  • Через обёртку (без глобальной установки Gradle):
    ./gradlew run          # Linux/macOS
    gradlew.bat run # Windows
  • Создание исполняемого JAR с манифестом:
    jar {
    manifest {
    attributes 'Main-Class': 'com.example.App'
    }
    }
    Запуск после сборки:
    java -jar build/libs/my-app-1.0.0.jar

Обёртка gradlew скачивает указанную версию Gradle при первом запуске и использует её для всех последующих сборок, гарантируя воспроизводимость независимо от окружения разработчика.


Сравнение Maven и Gradle

Краткое сравнение Maven и Gradle

КритерийMavenGradle
Тип конфигурацииXMLGroovy/Kotlin DSL
Файл конфигурацииpom.xmlbuild.gradle

Команды в Maven и Gradle

КритерийMavenGradle
Компиляцияmvn compilegradle compileJava
Запуск тестовmvn testgradle test
Сборка JAR/WARmvn packagegradle build
Установка в локальный кэшmvn installgradle publishToMavenLocal
Очистка кэшаmvn cleangradle clean
Запуск приложенияmvn exec:javagradle run
Пропустить тестыmvn install -DskipTestsgradle build -x test
Обновить зависимостиmvn dependency:resolvegradle dependencies
Создать проектmvn archetype:generategradle init
Показать список задачmvn help:describegradle tasks

Аналогичные секции в pom.xml и build.gradle

MavenGradle
<groupId>group
<artifactId>Имя проекта (из settings.gradle)
<version>version
<properties>Переменные в ext {}
<dependencies>dependencies {}
maven-compiler-pluginjava.toolchain

Gradle стал стандартом для:

  • Android-разработки — официальная система сборки Android Studio
  • Spring Boot — генератор проектов start.spring.io предлагает Gradle по умолчанию
  • Мультипроектных систем — поддержка иерархии проектов с общими зависимостями
  • Кастомных сценариев — публикация в репозитории, генерация документации, деплой на серверы

Гибкость скриптов позволяет интегрировать любые внешние инструменты: запускать утилиты командной строки, обрабатывать файлы, взаимодействовать с веб-сервисами.


Структура проекта Gradle

my-app/
├── build.gradle # или build.gradle.kts
├── settings.gradle
├── gradlew
├── gradlew.bat
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── App.java
│ │ └── resources/
│ └── test/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── AppTest.java
│ └── resources/
└── build/ # генерируется автоматически

Каталог gradle/wrapper содержит обёртку для кроссплатформенного запуска. Файл gradle-wrapper.properties указывает версию Gradle:

distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip

Пошаговое создание проекта Gradle

  1. Установите Gradle или используйте обёртку из другого проекта.
  2. Инициализируйте новый проект:
    gradle init --type java-application \
    --project-name my-app \
    --package com.example \
    --dsl groovy
  3. Система предложит выбрать вариант тестового фреймворка (JUnit 4, JUnit 5, TestNG). Выберите JUnit Jupiter (JUnit 5).
  4. Откройте сгенерированный файл src/main/java/com/example/App.java:
    package com.example;

    public class App {
    public String getGreeting() {
    return "Привет из Gradle!";
    }

    public static void main(String[] args) {
    System.out.println(new App().getGreeting());
    }
    }
  5. Соберите и запустите проект:
    ./gradlew build
    ./gradlew run

Gradle автоматически скачает зависимости из mavenCentral(), скомпилирует код с использованием JDK 17 (согласно настройкам toolchain), запустит тесты и выполнит основной класс. Все этапы логируются с указанием затраченного времени и статуса каждой задачи.


Работа с Java без систем сборки

Разработка на Java возможна без Maven и Gradle.

Минимальный рабочий процесс:

  1. Создайте структуру каталогов вручную:
    mkdir -p src/com/example/app
    mkdir -p bin
  2. Напишите класс src/com/example/app/Main.java:
    package com.example.app;

    public class Main {
    public static void main(String[] args) {
    System.out.println("Прямая компиляция");
    }
    }
  3. Скомпилируйте вручную:
    javac -d bin -sourcepath src src/com/example/app/Main.java
  4. Запустите:
    java -cp bin com.example.app.Main

Для проектов с зависимостями потребуется вручную скачивать JAR-файлы, формировать класспат через флаг -cp и отслеживать совместимость версий. Сборка артефактов потребует использования утилиты jar с ручным созданием манифеста.

Такой подход применим для учебных задач, скриптов или микропроектов. Для коммерческой разработки, командной работы и поддержки сложных систем ручное управление становится непрактичным из-за роста объёма рутинных операций и риска ошибок. Системы сборки автоматизируют повторяющиеся действия, обеспечивают воспроизводимость и интеграцию с инструментами непрерывной интеграции.