Classpath
В большинстве случаев команды java и javac должны найти другие классы необходимые для компиляции и выполнения. Самый распространенный случай — это использование классов входящих в Java SE. Или, например, нам нужно скомпилировать и запустить класс, который использует другие классы, не входящие в Java SE.
Команды java и javac используют следующий алгоритм поиска:
- Они используют один и тот же список каталогов, в которых ищут необходимые файлы.
- Обе команды в процессе поиска просматривают список каталогов в одном и том же порядке.
- Как только необходимый класс найден, процесс поиска прекращается. Если список каталогов содержит два или более классов с одним и тем же именем, используется первый найденный.
- Первое место используемое в процессе поиска — это каталоги содержащие классы Java SE.
- Второе место — каталоги определенные в так называемом Сlasspath.
Classpath может быть задано двумя способами:
- Как переменная окружения CLASSPATH. Команды java и javac используют этот способ по умолчанию.
- Как ключ -classpath (или -cp) команд java и javac. Этот способ переопределяет список каталогов заданный переменной окружения, но только для конкретного вызова. Данный метод является более предпочтительным.
2. Использование ключа -classpath
Рассмотрим использование ключа -cp используя классы first.Example1 и second.Example2 , описанные здесь. Но предположим, что класс second.Example2 находится в другом проекте и доступны только его .class файлы. На рисунке изображена схема каталогов для данного примера:
Следующая команда будет использована для компиляции first.Example1 класса, где ключ -cp указывает на расположение .class файла second.Example2 :
cd projectExample1 javac -d classes -cp ../projectExample2/classes src/first/Example1.java
Для запуска программы используется команда:
cd projectExample1 java -cp classes;../projectExample2/classes first.Example1
Ключ -cp указывает расположение .class файла second.Example2 (как и при компиляции), а также путь для поиска .class файла first.Example1 — classes.
Несколько важных правил при использовании ключа -cp :
- Ключ -cp может содержать несколько каталогов, разделенных точкой с запятой, как показано в примере при запуске команды java .
- Если указывается подкаталог, это НЕ означает что родительский каталог тоже входит в classpath. Например, для ключа -cp ../projectExample2/classes , каталог ../projectExample2 не будет входить в classpath.
- Если используется ключ -cp , то команды javac и java НЕ ищут классы в текущем каталоге по умолчанию. Для указания текущего каталога используется точка. Например:
cd projectExample1/classes java -cp .;../../projectExample2/classes first.Example1
Презентацию с видео можно скачать на Patreon .
Что такое classpath?
Classpath – это параметр, который указывает приложениям где искать пользовательские классы. По этому адресу должны быть найдены все классы, для которых не применяются специальные загрузчики. На место поиска стандартных классов JRE этот параметр не влияет.
Кроме непосредственно Java-приложений (команда java ), этот параметр применим и для других утилит JDK, таких как javac , javadoc и другие.
Есть два основных способа установки classpath: в переменной окружения ОС CLASSPATH , и в аргументе командной строки -cp (синоним -classpath ). Второй способ предпочтительнее, потому что позволяет устанавливать разные значения для разных приложений. Значение по умолчанию – текущая директория.
В параметре передаются пути к jar-файлам и корневым директориям с пакетами. Пути разделяют символом : в параметре командной строки, или же ; в переменной окружения. Чтобы включить все файлы директории, разрешается использовать в конце пути символ * .
Если приложение запускается из jar-файла ( java -jar ), classpath должен быть указан в его манифесте.
Pro Java
Теперь быстренько разберемся classpath, так как это достаточно важная тема для разработки на Java. Естественно я тут не смогу разобрать все тонкости и хитрости, но постараюсь представить самое необходимое чтобы было понимание этой темы. Для желающих разобраться глубже приведу линки для самостоятельного изучения. Ну и как всегда гугль в помощь.
Чтобы понимать что происходит под капотом у любой Java IDE когда она собирает проект и запускает его надо хоть немного попрактиковаться в использовании компилятора javac, среды исполнения java и понять classpath.
По существу classpath указывает компилятору или виртуальной машине где искать классы необходимые для сборки проекта или же его запуска. Немного об этом мы уже узнали тут. Теперь разберемся с этим получше.
Указать где компилятору или виртуальной машине искать классы можно через ключ –classpath или же системную переменную окружения CLASSPATH. Мы рассмотрим оба этих варианта.
Начнем с простого. Вернемся к нашему проекту Hello World (00004E_HelloWorld), там где мы разделили его на два файла Hello.java и Word.java.
Теперь попробуем создать исполняемый (jar) файл этого проекта из среды Eclipse. Так как скомпилированные, читай готовые к исполнению, файлы в Java имеют расширение class, а классов в реальных программах, могут быть сотни и тысячи, то их собирают в один или несколько jar архивов и таким образом запускают. То есть уже существует не россыпь файлов с расширением class, а один или несколько jar файлов.
И так! Понеслась! Воспользуемся Export для создания jar
После этого мы получим файл HelloWorld.jar готовый к исполнению на виртуальной машине java. Запустим его из командной строки:
Запускать jar файлы надо с ключом –jar как показано на скрине выше. Если этот ключ не использовать то вылетит ошибка:
Нам сообщили что не знают где искать главный (main) класс для HelloWorld.jar. Но запустить все же можно и без ключа –jar, но уже воспользовавшись на ключом –classpath , для которого существует сокращенный вариант –cp . Вот как это можно сделать:
Почему строчка запуска выглядит именно так? Вспоминаем что именно класс Hello.java содержит у нас метод main.
Класс Word.java такого метода не имеет.
Как я уже говорил метод main – это точка входа в программу, то есть место от куда начинается ее выполнение и поэтому виртуальной машине java надо знать, от куда надо начинать выполнять программу. Если она не может найти метод main, то она начинает ругаться, как это было показано выше.
И так в нашей строчке
java -cp HelloWorld.jar Hello
мы указали что искать метод main надо в классе Hello (расширение .class опускается) по пути HelloWorld.jar. Jar и zip архивы рассматриваются виртуальной машиной как каталоги, поэтому их надо указывать по полному имени с расширением.
Теперь скомпилируем наши классы Hello.java и Word.java самостоятельно из командной строки без помощи Eclipse, чтобы еще глубже понять как все работает. Для чистоты эксперимента рекомендую удалить все файлы с расширением .class и .jar из каталога bin.
Для начал просто скомпилируем исходники в class файлы без упаковки их в jar, чтобы было понятнее.
Переходим в коневой каталог 00004E_HelloWorld и от туда даем команду компиляции
javac -encoding utf8 -d bin src\Hello.java src\Word.java
Поскольку у нас программа состоит из двух классов Hello и Word, то их обоих сразу надо указать компилятору. Кроме того так же надо указать и кодировочку исходников. Так же мы указали папку bin – это то куда будут складываться откомпилированные файлы.
Теперь у нас в каталоге bin два файла Hello.class и Word.class. Перейдем в него чтобы запустить программу.
Все работает. Но файлики у нас там лежат россыпью классво .class
Теперь упакуем эти файлики .class в jar архив командой
jar cf HelloWorld.jar Hello.class Word.class
и попробуем запустить HelloWorld.jar
И вылетела ошибочка. Почему так? Ведь у нас уже есть jar файл в который упакованы оба класса.
Но все равно не работает. Это происходит потому, что внутри jar файла мы не указали какой файл у нас имеет метод main.
Запустить наш jar файл все таки можно указав дополнительно, какой класс содержит метод main.
java -cp HelloWorld.jar Hello
Теперь все работает. Но согласитесь так запускать jar файл не удобно, так как всегда надо знать какой класс содержит метод main. Если вы смотрели внимательно, то видимо заметили внутри архива HelloWorld.jar папку META-INF. В ней содержится файл MANIFEST.MF
Вот в нем и должна содержаться информация о классе содержащем метод main, но пока в нем ее нет.
Исправим эту ошибочку. Удалим файлик HelloWorld.jar и создадим его заново, но уже с добавлением информации о классе содержащим метод main. Сделаем это следующей командой
jar cfe HelloWorld.jar Hello Hello.class Word.class
И запустим файл HelloWorld.jar уже как полагается без танцев с бубном.
Как видим все работает нормально. Это произошло потому, что файл MANIFEST.MF уже содержит информацию о классе содержащем метод main.
Ну вот теперь мы имеем хоть какое-то представление о том что происходит когда какая-либо IDE создает исполняемый jar файл, а так же получили представление о classpath. В следующей статье мы немного углубим его.
P.S. Так же стоит знать что по умолчанию для виртуальной машины java доступны все классы стандартной библиотеки java, а так же все классы в текущем каталоге от куда запускается главный класс содержащий метод main.
Ну и на последок ссылка где про classpath рассказано достаточно подробно. Правда я не знаю как долго она проживет.
coreer
Это пример теоретического принципа динамической линковки библиотек в разработке ПО для java-платформы.
Суть в том, что библиотеки(вернее пакеты целиком или контектные классы) указанные в класспазе, загружаются только тогда, когда это нужно в конкретном месте выполнения приложения.
Вирутальная машина ищет и загружает библиотеки в следующем порядке(это типы библиотек):
1) Классы этапа загрузки — это библиотеки в которых непосредственно нуждается сама платформа java.
2) Классы из JAVA_HOME/jre/lib/ext.
3) Другие библиотеки.
Если первые две библиотеки виртуальная машина сама понужде знает откуда загружать, то вот последние библиотеки непонятно откуда брать.
Для этого нам и нужно пользоваться переменной CLASSPATH. Устанавливать ее мы можем в следующих местах:
1) В глобальных пременных ОС (либо для всех сессий входа в ОС, либо только для поточной сессии).
2) Дополнительным аргументом в утилите cli java.
3) В манифесте jar-архива.
Примеры:
№ 1
У нас есть структура каталогов
/home/user/myprogram/
|
---> org/
|
---> mypackage/
|
---> HelloWorld.class
---> SupportClass.class
---> UtilClass.class
Класс HelloWorld — это главный класс приложения.
Если мы находимя в каталоге myprogram, то можем вызвать просто:
java org.mypackage.HelloWorld
Если же мы находимся в любом другом месте, то
java -classpath /home/user/myprogram org.mypackage.HelloWorld
D:\myprogram\
|
---> lib\
|
---> supportLib.jar
|
---> org\
|
--> mypackage\
|
---> HelloWorld.class
---> SupportClass.class
---> UtilClass.class
java -classpath D:\myprogram;D:\myprogram\lib\supportLib.jar org.mypackage.HelloWorld
set CLASSPATH=D:\myprogram;D:\myprogram\lib\supportLib.jar
java org.mypackage.HelloWorld
Интересность 1
Можно заметить, что в предыдущих примерах мы переопределяем класспаз для контретного ява-вызова полсностью, тоесть если какие-то библиотеки указаны в глобальной ОС переменной, то они будут перетерты под конкретный вызов. Если же нужно сохранить и эти места библиотек, нужно еще и указать через разделитель(в юникс-системах “:”, в виндовсе “;”) точку “.” Можно увидеть пример в примере следующей интересности.
Интересность 2
Если у нас есть много библиотек запакованных в jar-архивы, которые находятся в одной директории, то мы с версии платформы java 6 можем указать *, и все эти архивы окажутся в класспазе, а не перечислять каждый архив отдельно:
java -classpath ".;c:\mylib\*" MyApp
Интересность 3
Вот так можно узнать какой класспаз в данный момент выполнения приложения:
System.getProperty("java.class.path");
№ 3
Пример с архивом:
D:\myprogram\
|
---> helloWorld.jar
|
-----------> lib\
|
---> supportLib.jar
---> supportLib2.jar
В манифесте должно быть:
Main-Class: org.mypackage.HelloWorld
Class-Path: lib/supportLib.jar lib/supportLib2.jar
В таком случае мы можем вызывать так:
java -jar D:\myprogram\helloWorld.jar