Почти с тех пор, как я начал узнавать о Java, поле заголовка Class-Path в файле Manifest задает относительный путь к классу времени выполнения для исполняемых JAR (JAR с начальной точкой приложения, указанной в другом манифесте с именем Main-Class ). Недавно коллега столкнулся с проблемой, которая удивила меня, поскольку доказала, что запись Class-Path к Class-Path в манифесте файла JAR также влияет на Class-Path к классам времени компиляции, когда содержащий JAR включается в Class-Path к классам во время выполнения javac . Этот пост демонстрирует этот новый для меня нюанс.
В разделе « Добавление классов в путь к классам JAR-файла» в журнале развертывания Учебных материалов по Java говорится: «Вы указываете классы для включения в поле заголовка Class-Path в файле манифеста апплета или приложения». В этом же разделе также говорится: «Используя заголовок Class-Path в манифесте, вы можете избежать указания длинного флага -classpath при вызове Java для запуска вашего приложения». Эти два предложения в основном суммируют то, как я всегда думал о заголовке Class-Path в файле манифеста: как Class-Path к Class-Path для содержащего JAR, выполняемого через средство запуска приложения Java (исполняемый файл java ).
Оказывается, что запись Class-Path в манифесте JAR влияет на компилятор Java ( javac ) так же, как он влияет на панель запуска приложений Java ( java ). Чтобы продемонстрировать это, я собираюсь использовать простой интерфейс ( PersonIF ), простой класс ( Person ), который реализует этот интерфейс, и простой класс Main который использует класс, который реализует интерфейс. Списки кода приведены ниже для них.
PersonIF.java
|
1
2
3
4
|
public interface PersonIF{ void sayHello();} |
Person.java
|
1
2
3
4
5
6
7
8
9
|
import static java.lang.System.out;public class Person implements PersonIF{ public void sayHello() { out.println("Hello!"); }} |
Main.java
|
1
2
3
4
5
6
7
8
|
public class Main{ public static void main(final String[] arguments) { final Person person = new Person(); person.sayHello(); }} |
Как видно из приведенных выше списков кода, класс Main зависит от (использует) класс Person а класс Person зависит от (реализует) PersonIF . Я намеренно PersonIF.class файл PersonIF.class в его собственный JAR- PersonIF.jar и PersonIF.jar этот JAR-файл в (другом) подкаталоге. Файл Person.class будет существовать в своем собственном JAR-файле Person.jar и этот JAR-файл содержит MANIFEST.MF file с заголовком Class-Path ссылающимся на PersonIF.jar в относительном подкаталоге.
Сейчас я Main.class скомпилировать Main.class из Main.java только с текущим каталогом в classpath. Ранее я ожидал, что компиляция потерпит неудачу, когда javac не сможет найти PersonIF.jar в отдельном подкаталоге. Тем не менее, это не подводит!
Это казалось мне удивительным. Почему это компилируется, когда я не указал явно PersonIF.class (или JAR, содержащий его) в качестве значения classpath, предоставленного с помощью флага -cp ? Ответ можно увидеть, запустив javac с флагом -verbose .
Выходные данные javac -verbose предоставляют «путь поиска исходных файлов» и «путь поиска файлов классов ». «Путь поиска для файлов классов» был в этом случае значительным, потому что я переместил исходные файлы PersonIF.java и Person.java в совершенно не связанный каталог, а не в указанные пути поиска. Интересно видеть, что путь поиска файлов классов (а также путь поиска исходных файлов) включает archive/PersonIF.jar хотя я не указал этот JAR (или даже его каталог) в значении -cp . Это демонстрирует, что предоставленный Oracle компилятор Java учитывает содержимое пути к Class-Path указанное в заголовке Class-Path файла MANIFEST.MF любого JAR-файла, указанного в пути к классам.
Следующий снимок экрана демонстрирует запуск вновь скомпилированного класса Main.class и получение зависимости PersonIF.class из archive/PersonIF.jar без archive/PersonIF.jar его в значении, передаваемом флагу java -cp archive/PersonIF.jar запуска приложения java -cp . Я ожидал, что поведение во время выполнения будет таким, хотя по общему признанию я никогда не пробовал это или даже думал о том, чтобы сделать это с JAR, у которого файл MANIFEST.MF не имел заголовка Main-Class (неисполняемый JAR). В Person.jar манифеста Person.jar в этом примере не указан заголовок Main-Class а указан только заголовок Class-Path , но он по-прежнему мог использовать содержимое этого classpath во время выполнения при вызове с java .
Последняя демонстрация для этого поста включает в себя удаление заголовка Class-Path и связанного с ним значения из файла JAR и попытку компиляции с использованием javac и того же указанного в командной строке classpath. В этом случае JAR, содержащий Person.class , называется Person2.jar и на следующем снимке экрана показано, что его файл MANIFEST.MF не имеет заголовка Class-Path .
Следующий снимок экрана демонстрирует, что компиляция с использованием javac теперь завершается неудачно, поскольку, как и ожидалось, PersonIF.class явно не указан в пути к классам и больше не доступен по ссылке из заголовка MANIFEST.MF Class-Path JAR, который находится на CLASSPATH.
Из предыдущего снимка экрана видно, что пути поиска для исходных файлов и файлов классов больше не включают в себя archive/PersonIF.jar . Без этого JAR-файла javac не может найти PersonIF.class и сообщает об ошибке: «файл класса для PersonIF не найден».
Общие замечания
- Заголовок
Class-Pathв файлеMANIFEST.MFне зависит от существования заголовкаMain-Classсуществующего в том же файле JARMANIFEST.MF.- JAR с заголовком манифеста
Class-Pathсделает эти записи пути к классам доступными для загрузчика классов Java независимо от того, выполняется ли этот JAR с помощьюjava -jar ...или просто помещается вjava -jar ...к классам более крупного Java-приложения. - JAR с заголовком манифеста
Class-Pathсделает эти записи пути к классам доступными для компилятора Java (javac), если этот JAR включен вClass-Pathк классам, указанный для компилятора Java.
- JAR с заголовком манифеста
- Поскольку использование
Class-Pathв файле манифеста JAR не ограничивается областью действия JAR, чейMain-Classвыполняется, зависимости класса могут быть потенциально непреднамеренно (возможно, даже с неправильными версиями) ими, вместо того, чтобы разрешать явно указанные записи пути к классам , Следует соблюдать осторожность при создании JAR с манифестами, в которых указанClass-Pathили при использовании сторонних JAR сClass-Pathуказанным в их файлах манифеста. - Важность файла манифеста JAR иногда недооценивается, но эта тема является напоминанием о полезности знания того, что находится в конкретном файле манифеста JAR .
- Эта тема является напоминанием о том, что время от времени можно запускать
javacбез-verboseчтобы узнать, что это такое. - Всякий раз, когда вы помещаете JAR в путь к классам компилятора
javacили в программу запуска приложенийjava, вы помещаете в путь к классам не только определения классов в этом JAR; вы также помещаете любые классы и JAR, на которые ссылаетсяClass-Pathэтого манифеста JAR, вClass-Pathк классам компилятора или модуля запуска приложения.
Вывод
Существует много мест, из которых загрузчик классов Java может загружать классы для создания и запуска приложений Java. Как показано в этом посте, заголовок Class-Path файла MANIFEST.MF в JAR является еще одной точкой соприкосновения для определения того, какие классы загрузчик классов будет загружать как во время выполнения, так и во время компиляции. Использование Class-Path не влияет только на JAR, которые являются «исполняемыми» (имеют заголовок Main-Class указанный в их файле манифеста и запускаются с java -jar ... ), но могут влиять на загруженные классы для компиляции и для любого Выполнение приложения Java, в котором JAR с файлом манифеста, содержащим заголовок Class-Path находится в пути к классам.
| Ссылка: | JAR Manifest Class-Path не предназначен для запуска приложений Java только от нашего партнера по JCG Дастина Маркса из блога Inspired by Actual Events . |




