Статьи

Механизм расширения Java загружает все файлы JAR

Механизм Java Расширение  описан в  руководстве по Java ,  как «стандартный, масштабируемый способ сделать пользовательские интерфейсы доступны для всех приложений , работающих на платформе Java.» Как описано в  разделе « Понимание загрузки классов расширений» , «структура расширений использует механизм делегирования загрузки классов» с классами расширений, загруженными после классов начальной загрузки в  rt.jar  (и связанных с ними JAR), но до классов, загружаемых из типичного  пути к классам .

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

Характерная черта Classpath Механизм выдвижения (дополнительные пакеты)
Сфера Типично для конкретного приложения

Потенциально все JRE на хосте

  •  Переменная среды CLASSPATH
Все JVM, работающие в определенной JRE

Все хосты JRE

  • Solaris: /usr/jdk/packages/lib/ext
  • Linux: /usr/java/packages/lib/ext
  • Окна:%SystemRoot%\Sun\Java\lib\ext
Как указано Файлы .jar

  • Явно указано по имени (в том числе  .jar)
  • Подстановочный знак (*), соответствующий всем JAR-файлам с  .jarрасширениями

Файлы .class

  • Каталог, содержащий .class указанные файлы
Все JAR-файлы (даже если расширение, отличное от расширения  .jar или не имеющее его вообще) в указанных каталогах загружаются.
Порядок загрузки класса После начальной загрузки и загрузки расширений. После начальной загрузки, но до classpath.

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

Я собираюсь использовать несколько простых примеров в этом посте, чтобы продемонстрировать некоторые из этих различий. Следующие два списка кода предназначены для очень простого  HelloWorld класса и основного класса приложения,  Main который использует этот HelloWorld класс.

HelloWorld.java

public class HelloWorld
{
   @Override
   public String toString()
   {
      return "Hello, World!";
   }
}
Main.java

import static java.lang.System.out;

public class Main
{
   public static void main(final String[] arguments)
   {
      out.println(new HelloWorld());
   }
}

Чтобы продемонстрировать основное различие между classpath и механизмом расширения ( необязательные пакеты ), я заархивирую скомпилированный  HelloWorld.class файл в вызываемый JAR  HelloWorld.jar и поместу его в другой каталог, чем скомпилированный  Main.class файл.

Чтобы продемонстрировать использование традиционного пути к классам, я поместил  HelloWorld.jar файл в каталог с именем C:\hello и получу доступ к этому JAR через подстановочный знак (*) для  Main использования. Это продемонстрировано на следующих двух снимках экрана.

Два предыдущих изображения демонстрируют, что Main приложение Java по-  прежнему может загружать  HelloWorld.classфайл, даже если я удалил его из текущего каталога, потому что программе  запуска Java  было явно сказано (через  -classpath опцию) искать его в  C:\hello. Используя механизм расширений (необязательные пакеты), можно загрузить класс без его нахождения в том же каталоге и без явной спецификации пути к классам. Это показано на следующем снимке экрана.

Предыдущий снимок экрана демонстрирует, что средство запуска Java даже не нуждается HelloWorld.class в том же каталоге или указано в его пути к классам, когда этот класс находится внутри JAR, который находится в каталоге расширений (необязательных пакетов). Это часто упоминается как  преимущество использования механизма расширений,  поскольку все приложения, использующие этот JRE (или, возможно, все приложения на хосте), могут видеть одни и те же классы без необходимости явно указывать их в пути к классам.

С традиционным подходом classpath инструктирования приложения загружать классы из JAR, файл JAR, содержащий  .class файл, должен заканчиваться  .jar расширением. На следующем снимке экрана показано, что происходит, когда   объект HelloWorld.jar переименовывается  HelloWorld.backupв тот же каталог, на который ссылается classpath.

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

Предыдущее изображение демонстрирует, что переименование файла JAR, который находится в каталоге расширений, чтобы не иметь никакого расширения файла, вообще не мешает загрузчику классов загружать классы этого JAR. Другими словами, механизм загрузки классов загружает все файлы JAR в указанный каталог расширений на основе типа файла, а не имени файла или расширения. Как  факультативных пакеты Обзор  резюмирует «Существует ничего особенного о каком — либо конкретном JAR сам файл или классов , которые он содержит , что делает его установлен дополнительный пакет. Это установленный дополнительный пакет в силу своего расположения в JRE / Библиотека / внутр.»

Существуют некоторые  риски и недостатки,  связанные с размещением слишком большого числа определений классов в JAR-файлах внутри каталога расширений. Может быть невыносимо задаться вопросом, почему  , например, NoSuchMethodError возникает, когда видно, что класс, указанный явно в пути к классам, имеет рассматриваемый метод. Я ранее написал  об одном из многих возможных причин  NoSuchMethodError, но забытые устаревшие и устаревшие определения классов , находящиеся внутри JAR — файлов в каталоге расширений являются еще одной потенциальной причиной. Это демонстрируется следующим.

Следующие два списка кода показывают исправленные версии  Main.java и  HelloWorld.java. В частности, HelloWorld есть совершенно новый метод, который Main вызывает новая версия  . В этом случае я собираюсь оставить вновь скомпилированный  HelloWorld.class файл в том же каталоге, когда я запускаю,  Main чтобы продемонстрировать, что старая отключенная версия  HelloWorld.class в JAR в каталоге расширений имеет приоритет над новой активностью  HelloWorld.class в текущем каталоге.

Пересмотрено Hello World.java (новый метод)

public class HelloWorld
{
   @Override
   public String toString()
   {
      return "Hello, World!";
   }

   public String directedHello(final String name)
   {
      return "Hello, " + name;
   }
}
Пересмотренный Main.java
import static java.lang.System.out;

public class Main
{
   public static void main(final String[] arguments)
   {
      final HelloWorld helloWorld = new HelloWorld();
      out.println(helloWorld);
      out.println(helloWorld.directedHello("Dustin"));
   }
}

Последнее изображение демонстрирует, что устаревшее в настоящее время определение класса  HelloWorld в каталоге расширений имеет приоритет над новым определением класса  HelloWorld в том же каталоге. Даже когда я указываю текущий каталог в classpath, старая версия в каталоге расширений имеет приоритет. Это показано на следующем снимке экрана, который также показывает, что JAR в каталоге расширений, который «скрывает» более новый JAR и более новый метод его класса, все еще даже не назван с  .jar расширением.

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

Использование каталога расширений может упростить работу разработчиков, поскольку классы файлов JAR, находящихся в каталоге расширений (или каталогах), доступны для всех приложений в JRE, связанных с каталогом расширений (или со всеми JRE на хосте, если операционная система используется каталог расширений хоста). Однако существуют определенные риски, связанные со слишком либеральным использованием каталога. Может быть легко забыть, что устаревшие определения классов, находящиеся в JAR в этом каталоге, не позволяют загрузчикам классов загружать более новые и, казалось бы, очевидные версии определений классов. Когда это происходит, сам механизм расширений (необязательных пакетов), который облегчает жизнь разработчиков, теперь делает его более трудным.

Elliotte Rusty Harold  предупреждает об использовании механизма расширений (необязательных пакетов): «Хотя это кажется удобным, это также долгосрочная ошибка … Рано или поздно (возможно, раньше) вы загрузите неправильную версию класс из места, о котором вы даже не задумывались и тратите часы на отладку «.  Java Tutorial  также рекомендует осторожность (я добавил  акцент ), «Поскольку этот механизм расширяет ядро API — платформы, его использование должно быть  разумно применять . Чаще всего он используется для хорошо стандартизованные интерфейсы , такие как те , которые определены в рамках Java Community Process, хотя это также может быть подходящим для интерфейсов для всего сайта. «

Хотя механизм расширений (необязательных пакетов) аналогичен механизму пути к классам, и оба они используются как часть загрузки классов, важно отметить различия между ними. В частности, важно помнить, что .jar будут загружены все файлы JAR (даже если у них нет  расширений файлов), которые находятся в каталоге, на который ссылается каталог расширений. Переименование этих JAR-файлов и даже изменение их расширения не будет достаточным, чтобы загрузка классов игнорировала их. С classpath, с другой стороны, переименование JAR является достаточным для предотвращения загрузки, когда classpath явно указывает отдельные файлы JAR, а изменение расширения файла обычно достаточно для предотвращения загрузки, даже когда classpath использует подстановочный знак (*) для указания всех JAR-файлов в каталог.

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