Почти с тех пор, как я начал узнавать о 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 . |