Java: Требуется ли ОБА компилятору и JRE доступ ко всем файлам классов сторонних производителей? - PullRequest
4 голосов
/ 03 апреля 2011

У меня 15-летний опыт работы с C ++, но я новичок в Java.Я пытаюсь понять, как отсутствие заголовочных файлов обрабатывается Java.У меня есть несколько вопросов, связанных с этой проблемой.

В частности, предположим, что я пишу исходный код для класса 'A', который импортирует сторонний класс 'Z' (и использует Z).Я понимаю, что во время компиляции компилятор Java должен иметь «доступ» к информации о Z, чтобы скомпилировать A.java, создав A.class.Следовательно, либо Z.java, либо Z.class (или JAR, содержащий один из них; скажем, Z.jar) должны присутствовать в локальной файловой системе во время компиляции - правильно?

Использует ли компилятор загрузчик классовзагрузить Z (повторить - во время компиляции)?

Если я прав, что загрузчик классов используется во время компиляции, что если пользовательский загрузчик классов (L) желателен и является частьюпроекта компилируется?Предположим, например, что L отвечает за загрузку Z.class AT RUNTIME по сети?В этом случае, как компилятор Java получит Z.class во время компиляции?Будет ли он пытаться сначала скомпилировать L, а затем использовать L во время компиляции, чтобы получить Z?

Я понимаю, что используя Maven для сборки проекта, Z.jar может быть расположен в удаленном хранилище через Интернет при компиляциивремя - либо в ibiblio, либо в пользовательском репозитории, определенном в файле POM.Надеюсь, я прав, что именно MAVEN отвечает за загрузку стороннего JAR-файла во время компиляции, а не JVM компилятора?

Обратите внимание, однако, что в RUNTIME A.class снова требуетZ.class - как JRE узнает, где можно загрузить Z.class (без помощи Maven)?Или разработчик несет ответственность за поставку Z.class вместе с A.class вместе с приложением (скажем, в файле JAR)?(... при условии, что определяемый пользователем загрузчик классов не используется.)

Теперь связанный вопрос, просто для подтверждения: я предполагаю, что после компиляции A.class содержит только символические ссылки на Z.class -байт-коды Z.class не являются частью A.class;Пожалуйста, поправьте меня, если я ошибаюсь.(В C ++ статическое связывание будет копировать байты из Z.class в A.class, тогда как динамическое связывание не будет.)

Еще один связанный с этим вопрос, касающийся процесса компиляции: если необходимые файлы, описывающие Z, расположены наCLASSPATH во время компиляции: требуется ли компилятору байт-коды из Z.class для компиляции A.java (и при необходимости создаст Z.class из Z.java) или для компилятора достаточно Z.java?

Мое общее замешательство можно резюмировать следующим образом.Кажется, что полный [байтовый] код для Z должен присутствовать ДВАЖДЫ - один раз во время компиляции и второй раз во время выполнения - и что это должно быть верно для ALL классов, на которые ссылается программа Java.Другими словами, каждый класс должен быть загружен / представлен ДВАЖДЫ.Ни один класс не может быть представлен во время компиляции как просто файл заголовка (как это может быть в C ++).

Ответы [ 6 ]

3 голосов
/ 03 апреля 2011

Лучший способ понять, как Maven вписывается в картину - это понять, что это (в основном) не подходит.

Maven НЕ УЧАСТВУЕТ в процессах, с помощью которых компилятор находит определения, или во время выполнения система загружает классы.Компилятор делает это сам по себе ... в зависимости от того, что говорит путь к классу во время сборки.К тому времени, когда вы запустите приложение, Maven больше не будет на картинке.

Во время сборки роль Maven заключается в проверке зависимостей проекта , объявленных в файлах POM, проверке версий, загрузке отсутствующих проектов, размещении JAR-файлов в известном месте и создании "classpath""для использования компилятором (и другими инструментами).

Затем компилятор «загружает» необходимые ему классы из этих файлов JAR для извлечения подписи типа информации в скомпилированных файлах классов.Для этого он не использует обычный загрузчик классов, но основной алгоритм определения местоположения такой же.

После того, как компилятор это сделал, Maven затем берет на себя упаковку в файлы JAR, WAR, EAR.и так далее, как указано в файлах POM.В случае файла WAR или EAR все необходимые зависимые файлы JAR, упакованные в файл.

Во время выполнения загрузка JAR, направленная Maven-файлами, не выполняется.Однако возможно, что запуск приложения может включать загрузку файлов JAR;например, если приложение развернуто с использованием Java WebStart.(Но в этом случае JAR-файлы не будут загружаться из репозитория Maven ...)

Еще несколько замечаний:

  • Maven не нужно чтобы быть на картинке вообще.Вы можете использовать IDE для сборки, инструмент сборки Ant (может быть, с Ivy), Make или даже «тупые» сценарии оболочки.В зависимости от механизма сборки вам может потребоваться обрабатывать внешние зависимости вручную;например, поиск внешних JAR-файлов для загрузки, где их разместить и т. д.

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

    Например, предположим, что класс A имеет метод, который использует класс B в качестве параметра, а класс B имеет метод, который использует класс C в качестве параметра.При компиляции A необходимо загрузить B, но не C (если A напрямую не зависит от C в некотором роде).При выполнении A необходимо загружать как B, так и C.

    Второй пример, предположим, что класс A зависит от интерфейса I с реализациями IC1 и IC2,Если A явно не зависит от IC1 или IC2, компилятору не нужно загружать их для компиляции A.

  • Также возможно динамически загружать классы пово время выполнения;например, вызывая Class.forName(className), где className является строковым выражением.


Вы написали:

Например, в вашем второмпулевая точка - я думаю, что разработчик мог бы предоставить во время компиляции файл-заглушку для B, который не включает метод B, использующий C, и A скомпилируется просто отлично.Это подтвердило бы мою оценку, что во время компиляции то, что можно было бы назвать «заголовочными» файлами только с объявленными необходимыми функциями (даже в качестве заглушек), полностью разрешено в Java - так что просто для удобства / соглашения инструменты развивались со временем, а неиспользовать различие заголовка / исходного файла.(Поправьте меня, если я ошибаюсь.)

Это не удобство / эволюционная вещь.Java НИКОГДА не поддерживает отдельные заголовочные файлы.Джеймс Гослинг и др. Исходили из того, что заголовочные файлы и препроцессоры были плохой идеей.

Ваша гипотетическая версия заглушки B будет иметь все видимые методы, конструкторы и поля реального B, а методы и конструкторы должны иметь тела. Заглушка B не компилируется иначе. (Я предполагаю, что в теории тела могут быть пустыми, возвращать фиктивное значение или генерировать непроверенное исключение.)

Проблема этого подхода в том, что он будет ужасно хрупким . Если вы допустили наименьшую ошибку в поддержании заглушки и полной версии B в действии, в результате загрузчик классов (во время выполнения) сообщит о фатальной ошибке.

Кстати, C и C ++ в значительной степени являются исключением из-за наличия отдельных заголовочных файлов. В большинстве других языков, которые поддерживают отдельную компиляцию (из разных файлов, входящих в приложение), компилятор может извлекать информацию об интерфейсе (например, подписи) из исходного кода реализации.

3 голосов
/ 03 апреля 2011

Использует ли компилятор загрузчик классов для загрузки Z (для повторения - во время компиляции)?

Почти.Он использует JavaFileManager, который во многих отношениях действует как загрузчик классов.На самом деле он не загружает классы, хотя он должен создавать сигнатуры классов из файлов .java, а также файлов .class.

Надеюсь, я прав, что именно MAVEN отвечает зазагрузка стороннего JAR-файла во время компиляции, а не JVM компилятора?

Да, Maven вытаскивает jar-файлы, хотя возможно реализовать JavaFileManager, который ведет себя как URLClassLoader.Maven управляет локальным кешем jar-файлов и заполняет этот кеш из сети по мере необходимости.

Еще один связанный с этим вопрос, касающийся процесса компиляции: как только необходимые файлы, описывающие Z, находятся на CLASSPATH во время компиляцииТребуется ли компилятору байт-коды из Z.class для компиляции A.java (и будет строить Z.class, если необходимо, из Z.java), или достаточно Z.java для компилятора?

Не требуется весь байт-код.Просто класс, метод, свойство подписи и метаданные.Если A зависит от Z, эта зависимость может быть удовлетворена с помощью Z.java, найденного в исходном пути, в Z.class, найденном в любом из (путь к классу, путь к системному классу), или через какое-то пользовательское расширение, такое как Z.jsp.

Мое общее замешательство можно резюмировать следующим образом.Кажется, что полный [байтовый] код для Z должен присутствовать ДВАЖДЫ - один раз во время компиляции и второй раз во время выполнения - и что это должно быть верно для ВСЕХ классов, на которые ссылается программа Java.Другими словами, каждый класс должен быть загружен / представлен ДВАЖДЫ.Ни один класс не может быть представлен во время компиляции как просто файл заголовка (как это может быть в C ++).

Может быть, пример может помочь прояснить это.Спецификация языка Java требует от компилятора определенных оптимизаций.Встраивание static final примитивов и String с.

Если класс A зависит только от B только для константы:

class B {
  public static final String FOO = "foo";
}

class A {
  A() { System.out.println(B.FOO); }
}

, тогда A можно скомпилировать, загрузить,и создается без B.class на пути к классам.Если вы изменили и отправили B.class с другим значением FOO, то у А все равно будет эта зависимость от времени компиляции.

Так что возможно иметь зависимость от времени компиляции, а не от времени соединениязависимость.

Конечно, возможно иметь зависимость времени выполнения без зависимости времени компиляции с помощью отражения.

Подводя итог, во время компиляции компилятор должен убедиться, что методы иСвойства, к которым обращается класс, доступны.

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

Эти различия можно стереть.Например, JSP использует специальный загрузчик классов, который вызывает компилятор java для компиляции и загрузки классов из исходного кода по мере необходимости во время выполнения.

1 голос
/ 03 апреля 2011

У меня 15-летний опыт работы с C ++, но я новичок в Java.

Самая большая проблема, с которой вы, вероятно, столкнетесь, состоит в том, что многие вещи, которые рассматриваются как важные в C ++, такие какsizeof () объект, целые числа без знака и деструкторы, нелегко сделать в Java и не имеют такого же значения и имеют другие решения / обходные пути.

Я пытаюсь понять, какОтсутствие заголовочных файлов обрабатывается Java.У меня есть несколько вопросов, связанных с этой проблемой.

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

Использует ли компилятор загрузчик классов для загрузки Z (для повторения - во время компиляции)?

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

именно MAVEN отвечает за загрузку стороннего JAR-файла при компиляциивремя, а не JVM компилятора?

Maven должен загрузить файл в локальную файловую систему, расположение по умолчанию: ~/.m2/repository

как JRE узнает, где скачатьZ.class (без помощи Maven)?

Он должен либо использовать Maven;Некоторые контейнеры OSGi могут загружать и выгружать разные версии динамически, например, вы можете изменить версию библиотеки в работающей системе или обновить SNAPSHOT из сборки maven.

Или у вас есть отдельное приложение;Используя плагин Maven, такой как appassembly, вы можете создать пакетный скрипт / shell-скрипт и каталог с копией всех необходимых вам библиотек.

Или веб-архив war, который содержит метаинформацию и множество jar-файлов внутри.(Это просто баночка с банками;)

Или разработчик должен отправить Z.class вместе с A.class с приложением

Для автономного приложенияда.

Теперь связанный вопрос, просто для подтверждения: я предполагаю, что после компиляции A.class содержит только символические ссылки на Z.class

Технически, это толькосодержит строки с Z, а не .class.Вы можете изменить много Z без компиляции A снова, и он все равно будет работать.Например, вы можете скомпилировать одну версию Z и заменить ее другой версией позже, и приложение все еще может работать.Вы даже можете заменить его во время работы приложения.;)

байт-коды Z.class не являются частью A.class;

Компилятор почти не оптимизируется.Единственное существенное ИМХО, это то, что он встроен в константы времени компиляции.Это означает, что если вы измените константу в Z после компиляции A, она не может измениться в A. (Если вы сделаете константу, неизвестную во время компиляции, она не будет встроена в нее)

Никакой байт-код не встроен,нативный код из байтового кода вставляется во время выполнения в зависимости от того, как на самом деле работает программа.Например, у вас есть виртуальные методы с N реализациями.Компилятор C ++ не знает, какие из них встроить в esp, поскольку они могут быть недоступны во время компиляции.Однако JVM может видеть, какие из них используются чаще всего (она собирает статистику во время работы программы) и может использовать две наиболее часто используемые реализации.(Пища для размышления относительно того, что происходит, когда вы удаляете / обновляете один из этих классов во время выполнения;)

Пожалуйста, исправьте меня, если я ошибаюсь.(В C ++ статическое связывание будет копировать байты из Z.class в A.class, тогда как динамическое связывание не будет.)

Java имеет только динамическое связывание, но это не предотвращает встраивание кода ввремя выполнения, которое так же эффективно, как и использование макроса.

Другой связанный с этим вопрос, касающийся процесса компиляции: как только необходимые файлы, описывающие Z, находятся в CLASSPATH во время компиляции, требуется ли компилятору байт-коды из Z.class для компиляции A.java (и будет собирать Z.class(если необходимо, из Z.java) или Z.java достаточно для компилятора?

Компилятор будет компилировать все файлы .java по мере необходимости.Вам нужно только указать .java, но он должен скомпилироваться (т.е. его зависимости должны быть доступны). Однако, если вы используете файл .class, не все его зависимости должны быть доступны для компиляции A.

Мое общее замешательство можно резюмировать следующим образом.Кажется, что полный [байтовый] код для Z должен присутствовать ДВАЖДЫ - один раз во время компиляции и второй раз во время выполнения -

Технически класс содержит байт-код и метаданные, такие каксигнатуры методов, поля и константы.Ни один байт-код не используется во время компиляции, только метаинформация.Байт-код во время компиляции не должен совпадать с тем, который используется во время выполнения.(Используемые сигнатуры / поля делают) Просто проще иметь по одной копии каждого класса, но вы можете использовать урезанную версию во время компиляции, если вам нужно для какой-то цели.

и этоэто должно быть верно для ВСЕХ классов, на которые ссылается программа Java.Другими словами, каждый класс должен быть загружен / представлен ДВАЖДЫ.Ни один класс не может быть представлен во время компиляции как просто файл заголовка (как это может быть в C ++).

Его нужно загрузить только один раз, так как он находится в хранилище или где-то еще на вас.диск.Интерфейсы, такие как заголовки, могут быть всем, что вам нужно во время компиляции, и это может быть отдельная библиотека, но обычно это не так, поскольку в большинстве случаев проще иметь один архив (OSGi - единственный известный мне пример того, где он находитсястоит разделить их)

1 голос
/ 03 апреля 2011

Еще одна часть головоломки, которая может помочь, интерфейсы и абстрактные классы также скомпилированы в файлы классов.Таким образом, при компиляции A, в идеале вы должны компилировать с API, а не с конкретным классом.Поэтому, если A использует интерфейс B (который реализуется Z) во время компиляции, вам понадобятся файлы классов для A & B, но во время выполнения вам понадобятся файлы классов для A, B и Z. Вы правы, что все классы динамически связаны (выможет взломать файлы, посмотреть на байт-код и увидеть там полные имена. jclasslib - отличная утилита для проверки файлов классов и чтения байт-кода, если вам интересно).Я могу заменить классы во время выполнения.Но проблемы во время выполнения часто приводят к различным формам LinkageErrors

Часто решение о том, должен ли класс быть отправлен с вашими скомпилированными jar-файлами, зависит от вашего конкретного сценария.Существуют классы, которые должны быть доступны в каждой реализации JRE.Но если бы у меня был свой собственный API и реализация, мне бы пришлось как-то предоставлять их везде, где они запускаются.Хотя есть некоторые API, например, servlets , где я буду компилировать с использованием API сервлета, но контейнер (например, Websphere) отвечает за предоставление API и реализации сервлета во время выполнения для меня (поэтому я не долженотправь свои копии).

0 голосов
/ 03 апреля 2011

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

В любом случае, ничто не мешает вам компилироватьс одним файлом класса 'header' и последующим запуском с самим файлом класса (Java в основном динамически связан), но это, кажется, делает дополнительную работу для себя.

0 голосов
/ 03 апреля 2011

Ваше резюме верно, однако я хотел бы добавить, что если вы компилируете в банку, то баночка будет содержать Z (а если Z - баночка, то только те файлы внутри Zar, которые необходимы.

Однако один и тот же Z можно использовать как для компиляции, так и для времени выполнения.

...