MyTetra Share
Делитесь знаниями!
Указание интерпретатору Java, где искать классы
Время создания: 31.03.2013 17:14
Текстовые метки: java, classpath, класс, путь
Раздел: Компьютер - Программирование - Java
Запись: xintrea/mytetra_syncro/master/base/13647356700tp9747ke2/text.html на raw.github.com

Указание интерпретатору, где искать классы


Интерпретатору нужно знать все места, где он может найти классы. Что является таким местом? Директория и/или библиотека – jar-файл. Прошу обратить особое внимание – именно файл. Иначе говоря, если у вас файл javaee.jar находится в директории E:\java\lib\ – интерпретатору нужен полный путь к файлу, а именно – E:\java\lib\javaee.jar. Простого указания директории НЕДОСТАТОЧНО!


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



Способ 1. Переменная окружения CLASSPATH


Способ прост. Заводится переменная окружения CLASSPATH, и в ней через разделитель (';' для Win, ':' для *NIX) прописываются все полные пути к файлам библиотек и к директориям, в которых лежат деревья классов. Обратите внимание, что директория – это точка, в которой лежит КОРЕНЬ всех скомпилированных классов. Т.е., если классы из пакета mypackage.test компилировались в директорию c:\myproject\classes, то в CLASSPATH должна фигурировать именно c:\myproject\classes, а не c:\myproject\classes\mypackage и не c:\myproject\classes\mypackage\test.


Способ простой, однако имеет несколько недостатков, за которые я его очень не люблю. Первое. Хорошо, библиотеки можно подключить так. Да и то с оговорками, см. ниже. А как быть с текущими классами? С теми, которые я как раз и пишу? Их тоже надо подключать. Включить все? Нереально, у меня больше 20 проектов, за деревьями не будет видно леса, если для каждого случая в CLASSPATH указать полный путь. Указывать относительный? Он тоже разный. Честно сказать, приемлемого решения я не нашел.


Второе. Допустим, я хочу подключить библиотеки (jar-файлы) через CLASSPATH. Вопрос. А сколько их у меня? Поверхностный поиск по диску дает... более 1550! Ладно, львиная доля из них – от приложений. Но тех, которые использую я сам, прямо или опосредованно, более 100 совершенно точно. Что будет, если в CLASSPATH прописать их все? Хватит ли размера памяти, выделяемого под переменную окружения операционкой? А если все приложения сделают то же самое?


Третье, самое неприятное. У меня стоят Log4J версий 1.2.8 и 1.3.0, Velocity 1.2, 1.3 и 1.4, Hibernate 2.1 и 3.0, Servlet 2.2, 2.3, 2.4 и JSP 1.1, 1.2 и 2.0, JDBC-MySQL трех различных версий, несчетное множество XML-парсеров и т.д. и т.п. В одних проектах я использую одни, в других – другие. Если я включу их все в CLASSPATH – компилятор будет ВСЕГДА использовать первый найденный класс в порядке перечисления библиотек. Какой версии? Не знаю. Но в любом случае – одной и той же. А мне нужны разные. И если первые два момента можно было бы как-то пережить, то этот – нет.


Потому – я никогда не устанавливаю у себя переменную окружения CLASSPATH. Тем более, что существует...



Способ 2. Указание ключа -classpath интерпретатору (компилятору)


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


Пример. Допустим, у меня в проектной директории есть поддиректория classes, в которой находятся скомпилированные классы, и поддиректория lib, в которой лежат библиотеки velocity-1.4.jar и log4j-1.2.8.jar. Тогда командная строка для запуска моего класса ru.skipy.myproject.Main будет выглядеть так:


java -classpath ./classes;./lib/velocity-1.4.jar;./lib/log4j-1.2.8.jar ru.skipy.myproject.Main


По пунктам: я указываю интерпретатору, что классы надо искать в директории ./classes ('.' – текущая директория), в библиотеке ./lib/velocity-1.4.jar и в библиотеке ./lib/log4j-1.2.8.jar, в порядке их перечисления.


Если же мне не нужно использовать внешние библиотеки, то командная строка превращается в следующую:


java -classpath ./classes ru.skipy.myproject.Main


Обратите внимание на -classpath ./classes. Я не знаю, откуда в некоторых книгах взялась конструкция java ru.skipy.myproject.Main, без указания classpath. Я не поленился, поставил себе jdk1.1.8, но и там classpath надо указывать. Именно отсутствие classpath в параметрах интерпретатора является источником большого количества проблем. Впрочем, об этом ниже.


Справедливости ради нужно упомянуть и третий способ подключения библиотек:

Способ 3. Директория <JRE_INSTALLATION_DIRECTORY>/lib/ext


Способ, пожалуй, самый простой. Но, должен признать, он пригоден только для подключения jar-файлов. Файл кладется в указанную директорию. Всё. При старте виртуальной машины библиотека подключается.


Недостаток очевиден. У меня в системе, к примеру, стоит 6 виртуальных машин, не считая тех, что шли с приложениями вместе: 1.1.8 (для которой этот способ вообще не работает), 1.3.1, 1.4.2, 1.5.0, JRockIt 1.4.2, JRockIt 1.5.0. Библиотеку придется класть КАЖДОЙ. Вопрос. Зачем?


Естественно, у этого метода есть и достоинства. Он хорош, когда нужно что-то поставить на клиентскую машину, где есть всего одна JRE (и то если есть). Есть также небольшие отличия с точки зрения безопасности (в разрешенных действиях) между кодом, подгруженным через classpath и кодом, загруженным из <JRE_INSTALLATION_DIRECTORY>/lib/ext. Но это уже тонкости, выходящие за рамки этой статьи.


А что будет, если не указать таки classpath, ни в качестве переменной окружения, ни в качестве параметра интерпретатору? А будет всем известная ошибка. Вопрос о том, что это такое, появляется не реже раза в неделю. Что в числе прочего и побудило меня написать эту статью. Ошибка эта называется ...

Exception in thread "main" java.lang.NoClassDefFoundError: ...


Вместо '...' пишется имя класса, который виртуальная машина не смогла найти. Ошибка эта означает, что определение класса не найдено. Под определением класса виртуальная машина понимает его байткод. Т.е. она просто не смогла найти нужный файл.


Иногда встречается более изощренная разновидность этой ошибки. Допустим, класс, который нужно запустить, называется test.Test. Одна из классических разновидностей действия: скомпилировать класс, потом дойти до директории, где лежит непосредственно Test.class и запустить его оттуда:


javac Test.java

@rem file ./test/Test.class created

cd test

java -classpath . Test


Ошибка при этом будет такой:


Exception in thread "main" java.lang.NoClassDefFoundError: Test (wrong name: test/Test)

at java.lang.ClassLoader.defineClass0(Native Method)

at java.lang.ClassLoader.defineClass(ClassLoader.java:539)

at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123)

at java.net.URLClassLoader.defineClass(URLClassLoader.java:251)

at java.net.URLClassLoader.access$100(URLClassLoader.java:55)

at java.net.URLClassLoader$1.run(URLClassLoader.java:194)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:187)

at java.lang.ClassLoader.loadClass(ClassLoader.java:289)

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)

at java.lang.ClassLoader.loadClass(ClassLoader.java:235)

at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)



Что это означает? А означает это вот что. Виртуальная машина нашла класс, который я пытаюсь запустить – Test. Нашла она его исключительно потому, что я сказал – искать в этой директории класс по имени Test. Прошу обратить внимание – по ПОЛНОМУ имени Test. Машина нашла его. Но класс-то на самом деле – test.Test. Именно об этом она и сообщает – wrong name: test/Test


Еще одна ошибка, вызывающая появление сообщения Exception in thread "main" java.lang.NoClassDefFoundError: имя класса при запуске пишут вместе с расширением файла .class. Естественным образом виртуальная машина не находит этого класса, т.к. расширение считает частью имени, приписывает к имени .class и начинает искать файл с именем, заканчивающимся на class.class


Для продвинутых разработчиков сущестует еще один способ получить java.lang.NoClassDefFoundError. Заключается он в следующем: приложение, использующее стороннюю библиотеку, собирается в jar-файл и запускается командой java -jar <имя jar-файла>, после чего и появляется данная ошибка. Происходит это по следующей причине: при запуске приложения именно таким образом, с ключом интерпретатора -jar, в classpath включается один единственный файл – тот самый, который указывается в командной строке. Все остальные библиотеки – описаные в переменной CLASSPATH, указаные через ключ -classpath, – все они ИГНОРИРУЮТСЯ. Единственный способ этого избежать (и найден он – вот ведь поразительный факт! – опять-таки в документации) – указать в файле manifest.mf атрибут Class-Path – список относительных путей (обращаю особое внимание – относительных, относительно этой библиотки!) к необходимым библиотекам. Разделяются эти пути пробелами. Естественно, при этом работает и обычный вариант – указывать в явном виде classpath, включая туда все библиотеки, и имя исполняемого класса.


Еще один тип ошибок – при компиляции выдается сообщение "Can't resolve symbol ..." с указанием на точку в коде, где объявлена переменная, вернее, на тип этой переменной. Причина та же – компилятор не может найти класс, указанный как тип этой переменной по причине отсутствия этого класса в classpath.


Возможно, есть еще какие-нибудь характерные ошибки, связанные с classpath. Если вспомню – допишу.


 
MyTetra Share v.0.59
Яндекс индекс цитирования