Рубрики
Без рубрики

Сборка, компиляция, запуск: Ускоренный курс в пути к классам

В котором мы узнаем о различных путях к классам, важных для создания и запуска ваших проектов Java/Kotlin/Android. С тегами java, android, gradle.

Я называю это своим “ученичеством в классе”. “Однажды на работе я провел год, работая в Gradle, и имел прямой доступ к некоторым из Величайших Умов нашего поколения. Я вернулся с той вершины горы, неся с собой секрет: все дело в классной дорожке. На самом деле, все является путем к классу. Твоя собака? Классная дорожка, состоящая из лая, слюней и какашек.

javac -classpath barks.jar:drool.jar:poop.jar Dog.java

Ладно, возможно, это преувеличение. 1 Но это показывает, как трудно переоценить важность classpath в Java и разработке, связанной с Java: они действительно повсюду и имеют основополагающее значение. Мне потребовалось четыре года, чтобы начать понимать их, и еще два, чтобы написать этот пост, чтобы вы могли потратить семь минут на его чтение, попивая утренний кофе и избегая того, чтобы аналитический билет злобно смотрел на вас с доски Jira.

Для кого это

Если вы пишете код на Java или любом языке JVM, таком как Kotlin или Groovy, и независимо от того, ориентируетесь ли вы на JVM или Android, этот пост может быть вам полезен. Понимание пути к классам может помочь вам понять и избежать грубых ошибок и, поскольку это углубляет ваше понимание языков JVM, поможет вам стать более эффективным программистом.

Я буду использовать примеры из Android и Gradle, потому что это домены, которые я знаю лучше всего, и сделаю примеры проще (для меня). Мы также рассмотрим некоторые сканы сборки Gradle, так как они являются очень хорошим инструментом для визуализации путей к классам.

Что будет охвачено

В этом посте основное внимание уделяется основам пути к классам и загрузке классов в Java, а в будущих постах планируется конкретно рассказать о путях к классам во время сборки, во время компиляции и во время выполнения.

Что такое путь к классу?

Пути к классам – это то, как вы сообщаете инструменту SDK 2 (например, java или javac ), или инструмент сборки (например, Ant, Maven или Gradle), который обертывает инструмент SDK, где можно найти сторонние или пользовательские классы которые не являются расширениями или частью основного JDK . ( официальные документы )

Сторонние классы обычно называются библиотеками или зависимостями, обычно доступными в виде jar. Определяемые пользователем классы – это классы, написанные вами и вашей командой, то есть вашим приложением. Сравните это с основными классами платформы Java, такими как java.lang. Строка , которые являются частью JDK и их не нужно указывать в пути к классу. (Вы можете думать об этих классах платформы как о находящихся в пути к классам boot .)

Как мы рассмотрим более подробно ниже, пути к классам – это просто упорядоченные списки файлов jar и каталогов, содержащих файлы классов. Напоминая о нашем Dog.java пример выше, путь к классу для этой операции компиляции буквально состоит из трех указанных файлов jar, в порядке: barks.jar, drool.jar, poop.jar .

Конечно, как современные разработчики JVM, мы редко, если вообще когда-либо, взаимодействуем напрямую с такими инструментами, как javac (компилятор Java); мы используем “инструменты сборки” для организации наших все более сложных сборок. Точно так же мы обычно не указываем путь к классу напрямую; скорее, мы “объявляем зависимости. “”Управление зависимостями и их разрешение – это невероятно сложная тема сама по себе, и мы будем в основном обсуждать ее. Мы скажем только следующее: когда вы объявляете зависимость в сценарии сборки gradle, инструмент рассматривает это как инструкцию для устранения этой зависимости (часто включающую загрузку из Интернета), в конечном итоге один или несколько файлов jar, представляющих эту “зависимость”, попадают в путь к классу. Если вы, например, объявите следующее, взятое из типичного проекта Android:

dependencies {
  implementation 'androidx.appcompat:appcompat:1.1.0'
}

тогда наиболее вероятный результат 3 |/из этого следует, что библиотека Android appcompat v1.1.0, представленная в виде файла jar, 4 заканчивается на ваших путях к классам во время компиляции и во время выполнения. 5 Кроме того, все зависимости appcompat (а также их зависимости), также называемые транзитивными зависимостями, будут добавлены в те же пути к классам. Ниже приведен фрагмент сканирования сборки , показывающий зависимости верхнего уровня appcompat:

Как отмечалось выше, мы не будем углубляться в сложности механизма разрешения зависимостей gradle, поэтому, если вы хотите больше узнать об этих первых трех зависимостях с меткой ограничение , см. документы .

В то время как сканирование сборки показывает зависимости, организованные в древовидную структуру, когда gradle фактически вызывает соответствующие задачи компиляции, это дерево сглаживается в простой список.

Подводя итог:

Объявление зависимости функционально эквивалентно добавлению одной или нескольких банок в один или несколько путей к классам.

Чтобы понять, как пути к классам влияют на среду выполнения Java, мы должны поговорить о загрузке классов. Давайте сделаем это сейчас.

Загрузчики классов загружают классы

Для отличного ознакомления с загрузкой классов и классом ClassLoader я рекомендую этот учебник от Baeldung . Я кратко изложу здесь основные моменты.

Существует три встроенных загрузчика классов, используемых приложениями Java:

  1. Загрузчик классов начальной загрузки. Используется для загрузки классов JDK, таких как java.lang. Строка и java.util. Список массивов .
  2. Загрузчик классов расширения. Используется для загрузки классов расширений (за рамками этого поста).
  3. Загрузчик приложений или системных классов. Используется для загрузки сторонних и пользовательских классов. Это загрузчик классов, настроенный с помощью пользовательского пути к классам.

Вы также можете определить пользовательские загрузчики классов во время выполнения.

Следующий простой пример (адаптированный из учебника по строительству) демонстрирует первый и третий типы:

public class Main {
  public static void main(String... args) {
    System.out.println("Main class loader = " + Main.class.getClassLoader());
    System.out.println("String class loader = " + String.class.getClassLoader());
  }
}

Запуск этой программы приводит к такому результату:

Main class loader = sun.misc.Launcher$AppClassLoader@7852e922
String class loader = null

Обратите особое внимание, что Строка загрузчик классов отображается как null , что указывает на то, что это загрузчик классов начальной загрузки. Поскольку этот загрузчик классов написан в машинном коде, он не представлен как класс Java.

Вы можете скомпилировать и запустить эту программу, выполнив javac Main.java && java Основной .

Эти загрузчики классов организованы в иерархию, при этом загрузчик классов начальной загрузки функционирует как родительский загрузчик классов расширения, который сам является родительским загрузчиком классов приложений. Загрузчики классов делегируют полномочия своим родителям, когда их просят загрузить какой-либо класс. Эти родители делегируют полномочия своим родителям и так далее. Если родитель не может загрузить класс, непосредственный потомок затем пытается его загрузить и так далее, пока в конечном итоге класс не будет загружен или не будет вызвано исключение ClassNotFoundException или NoClassDefFoundError , в зависимости от .

Каждый Класс экземпляр имеет ссылку на Загрузчик классов , который его загрузил, и который извлекается методом Class.getClassLoader() .

Определенный вами путь к классам используется для настройки загрузчика классов приложения, который использует эту информацию для загрузки классов, запрошенных вашей программой во время выполнения.

Пути к классам зависят от порядка и допускают дублирование записей

Теперь мы знаем, как путь к классам влияет на Java или смежное с Java приложение: он указывает загрузчику классов приложений, как находить сторонние библиотеки и пользовательские классы. Таким образом, путь к классам влияет на среду выполнения Java на очень глубоком уровне.

Интересная особенность Java заключается в том, что она удивительно динамична, если злоупотреблять сильно перегруженным термином. Например, небольшое изменение пути к классу может привести к радикально отличающемуся поведению между двумя последовательными операциями.

Путь к классу можно представить как упорядоченный список элементов, которые предоставляют файлы class и/или jar, которые являются коллекциями файлов class и ресурсов. Эти файлы классов используются для таких операций, как организация сборки, компиляция или запуск вашего проекта.

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

javac -classpath barks.jar:drool.jar:poop.jar Dog.java

У нас были

javac -classpath drool.jar:barks.jar:poop.jar Dog.java

?

Может быть, ничего и не изменилось бы. Если, однако, drool.jar и barks.jar имели перекрывающиеся файлы классов (либо из-за неправильной упаковки, либо промежуточного шага в более крупном рефакторинге, либо плохой архитектуры и т. Д.), То в первом случае мы использовали бы файлы классов из barks.jar , а во втором случае файлы классов из drool.jar , и они вполне могут быть разными. Подразумевается, что пути к классам также допускают дублирование записей: инструменты SDK просто игнорируют каждую запись, которая дублирует ту, которая появляется ранее в списке.

Важный. Gradle гарантирует, что порядок путей к классам является детерминированным.

Пути к классам зависят от порядка, и в следующем посте мы собираемся использовать это на примере домена сборки.

Заканчиваем (на данный момент)

В этом посте мы узнали, что такое путь к классам; что он используется средой выполнения Java для настройки загрузчика классов приложений; что загрузчики классов организованы в иерархию с загрузчиком загрузочных классов в корне; и что пути к классам зависят от порядка, что имеет глубокие последствия для поведения ваших программ во время выполнения (включая build программу).

Имея за плечами эти основы, теперь мы можем приступить к изучению более практических, реальных примеров из областей сборки, компиляции и запуска. Тогда до встречи!

Особая благодарность

Благодаря Сесар Пуэрта за рассмотрение нескольких проектов этого поста и предоставление отличной и тщательной обратной связи. Однако любые ошибки – мои собственные!

Примечания в конце

1 Очевидно ли, что я любитель кошек? вверх 2 Этот термин искусства заимствован из документов Oracle. вверх 3 Я сказал, что это было сложно, хорошо? вверх 4 Я понимаю, что appcompat на самом деле упакован как aar, но одна из вещей, которую делает AGP, – это распаковать этот aar и поместить банку внутри него в путь к классам. вверх 5 Мы обсудим, почему это происходит более чем в одном классе, в следующем посте. вверх

Оригинал: “https://dev.to/autonomousapps/build-compile-run-a-crash-course-in-classpaths-f4g”