Эта простая инструкция поможет подключить обычные физические android-устройства в режиме USB debugging mode к среде разработки Eclipse в Ubuntu. Тестирование приложения на реальном устройстве гораздо продуктивнее и эта инструкция расскажет как сделать это на устройстве, без дополнительных утомительных действий. Достаточно просто запустить приложение в среде разработки Eclipse и оно автоматически стартует на android-устройстве. С выходом новых карт Google Maps API v2 для ОС Android, это стало весьма актуально, т.к. на данный момент не получится полноценно использовать Google Play Services на эмуляторе.

Прежде чем начать подключать устройство к системе, следует включить режим USB debugging на самом смартфоне или планшете, это включается в меню:

Android 3.2 и более ранние версии:
Settings > Applications > Development

Android 4.0 и выше:
Settings > Developer options

ВАЖНО: В Android 4.2 и выше, Developer options скрыто по умолчанию. Для активации опции нужно зайти в меню Settings > About phone и нажать на Build number семь раз. После этого следует вернуться в предыдущий пункт Developer options.

Теперь начнём, добавим правило работы с нужным нам USB устройством, для этого создадим следующий файл:

1
sudo touch /etc/udev/rules.d/51-android.rules

Далее откроем файл на редактирование:

1
sudo nano/etc/udev/rules.d/51-android.rules

Добавим в файл следующюю строку с атрибутами производителя устройства и доступа к устройствам на уровне системы:

1
SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", MODE="0666", GROUP="plugdev"

ВАЖНО: четырёхсимвольное значение параметра idVendor (0bb4) должно совпадать с идентификатором производителя устройства из таблицы ниже.

CompanyUSB Vendor ID
Acer0502
ASUS0b05
Dell413c
Foxconn0489
Fujitsu04c5
Fujitsu Toshiba04c5
Garmin-Asus091e
Google18d1
Hisense109b
HTC0bb4
Huawei12d1
K-Touch24e3
KT Tech2116
Kyocera0482
Lenovo17ef
LG1004
Motorola22b8
NEC0409
Nook2080
Nvidia0955
OTGV2257
Pantech10a9
Pegatron1d4d
Philips0471
PMC-Sierra04da
Qualcomm05c6
SK Telesys1f53
Samsung04e8
Sharp04dd
Sony054c
Sony Ericsson0fce
Teleepoch2340
Toshiba0930
ZTE19d2



Установим нужные права на только что созданный файл:

1
sudo chmod a+r/etc/udev/rules.d/51-android.rules

Теперь подключим или переподключим наше устройство к порту USB и перейдём в каталог platform-tools в Android SDK. В моём случае он располагается в директории /opt:

1
cd /opt/android-sdk-linux/platform-tools

И выполним в консоли такой код:

1
adb devices

В выводе должны отобразиться устройства виртуальные и физические. Идентификатор физического устройства похож на:

1
1234567890ABCDEF device

Если появляется ошибка «adb: команда не найдена», то нужно уствновить пакет:

1
sudo apt-getinstall android-tools-adb

Последний шаг, настройка исходного кода приложения, а именно файла AndroidManifest.xml. Нужно просто добавить параметр android:debuggable в тэг <application>, например:

1
2
3
4
<application
   android:icon="@drawable/icon"
   android:label="@string/app_name"
   android:debuggable="true">

ВАЖНО: перед выпуском приложения, параметр android:debuggable следует удалить.

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

Актуальная на сегодняшний день задача — установка комплекта разработчика мобильных приложений Android в операционной системе Ubuntu Linux. Процесс абсолютно не сложный, нужно просто понимать последовательность действий, и иногда требуется разрешение некоторых мелких проблем.

Я опишу проверенную последовательность действий для Ubuntu 14.04 и свежей, на момент написания инструкции, версии Eclipse 4.3.2 Kepler.

Шаг 1. Установка Eclipse IDE

Сразу оговорюсь, что на данный момент Eclipse IDE лучший выбор для разработки андроид приложений, да и для других он тоже подойдёт. Итак, скачаем свежую версию Eclipse с официального сайта, нам подойдёт редакция Eclipse Standard 4.3.2 (версия может отличаться), 32 или 64 бита, в зависимости от разрядности операционки.

Скачаный файл eclipse-standard-kepler-SR2-linux-gtk-x86_64.tar.gz нужно просто распаковать в директорию, из которой он будет запускаться. Я выбрал диреторию /opt/eclipse:

sudo tar -zxvf ./eclipse-standard-kepler-SR2-linux-gtk-x86_64.tar.gz -C /opt

Сделаем файл запускаемым:

sudo chmod +x /opt/eclipse/eclipse

Для удобства создадим символическую ссылку в нашей домашней директории, чтобы потом запускать по ней Eclipse IDE:

sudo ln -s /opt/eclipse/eclipse /home/username/eclipse

Для запуска из терминала создадим символическую ссылку

sudo ln -s /opt/eclipse/eclipse /usr/local/bin/eclipse

Теперь среду разработки можно запускать из папки /opt/eclipse или по символической ссылке.

Чтобы Eclipse заработал, системе потребуется Java, подойдёт «OpenJDK Java 7 Runtime» из центра приложений Ubuntu, иначе при запуске Eclipse появится сообщение об ошибке.

Русифицируем

скачиваем http://mirror.tspu.ru/eclipse/technology/babel/update-site/R0.12.0/babel-R0.12.0-juno.zip

Набираем в верхнем меню Help — Install New Software.

В появившемся окне нажал кнопку Add...
В строке Name: набрал Babel
В строку Location: скопировал вышенайденную строку для моей версии eclipse.
Нажал Ok.
В строке Work with: появилось Babel — http://mirror.tspu.ru/eclipse/technology/babel/update-site/R0.12.0/juno/
А в списке появилась надпись pending...
Несколько минут ждал, пока он отпендится.
В появившемся списке выбрал русский языковой пакет.
Нажал Next. Нажал Finish.
Началось скачивание и установка пакетов русификации.
Выскочило security warning, но я согласился с продолжением установки.
Далее согласился с перезагрузкой eclipse.
После перезагрузки увидел родные слова начертанные русскими буквами: Файл, Изменить, Проект и прочие.


Шаг 2. Установка Android SDK

Скачиваем Android SDK с официального сайта, на момент написания файл назывлся android-sdk_r21.0.1-linux.tgz. Распаковываем архив, можно туда же, где установлен Eclipse IDE:

sudo tar -zxvf ./android-sdk_r21.0.1-linux.tgz -C /opt

Путь для запуска менеджера пакетов, получился таким /opt/android-sdk-linux/tools/android, сами пакеты можно установить самому, а можно потом воспользоваться перспективой Eclipse, которая по сути выдаст то же окно менеджера пакетов. В общем случае нужно выбрать версию API, например Android 4.2 (API 17) или несколько, а может и все.

Шаг 3. Установка ADT Plugin для Eclipse

Перед установкой плагина лучше сразу установить пару пакетов, без которых возникнут ошибки, это GEF и WST Server Adapters. Установить плагины можно в самом Eclipse, для этого запускаем стандартную утилиту из меню программы:

Help > Install New Software...

В самой верхней строке, появившегося окна, вводим адрес репозитория GEF:

http://download.eclipse.org/tools/gef/updates/releases/

назовём его «GEF Plugin», для понимания. После ввода ссылки репозитория в окне обновлений появится строка с чекбоксом, отметим её:

GEF (Graphical Editing Framework)

Завершим установку плагина, скорее всего потребуется перезагрузка Eclipse.

Аналогичным образом добавим репозиторий Eclipse Juno, адрес:

http://download.eclipse.org/releases/juno

Если версия Eclipse отличается от Juno — следует указать её. Назовём репозиторий «Juno». В окне обновлений нужно выбрать один пункт:

Web, XML, Java EE and OSGi Enterprise Development > WST Server Adapters

Завершим установку плагина. Теперь пора приступить к установке самого ADT PLugin, вводим адрес репозитория:

https://dl-ssl.google.com/android/eclipse/

назовём его «ADT Plugin», для понимания. После ввода ссылки репозитория в окне обновлений появится пару строк, нам нужна строка с интсрументами разработчика, отметим её:

Developer Tools

Теперь остаётся завершить установку, согласиться или нет с лицензией и т.п. В конце установки Eclipse попросит перезагрузки.

Если по каким-то причинам в меню Window отсутствуют пункты Android SDK Manager и Android Virtual Device Manager, нужно просто включить отображение этих опций через меню Eclipse:

Window > Customize perspective > Command Groups Availability

В открывшейся вкладке выбираем пункт Android SDK and AVD Manager и жмём OK.

На будущее, расположение Android SDK настраивается через меню:

Window > Preferences > Android

первая строка это то что нам нужно.

Теперь установка комплекта разработчика завершена и можно приступать к самому интересному, но это уже совсем друная история.

Возможная ошибка при запуске Android приложения в Eclipse:

error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory

Устранятся установкой пакета ia32-libs:

sudo apt-get install ia32-libs

Зачем нужны JAR-архивы?

Использование JAR-архивов предоставляет разработчикам аплетов (а также и автономных приложений) ряд преимуществ.

  • Повышение эффективности загрузки. Вместо нескольких файлов отдельных классов загружается единственный файл JAR-архива.
  • Улучшенное хранилище файлов, так как файлы классов хранятся в одном сжатом файле архива.
  • Повышение защищённости. В JAR-файл можно поместить цифровую подпись, дающею конечному пользователю гарантии, что файл архива не изменился с момента её внесения. Если конечный пользователь доверяет вашей фирме, он может предоставить подписанным вами аплетам право доступа к его жестким дискам или другим важным ресурсам системы.
  • Независимость от платформы. Построение JAR-архивов базируется на использовании популярной программы PKZIP сжатия файлов. Однако JAR-файлы могут создаваться и сохраняться на любой компьютерной платформе.
  • Расширяемость. В спецификацию JAR-файлов внесены некоторые дополнения, обеспечивающие возможность дальнейших расширений.

Одно только повышение эффективности загрузки даёт какое преимущество! Если вы знакомы с работой протокола HTTP, то должны понимать насколько неэффективно, с его помощью, выполнять передачу нескольких небольших файлов. В каждом случае потребуется установить новое соединение TCP/IP, которое после передачи данного файла будет разорвано. Установка каждого соединения связана с дополнительной нагрузкой на сервер и сеть. При использовании коммутируемых линий связи среднее время установки TCP/IP-соединения может составлять около 0,5 c. Если для работы аплета необходимо 16 файлов, общее время затрачиваемое только на установку соединений — 8 с.

Создание JAR-архива

Для создания и модификации JAR-архивов можно использовать любую поддерживающую формат PKZIP программу. JAR-файл отличается от zip-файла наличием дополнительного текстового файла, называемого файлом описания (manifest file). Этот файл содержит сведения обо всех помещённых в данный архив файлах. В состав файла описания должны входить определённые элементы, в частности он должен включать следующие.

  • Номер версии стандарта JAR. В соответствии, с которым построен данный архив. Этот номер задаётся параметром Manifest-Version и является обязательным. В SDK 1.2 значение этого параметра должно быть равно 1.0
  • Минимальный номер версии утилиты JAR, которая сможет прочитать этот архив. Этот параметр необязателен и имеет название Required-Version.
  • Отдельная запись для любого, помещённого в архив файла. Необязательно перечислять все помещённые в архив файлы, достаточно указать только файлы главных классов.

Фирма Sun предоставила, для создания JAR архивов, специализированный инструмент для всех поддерживаемых платформ. Рассмотрим создание JAR-архива с помощью этой утилиты под названием jar.

Предположим, что у нас имеется каталог, содержащий несколько файлов типа .class и подкаталог с именем images содержащий несколько файлов типа .gif. Пусть имя создаваемого архива будет archive.jar

Общий формат команды вызова утилиты jar: jar параметры имена_файлов

Параметр имена_файлов представляет собой список имён файлов, первым в котором всегда указывается имя самого архивного файла. Назначение остальных имён файлов зависит от ключей:

c
— создать новый архив.
m
— использовать внешний файл описания, имя которого указано вторым в списке имена_файлов.
M
— не создавать файл описания.
t
— вывести содержание указанного архивного.
x
— извлечь файлы, указанные в списке имена_файлов. Если имена не указаны, то извлечь все файлы.
f
— указывает, что имя архивного файла помещено первым в списке имена_файлов.
v
— указывает, что утилита должна сопровождать сообщениями выполнение всех действий, заданных другими параметрами.
0
— сохранение файлов в архиве выполняется без их сжатия.
u
— указывает, что нужно обновить указанные файлы. Или в случае команды jar umf manifest имя_архива указывает что нужно обновить информацию в файле описания.
-i
— указывает, что необходимо сгенерировать файл INDEX.LIST содержащий информацию о всех файлах архива.

И так для создания нового архива введите:
jar cf archive.jar .class images/.gif

Помещение в JAR-архив цифровой подписи

Для того чтобы разобраться, как цифровая подпись помещается в JAR-архив, необходимо предварительно усвоить определённые базовые понятия из области криптографии -шифрование с помощью открытого ключа.

Для внесения цифровой подписи в JAR-архив необходимо иметь два инструмента.

  • Утилита keytool. Используется для генерации пары открытого и закрытого ключа и сертификата.
  • Утилита jarsigner. Используется для непосредственного помещения цифровой подписи в JAR-архивы с применением имеющегося сертификата.

Что нам понадобится:

  1. Выполнить генерацию пары ключей.
  2. Получить сертификат на эту пару.
  3. Использовать сертификат для помещения цифровой подписи в JAR-архив.

Генерация пары ключей

Для генерации новой пары ключей можно использовать следующею команду:
keytool -genkey -alias testkey

В результате выполнения этой команды будет создана новая пара ключей сохранённая в базе данных под именем testkey. Вот что у нас получится:
testkey

Программа keytool позволяет указывать следующие параметры:

-v
— запрос вывода сообщений о действии программы.
-alias псевдоним
— псевдоним (имя) который присваивается этой паре.
-keyalg алгоритм_ключа
— алгоритм шифрования вашей подписи — обычно по умолчанию это алгоритм SHA1 with DSA и его можно не указывать если не собираетесь изменять его, размер ключа при генерации DSA ключевой пары может быть от 512 до 1024 бит, но если вы хотите применить MD5 with RSA то укажите опцию -keyalg «RSA» Следует заметить что опция -keyalg обуславливает и опцию -sigalg — алгоритм подписи который будет использован по умолчанию для подписания jar-файла (при создании дайджестов сообщений).
-keysize длина_ключа
— размер генерируемых ключей в битах.
-keypass пароль
— пароль для данного ключа. Если пароль не будет указан в командной строке, программа предложит ввести его значение в режиме диалога. Длина пароля должна быть не менее шести символов.
-keystore хранилище
— расположение хранилища ключей.
-storepass пароль
— пароль доступа к хранилищу ключей.
-validity valDays
— срок годности вашего сертификата. По умолчанию это 180 дней, можно указать больше или меньше.

По умолчанию утилита keytool помещает открытый ключ в подписанный вами же сертификат X.509.v1.

Командой keytool -list мы можем посмотреть содержимое keystore:
testkey1

Получение сертификата

После генерации новой пары ключей необходимо выполнить генерацию запроса CSR (Certificate Signing Request). Этот запрос отсылается в любую выбранную службу сертификации.

Для генерации CSR запроса введите следующею команду:
keytool -certreq

Для нашей пары ключей testkey команда будет выглядеть следующим образом:
keytool -certreq -alias testkey
testkey2

При генерации CSR запроса допускаются следующие параметры:

-v
— сопровождение работы программы выдачей сообщений.
-alias псевдоним
— определение псевдонима пары ключей, для которых необходимо получить сертификат. По умолчанию применяется значение mekey.
-sigalg алгоритм_сигнатуры
— задание используемого алгоритма внесения подписи.
-file csr_файл
— имя и расположение файла, в котором помещается генерируемый запрос.
-keypass пароль
— пароль для доступа к данному ключу.
-storepass пароль
— пароль доступа к хранилищу ключей.
-keystore хранилище
— имя и расположения файла с парой ключей.

Сгенерированный CSR-запрос отсылается в выбранную службу сертификации. После проведения всех необходимых проверок и удостоверения вашей личности вам будет выдан требуемый сертификат.

В любом случае самостоятельно занесите полученный сертификат в файл и введите следующею команду:
keytool -import

Следующие параметры могут быть указаны в режиме import:

-v
— сопровождение работы программы выдачей сообщений.
-alias псевдоним
— псевдоним полного имени, которое будет использоваться с данным сертификатом.
-file файл-сертификата
— имя и расположение файла, в котором сохранён полученный сертификат.

Команда:
keytool -export -alias testkey -file имя_файла
даст указание утилите скопировать ваш сертификат в указанный файл. Отправьте свой сертификат всем получателям, которые будут использовать подписанные вами JAR-архива.

Помещение в JAR-архив цифровой подписи с помощью утилиты jarsigner

Помимо помещения цифровой подписи в JAR-архивы утилита jarsigner способна также проверять целостность подписанных JAR-архивов. Для этого достаточно запустить её с параметром -verify/

При вводе команды:
jarsigner myJarFile.jar
тилита воспользуется хранилищем ключей по умолчанию и результат будет помещён в файл myJarFile.jar, который заместит исходный файл архива.

Для создания JAR-архива подписанного нашими ключами testkey введите следующею команду:
jarsigner myJarFile.jar testkey

Для того чтобы файл myJarFile.jar остался неизменённым а результат записался, например, в файл mySignedJarFile.jar введите команду:
jarsigner -signedjar mySignedJarFile.jar myJarFile.jar testkey.

 

Classpath представляет собой связующее звено между исполняемым модулем Java и файловой системой. Он определяет, где интерпретатор ищет файлы класса для загрузки. Основная идея состоит в том, что иерархия файловой системы отражает иерархию пакета Java, а classpath определяет, какие директории в файловой системе служат корневыми каталогами для иерархии пакета Java.

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

Хорошая IDE типа Eclipse может защитить вас от некоторых трудностей управления classpath, но только отчасти, и только если ничего не случится (а что-то постоянно случается). Следовательно, каждый Java-программист непременно должен полностью понимать classpath. Только при глубоком понимании вы можете надеяться на устранение сложных проблем, которые возникают из-за classpath.

В этой статье я излагаю все, что вам нужно знать о Java classpath (и соответствующем sourcepath) в UNIX, Linux, и Mac OS X.  Следование методам, изложенным здесь, послужит вам руководством на этом пути и должно помочь в решении большинства проблем с classpath.

Структура пакета

Освоение classpath начинается с исходного кода. Каждый класс принадлежит пакету, и этот пакет программ должен следовать стандартным соглашениям по именованию. Сделаем краткий обзор: Имя пакета начинается с двухуровневого обратного имени домена, например com.example или edu.poly. За ним следует по крайней мере еще одно слово, которое описывает содержимое пакета программ. Например, так как я являюсь владельцем имени домена elharo.com, если бы я должен был написать класс Fraction, я мог бы расположить его в одном из следующих пакетов:

  • com.elharo.math
  • com.elharo.numbers
  • com.elharo.math.algebra.fields

После обратного имени домена используйте только имена подпакета, состоящие из одного слова. Не используйте сокращения и пишите все слова правильно. Используйте программу проверки орфографии, если вам это нужно. Большой процент проблем, связанных с classpath, вызван использованием одного слова в исходной программе и слегка измененного написания или сокращения этого слова в файловой системе. Единственный разумный выбор — всегда использовать правильно написанные, несокращенные имена.

Полное имя пакета должно быть написано строчными буквами, даже для имен собственных и аббревиатур, которые обычно пишутся заглавными буквами. Имя пакета должно быть составлено только из ASCII-символов. Хотя компилятор принимает имена пакета, записанные на иврите, кириллице, греческом, и других шрифтах, многие файловые системы не принимают их. Как вы скоро увидите, эти имена пакетов будут выполнять двойную задачу в качестве имен директорий. Следовательно, имена пакетов (и классов) должны быть ограничены ASCII. (В то время как в пакетах Java и именах классов используется юникод, многие файловые системы ещё его не понимают. Простое копирование файла в систему с другой кодировкой по умолчанию может помешать компилятору и интерпретатору найти правильные классы.)

Одноразовая программа

Если вы просто пишите отдельный класс, чтобы проверить вашу интуицию в отношении API, и вы выбросите его сразу же после того, как вы его однократно запустите, тогда вам не нужно вкладывать его в пакет. Тем не менее, любой класс, который будет использоваться чаще, чем один раз, должен быть в пакете.

Не экономьте на имени вашего пакета! Это только приведет к катастрофе в конечном счете. Если вам нужно имя домена, купите его. Если имена слишком длинные, приобретите имя покороче. (Я однажды приобрел xom.nu, таким образом мой префикс пакета составлял только шесть символов.) Не располагайте ваши классы в пакете по умолчанию (пакете, который вы получаете, если не включаете оператор пакета в класс). Если доступ пакета мешает объектам посылать сообщения, добавьте больше общедоступных методов в классы. Каждый класс, который будет использоваться чаще, чем один раз, должен быть в пакете.

Структура директории

Следующий шаг — это организовать ваши исходные файлы так, чтобы они соответствовали структуре пакета. Создайте где-нибудь чистую, пустую директорию. Для целей этой статьи я назову ее project (проект). Внутри этой директории создайте еще две директории: bin и src. (Некоторые люди предпочитают называть их build и source соответственно.)

Затем внутри директории src создайте иерархию, которая зеркально отображает вашу иерархию пакета. Например, при классе с именем com.elharo.math.Fraction, я бы расположил директорию com в директории src. Затем я бы создал директорию elharo внутри директории com. Затем я бы поместил директорию math внутрь директории elharo. И наконец, я бы поместил Fraction.java внутри этой директории math, как показано на рисунке 1:

Рисунок 1. Структура директории отражает структуру пакета

Fraction

Очень важно: никогда не помещайте ничего, кроме исходного кода в вашу директорию src. Обычно единственные файлы, которые вы помещаете туда, это .java-файлы. Иногда вы можете расположить .html-файлы (для Javadoc) или другие типы исходного кода в эту директорию. Тем не менее, вы не должны помещать .class-файлы или другие скомпилированные, сгенерированные артефакты в эту иерархию. Выполнение этого действия непременно приведет к катастрофе. К сожалению, компилятор javac сделает именно так, если вы не проявите осторожность. В следующем разделе я покажу вам, как наладить это.

В начало

Компилирование

Компилирование кода Java — коварная вещь, потому что вам нужно отслеживать несколько связанных, но разных вещей:

  • Целевой файл, который вы компилируете.
  • Директорию, где компилятор ищет .java-файлы, которые импортирует целевой файл.
  • Директорию, где компилятор ищет .class-файлы, которые импортирует целевой файл.
  • Директорию, куда компилятор помещает скомпилированный вывод.

По умолчанию, компилятор javac думает, что все это — текущая рабочая директория, что почти всегда не то, что вам нужно. Следовательно, при компиляции вы должны четко точно определить каждый из элементов.

Файл для компиляции

Первое, что вы определяете, — это .java-файл, который вы собираетесь компилировать. Это дается как путь к файлу из текущей рабочей директории. Например, предположим, что вы в директории project, как показывает Рисунок 1. Эта директория содержит директорию src. Директория src содержит директорию com, которая содержит директорию example, которая содержит файл Fraction.java. Следующая командная строка компилирует его:

$ javac src/com/elharo/math/Fraction.java

Если путь неверен, вы получите сообщение об ошибке следующего рода:

error: cannot read: src/com/example/mtah/Fraction.java

Если вы видите такое сообщение об ошибке, проверьте каждый фрагмент пути, чтобы убедиться, что он написан правильно. Затем проверьте, находится ли файл именно там, где он должен находиться, выполнив ls, как показано здесь:

$ ls src/com/example/math
ls: src/com/example/math: No such file or directory

Эта проблема обычно указывает на неверно набранный путь, но она также может означать, что вы не находитесь в той директории, в которой вы рассчитываете находиться. В этом примере вы должны убедиться, что текущая рабочая директория — это директория проекта. Здесь поможет команда pwd. Например, следующее сообщает мне, что на самом деле я в project/src, а не в директории project:

$ pwd
/Users/elharo/documents/articles/classpath/project/src

Мне нужно выполнить команду cd ... перед компилированием.

Куда идет вывод

Предполагая, что синтаксические ошибки отсутствуют, javac располагает скомпилированный .class-файл в той же директории, где находится .java файл. Вам это не нужно. Смешивание файлов .class и .java затрудняет очистку скомпилированных файлов без случайного удаления .java-файлов, которые вы хотите сохранить. Это делает чистую сборку проблематичной и ведет к возникновению различных проблем. Это также затрудняет упаковку в jar только что скомпилированных .class-файлов при распространении двоичного файла. В связи с этим вам нужно велеть компилятору поместить скомпилированный вывод в совершенно другую директорию. Переключатель -d точно определяет директорию вывода (обычно называемую bin, build, или classes):

$ javac -d bin src/com/elharo/math/Fraction.java

Сейчас вывод такой, как показано на рисунке 2. Обратите внимание, что javac создал полную иерархию директорий com/elharo/math. Вам не нужно делать это вручную.

Рисунок 2. Параллельные иерархии исходных и скомпилированных файлов

CompiledFraction

Путь к исходным файлам (sourcepath)

Директория, где Java ищет исходные файлы называется sourcepath. В схеме, намеченной здесь, это src директория. Это директория, которая содержит иерархию исходных файлов, организованных в их собственных директориях. Это не директория com или src/com/elharo/math.

Большинство проектов используют больше, чем один класс и больше, чем один пакет. Они связаны импортирующими операторами и полностью подготовленными для пакета именами класса. Например, предположим, что вы сейчас создаете новый класс MainFrame в пакете com.elharo.gui, как показано в листинге 1:

Листинг 1. Класс в одном пакете может импортировать класс в другом
package com.elharo.gui;

import com.elharo.math.*;

public class MainFrame {

  public static void main(String[] args) {
    Fraction f = new Fraction();
    // ...
  }
}

Этот класс использует класс com.elharo.math.Fraction в другом пакете из класса MainFrame. Установка исходных файлов сейчас такова, как показано на рисунке 3. (Я удалил скомпилированный вывод из предыдущего шага. Я всегда смогу скомпилировать его снова.)

Рисунок 3. Структура исходных файлов для нескольких пакетов

MainFrameSource

Сейчас давайте посмотрим, что происходит, когда я пытаюсь скомпилировать MainFrame.java так, как я делал это ранее:

Листинг 2. Компилирование MainFrame.java
$ javac -d bin src/com/elharo/gui/MainFrame.java
src/com/elharo/gui/MainFrame.java:3: package com.elharo.math does not exist
import com.elharo.math.*;
^
src/com/elharo/gui/MainFrame.java:7: cannot find symbol
symbol  : class Fraction
location: class com.elharo.gui.MainFrame
  private Fraction f = new Fraction();
          ^
src/com/elharo/gui/MainFrame.java:7: cannot find symbol
symbol  : class Fraction
location: class com.elharo.gui.MainFrame
  private Fraction f = new Fraction();
                           ^
3 errors

Ошибки в листинге 2 произошли, потому что хотя javac знал, где найти MainFrame.java, он не знал, где найти Fraction.java. (Вы подумаете, что будет достаточно разумно заметить соответствующие иерархии пакета, но это не так.) Раскрывая секрет, я должен указать sourcepath. Он указывает директории, где компилятор ищет иерархию исходных файлов. В листинге 2 это src. Итак, я использую опцию -sourcepath, например, так:

$ javac -d bin -sourcepath src src/com/elharo/gui/MainFrame.java

Сейчас программа компилируется без ошибок и производит вывод, показанный на рисунке 4. Обратите внимание, что javac также скомпилировал файл Fraction.java, на который ссылается файл, который я компилировал.

Рисунок 4. Многоклассовый вывод

multipackagecompile

Компилирование множественных директорий в sourcepath

На самом деле вы можете иметь более одной директории в вашем sourcepath (они разделяются двоеточиями), хотя это обычно не нужно. Например, если я хочу включить и локальную директорию src, и директорию /Users/elharo/Projects/XOM/src, где я храню исходный код для другого проекта, я могу компилировать следующим образом:

$ javac -d bin -sourcepath src:/Users/elharo/Projects/XOM/src
  src/com/elharo/gui/MainFrame.java

Эта команда не компилирует каждый файл, найденный в какой-либо из этих иерархий. Она компилирует только файлы, на который делается ссылка прямо или косвенно одним .java-файлом, который я явно прошу скомпилировать.

Гораздо чаще у вас будет одна директория source для .java файлов, но много директорий для классов или JAR-архивов, где расположены прекомпилированные библиотеки сторонних производителей. Такова роль classpath.

Установка classpath

В средних и больших проектах перекомпилирование каждого файла каждый раз может занимать очень много времени. Вы можете облегчить нагрузку, отдельно компилируя и храня независимые части одного и того же проекта в разных классах или директориях bin. Эти директории добавляются к classpath.

Есть несколько способов добавить класс к classpath. Переключатель командной строки -classpath, тем не менее, — единственное, что следует использовать. Например, предположим, что я хочу импортировать файлы из другого проекта, который я до этого скомпилировал в директорию /Users/elharo/classes. Затем я бы добавил -classpath /Users/elharo/classes к командной строке следующим образом:

$ javac -d bin -sourcepath src -classpath /Users/elharo/classes
  src/com/elharo/gui/MainFrame.java

Теперь предположим, что мне нужно добавить две директории, /Users/elharo/project1/classes и /Users/elharo/project2/classes. Тогда я бы включил обе, разделив их двоеточием, как показано далее:

$ javac -d bin -sourcepath src
  -classpath /Users/elharo/project1/classes:/Users/elharo/project2/classes
  src/com/elharo/gui/MainFrame.java

Директории верхнего уровня

Обратите внимание, что директории, которые я указываю здесь, все являются директориями верхнего уровня, которые содержат иерархию, подобную com/elharo/foo/bar or nu/xom/util. Директории, имена которых соответствуют именам пакета (com, elharo, math, etc.) никогда не включаются прямо в sourcepath или classpath.

Конечно, вы можете использовать различные формы соответствующих путей, если вы предпочитаете. Например, если project1 и project2 — «братья» текущей рабочей директории (то есть, они имеют одну и ту же родительскую директорию), тогда я бы мог сделать на них ссылку на них следующим образом:

$ javac -d bin -sourcepath src
  -classpath ../project1/classes:../project2/classes
  src/com/elharo/gui/MainFrame.java

До сих пор я предполагал, что программа завершается сама и не использует никакие отдельно скомпилированные библиотеки сторонних производителей. Но если она использует их, вам нужно их также добавить к classpath. Библиотеки обычно размещаются как JAR-файлы, например junit.jar или icu4j.jar. В этом случае вы добавляете именно JAR-файл к classpath, а не директорию, которая содержит его. (По существу, JAR-файл действует как директория, которая содержит скомпилированные .class-файлы.) Например, следующая команда добавляет три вещи к classpath: директорию /Users/elharo/classes, файл icu4j.jar в текущей рабочей директории, и файл junit.jar в /Users/elharo/lib:

$ javac -d bin -sourcepath src
  -classpath /Users/elharo/classes:icu4j.jar:/Users/elharo/lib/junit.jar
  src/com/elharo/gui/MainFrame.java

JAR-файлы используются только для .class-файлов и classpath, а не для .java-файлов и sourcepath.

В начало

Запуск программы

Сейчас вы успешно скомпилировали вашу программу и готовы запустить ее. Это похоже на компилирование, но проще его. Во время запуска программы вам нужно определить только две вещи:

  • classpath.
  • Полностью классифицированное для пакета имя класса, которое содержит ваш main () method.

Вам не нужно указывать sourcepath.

Обычно classpath — это тот же самый classpath, который вы использовали, чтобы скомпилировать программу, с добавлением директории, куда был размещен скомпилированный вывод. Например, если команда компилирования была следующей:

$ javac -d bin -sourcepath src
  -classpath /Users/elharo/classes:/Users/elharo/lib/junit.jar
  src/com/elharo/gui/MainFrame.java

и метод main () был в классе com.elharo.gui.MainFrame, тогда вы будете запускать программу следующим образом:

$ java
   -classpath bin:/Users/elharo/classes:/Users/elharo/lib/junit.jar
    com.elharo.gui.MainFrame

Обратите особое внимание, что последний элемент командной строки — это имя класса. Это не имя файла. Оно не заканчивается на .java или .class. Этот класс должен быть найден где-нибудь в classpath.

В начало

Другие места, где располагаются классы

Я настоятельно рекомендую вам точно указывать classpath, когда вы компилируете и когда запускаете программу. Есть другие места, куда вы можете помещать файлы так, что они добавляются к classpath и обнаруживаются и компилятором javac и интерпретатором java. Эти опции экономят лишь немного набора текста, и они делают это за счет большого количества отлаживания, когда — а не если — вы случайно располагаете старую версию класса в classpath.

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

Текущая рабочая директория

Компилятор всегда добавляет текущую рабочую директорию (.) к classpath, запрашиваете вы это явно или нет. Очень легко забыть, что находится или не находится в той же директории, где и вы. Таким образом, попытайтесь избежать размещения любых классов или иерархий в ваш проект или начальную директорию. Вместо этого всегда храните вещи аккуратно разделенными на src-директории для .java-файлов и директории bin для .class-файлов.

CLASSPATH

Через некоторое время вы можете устать от ручного добавления директорий bin и JAR-архивов к classpath. Тогда вы можете открыть для себя переменную среды classpath. Вы можете добавить директории и JAR-архивы к переменной среды classpath только один раз. Затем вам не придется набирать их пути каждый раз, когда вы запускаете javac или java.

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

jre/lib/ext

JAR-архивы, расположенные в вашей jre/lib/ext директории, добавляются к classpath всех приложений, запущенных на данной виртуальной машине. Хотя это кажется удобным, эта ошибка даст о себе знать позже, она сродни добавлению директорий к переменной среды classpath. Рано или поздно (возможно рано), вы загрузите неверную версию класса из места, о котором вы даже и не думаете, и потеряете много времени на отладку.

Эта проблема особенно серьезна во время использования серверных приложений. Убедитесь, что сервер, который вы используете, не имеет никаких дополнительных JAR'ов в своей директории jre/lib/ext. Проблемы, вызванные неверной версией JAR-архива в classpath может быть чрезвычайно трудно устранить, если вы не распознаете признаки или не знаете, что конкретно искать. Чтобы избежать этих проблем, некоторые среды разработки зашли настолько далеко, что пишут свои собственные программы загрузки классов, которые обходят обычные загрузочные механизмы кода Java.

jre/lib/endorsed

JAR-файлы в директории jre/lib/endorsed также добавляются к classpath всех приложений, запущенных на данной виртуальной машине. Разница заключается в том, что эти файлы на самом деле добавляются скорее к bootclasspath, чем обычному classpath, и могут замещать стандартные классы, поставляющиеся с JDK. Этот подход особенно полезен для модернизации XML-анализатора и устранения ошибок в виртуальной машине.

Еще раз повторю, что хотя этот способ кажется удобным, это долговременная ошибка по той же самой причине. Если вам нужно переместить классы JDK, используйте опцию -Xbootclasspath/p во время запуска, чтобы избежать случайной загрузки неверной версии класса:

$ java -classpath /Users/elharo/classes
       -Xbootclasspath/p:xercesImpl.jar com.elharo.gui.MainFrame

В начало

Автоматизация управления classpath

Вам следует научиться использовать молоток, прежде чем вы возьмете в руки «пистолет» для забивания гвоздей. Точно так же, вы сперва должны научится чувствовать себя уверенно при ручном управлении классами, прежде чем вы попытаетесь использовать более мощные инструменты. Тем не менее, существуют инструменты, разработанные для того, чтобы уменьшить мучения от операций с sourcepath и classpath. Чаще всего они делают это, организуя для вас файлы в соответствии с теми принципами, которые я изложил в этой статье.

IDE

Большинство интегрированных сред разработки, например Eclipse и NetBeans автоматизируют управление classpath и помогают в некоторых его аспектах. Например, когда вы меняете имя пакета, Eclipse предлагает передвинуть соответствующий .java-файл, чтобы он совпадал, как показано на рисунке 5:

Рисунок 5. Быстрое исправление classpath в Eclipse

quickfix

Помните, однако, что эти IDE по-прежнему располагаются на вершине файловой системы, что должно быть должным образом настроено, особенно, если вам нужно осуществить интеграцию с другими инструментами и другими IDE. Главный вклад этих инструментов состоит в том, что переключатели командной строки заменяются графическими диалогами, древовидными списками и вкладками; основная же файловая структура остается прежней.

Ant

Ant — это, фактически, стандартный инструмент для автоматизации процесса сборки. В отличие от расположения директорий в jre/lib/ext или в переменной среды CLASSPATH, Ant на самом деле позволяет вам создать пошаговые процессы сборки. Вам все еще нужно настроить classpath в файле build.xml редактора Ant и вручную расположить исходные файлы в правильных директориях, но, по крайней мере, вам не нужно указывать их повторно каждый раз во время компиляции.

Maven

Maven идет еще дальше, чем Ant в организации и автоматизации процесса сборки и связанных с ним операций с classpath. Maven обеспечивает рациональную установку по умолчанию, которая помогает вам компоновать простые проекты лишь с несколькими строками кода, если вы располагает исходные файлы там, где Maven ожидает их найти. Вам все-таки придется координировать иерархию файловой системы и иерархию пакета. Maven особенно полезен при управлении зависимостями в библиотеках сторонних производителей, хотя его не так легко настраивать, как Ant.

В начало

В заключение

Даже такую непростую проблему как classpath, вы можете решить, используя несколько простых правил. В частности:

  • Располагайте каждый класс в пакете.
  • Точно следуйте стандартам называния пакета и класса и правилам использования прописных букв.
  • Убедитесь, что иерархия вашего пакета соответствует иерархии директории.
  • Всегда используйте опцию -d для javac.
  • Никогда ничего не помещайте в jre/lib/ext.
  • Никогда ничего не помещайте в jre/lib/endorsed.
  • Никогда не помещайте .java-файлы в ту же самую директорию, что и .class-файлы.
  • Никогда не помещайте никакие .java или .class-файлы в текущую рабочую директорию.

Один последний совет: много проблем с classpath, требующих большого расхода времени, вращаются вокруг простых ошибок, таких как неправильное написание имени директории или компилирование из неверной директории. Если вы не можете определить, что идет не так, попросите друга или коллегу взглянуть на вашу проблему. Часто я обнаруживаю, что нахожусь слишком близко к ошибке, чтобы её увидеть, а для кого-то ещё она сразу же очевидна. Еще одна пара глаз — это эффективный метод устранения ошибок.

Classpath не легок, но он управляем, и есть способ справиться с его сумасшествием. Немного осторожности и внимания к заданию имён, аргументам командной строки, и структуре директорий должны помочь вам скомпилировать и запустить программы с минимальными трудностями.

Сейчас уже никто не создает программы в консоли. Используя любимую IDE, разработчик чувствует себя неуютно за чужим компьютером, где её нет.
Решив разобраться в работе Ant и Maven, я поймал себя на том, что не смогу собрать приложение без них в консоли.
В данной статье я постарался уместить все этапы проектирования демонстрационного приложения, чтобы не искать справку по каждой команде на просторах Интернета.

От простого к ...

Каждая программа обычно содержится в отдельном каталоге. Я придерживаюсь правила создавать в этом каталоге по крайней мере две папки: src и bin. В первой содержатся исходные коды, во второй — результат компиляции. В данных папках будет структура каталогов, зависящая от пакетов.

Один файл

Можно сделать и без лишних папок.
Берем сам файл.

publicclassHelloWorld
{
    publicstaticvoidmain(String[] args)
    {
        System.out.println("Hello World!");
        Calculator calc=newCalculator();
        System.out.println("2+2="+calc.sum(2,2));
    }
}

Переходим в каталог, где лежит данный файл, и выполняем команды.

    javac HelloWorld.java

В данной папке появится файл HelloWorld.class. Значит программа скомпилирована. Чтобы запустить

    java -classpath . HelloWorld

 

Отделяем бинарные файлы от исходников


Теперь сделаем тоже самое, но с каталогами. Создадим каталог HelloWorld и в нем две папки src и bin.
Компилируем

    javac -d bin src/HelloWorld.java

Здесь мы указали, что бинарные файлы будут сохраняться в отдельную папку bin и не путаться с исходниками.

Запускаем

    java -classpath ./bin HelloWorld

 

Используем пакеты


А то, вдруг, программа перестанет быть просто HelloWorld-ом. Пакетам лучше давать понятное и уникальное имя. Это позволит добавить данную программу в другой проект без конфликта имен. Прочитав некоторые статьи, можно подумать, что для имени пакета обязательно нужен домен. Это не так. Домены — это удобный способ добиться уникальности. Если своего домена нет, воспользуйтесь аккаунтом на сайте (например, ru.habrahabr.mylogin). Он будет уникальным. Учтите, что имена пакетов должны быть в нижнем регистре. И избегайте использования спецсимволов. Проблемы возникают из-за разных платформ и файловых систем.

Поместим наш класс в пакет с именем com.qwertovsky.helloworld. Для этого добавим в начало файла строчку

    packagecom.qwertovsky.helloworld;

В каталоге src создадим дополнительные каталоги, чтобы путь к файлу выглядел так: src/com/qwertovsky/helloworld/HelloWorld.java.
Компилируем

    javac -d bin src/com/qwertovsky/helloworld/HelloWorld.java

В каталоге bin автоматически создастся структура каталогов как и в src.

    HelloWorld
    '---bin
    '   '---com
    '       '---qwertovsky
    '          '---helloworld
    '             '---HelloWorld.class
    '---src
        '---com
            '---qwertovsky
                '---helloworld
                    '---HelloWorld.java

Запускаем

    java -classpath ./bin com.qwertovsky.helloworld.HelloWorld

 

Если в программе несколько файлов


Изменим программу. Не обращайте внимание на логику. Её нет.

HelloWorld.java
packagecom.qwertovsky.helloworld;
publicclassHelloWorld
{
    publicstaticvoidmain(String[] args)
    {
        inta=2;
        intb=3;
        Calculator calc=newCalculator();
        System.out.println("Hello World!");
        System.out.println(a+"+"+b+"="+calc.sum(a,b));
    }
}
Calculator.java
packagecom.qwertovsky.helloworld;

importcom.qwertovsky.helloworld.operation.Adder;
publicclassCalculator
{
    publicintsum(int... a)
    {
        Adder adder=newAdder();
        for(inti:a)
        {
            adder.add(i);
        }
        returnadder.getSum();
    }
}
Adder.java
packagecom.qwertovsky.helloworld.operation;

publicclassAdder
{
    privateintsum;

    publicAdder()
    {
        sum=0;
    }

    publicAdder(inta)
    {
        this.sum=a;
    }

    publicvoidadd(intb)
    {
        sum+=b;
    }
    publicintgetSum()
    {
        returnsum;
    }
}

Компилируем

    javac  -d bin src/com/qwertovsky/helloworld/HelloWorld.java
    srccomqwertovskyhelloworldHelloWorld.java:9: cannot find symbol
    symbol  : class Calculator
    location: class com.qwertovsky.helloworld.HelloWorld
                Calculator calc=new Calculator();
                ^
    srccomqwertovskyhelloworldHelloWorld.java:9: cannot find symbol
    symbol  : class Calculator
    location: class com.qwertovsky.helloworld.HelloWorld
                Calculator calc=new Calculator();
                                    ^
    2 errors

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

    javac -sourcepath ./src -d bin src/com/qwertovsky/helloworld/HelloWorld.java

Запускаем

    java -classpath ./bin com.qwertovsky.helloworld.HelloWorld
    Hello Word
    2+3=5

 

Если удивляет результат


Есть возможность запустить отладчик. Для этого существует jdb.
Сначала компилируем с ключом -g, чтобы у отладчика была информация.

    javac -g -sourcepath ./src -d bin src/com/qwertovsky/helloworld/HelloWorld.java

Запускаем отладчик

    jdb -classpath bin -sourcepath src com.qwertovsky.helloworld.HelloWorld
    Initializing jdb ...
    >

Отладчик запускает свой внутренний терминал для ввода команд. Справку по последним можно вывести с помощью команды help.
Указываем точку прерывания на 9 строке в классе Calculator

    > stop at com.qwertovsky.helloworld.Calculator:9
    Deferring breakpoint com.qwertovsky.helloworld.Calculator:9.
    It will be set after the class is loaded.

Запускаем на выполнение.

    > run
    run com.qwertovsky.helloworld.HelloWorld
    Set uncaught java.lang.Throwable
    Set deferred uncaught java.lang.Throwable
    >
    VM Started: Set deferred breakpoint com.qwertovsky.helloworld.Calculator:9
    Hello World!
    Breakpoint hit: "thread=main", com.qwertovsky.helloworld.Calculator.sum(), line=9 bci=0
    9               Adder adder=new Adder();

Чтобы соориентироваться можно вывести кусок исходного кода, где в данный момент находится курссор.

    main[1] list
    5    public class Calculator
    6    {
    7       public int sum(int... a)
    8       {
    9 =>            Adder adder=new Adder();
    10              for(int i:a)
    11              {
    12                      adder.add(i);
    13              }
    14              return adder.getSum();

Узнаем, что из себя представляет переменная а.

    main[1] print a
     a = instance of int[2] (id=340)
    main[1] dump a
     a = {
    2, 3
    }
    main[1] stop at com.qwertovsky.helloworld.operation.Adder:19
    Deferring breakpoint com.qwertovsky.helloworld.operation.Adder:19.
    It will be set after the class is loaded.

Продолжим исполнение.

    main[1] cont
    > Set deferred breakpoint com.qwertovsky.helloworld.operation.Adder:19

    Breakpoint hit: "thread=main", com.qwertovsky.helloworld.operation.Adder.add(), line=19 bci=0
    19              sum+=b;
    main[1] list
    15      }
    16
    17      public void add(int b)
    18      {
    19 =>           sum+=b;
    20      }
    21
    22      public int getSum()
    23      {
    24              return sum;
    main[1] print sum
     sum = 0
    main[1] print b
     b = 2

Выполним код в текущей строке и увидим, что sum стала равняться 2.

    main[1] step
    >
    Step completed: "thread=main", com.qwertovsky.helloworld.operation.Adder.add(), line=20 bci=10
    20      }
    main[1] print sum
     sum = 2

Поднимемся из класса Adder в вызвавший его класс Calculator.

    main[1] step up
    >
    Step completed: "thread=main", com.qwertovsky.helloworld.Calculator.sum(), line=10 bci=36
    10              for(int i:a)

Удаляем точку прерывания

    main[1] clear com.qwertovsky.helloworld.operation.Adder:19
    Removed: breakpoint com.qwertovsky.helloworld.operation.Adder:19
    main[1] step
    >
    Step completed: "thread=main", com.qwertovsky.helloworld.Calculator.sum(), line=12 bci=30
    12                      adder.add(i);

Можно избежать захода в методы, используя команду next.

    main[1] next
    >
    Step completed: "thread=main", com.qwertovsky.helloworld.Calculator.sum(), line=10 bci=36
    10              for(int i:a)
    main[1] next
    >
    Step completed: "thread=main", com.qwertovsky.helloworld.Calculator.sum(), line=14 bci=42
    14              return adder.getSum();

Проверяем значение выражения и завершаем выполнение.

    main[1] eval adder.getSum()
     adder.getSum() = 5
    main[1] cont
    > 2+3=5
    The application exited

 

Хорошо бы протестировать

Используем JUnit.

packagecom.qwertovsky.helloworld;

importstaticorg.junit.Assert.*;

importjava.util.Arrays;
importjava.util.Collection;

importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.junit.runners.Parameterized.Parameters;

@RunWith(value=org.junit.runners.Parameterized.class)
publicclassTestCalculator
{
    intexpected;
    int[] arg;

    @Parameters
    publicstaticCollection<int[][]> parameters()
    {

        returnArrays.asList(newint[][][]{
                {{4}, {2, 2}}
                ,{{-1},{4, -5}}
                ,{{0},{0,0,0}}
                ,{{0},{}}
                });
    }

    publicTestCalculator(int[] expected, int[] arg)
    {
        this.expected=expected[0];
        this.arg=arg;
    }
    @Test
    publicvoidtestSum()
    {
        Calculator c=newCalculator();
        assertEquals(expected,c.sum(arg));
    }
}

Компилируем

    mkdir test_bin
    javac  -classpath lib/path/junit-4.8.2.jar  -sourcepath ./src -d test_bin test/com/qwertovsky/helloworld/TestCalculator.java

Запускаем. В качестве разделителя нескольких путей в classpath в Windows используется ';', в Linux — ':'. В консоли Cygwin не работают оба разделителя. Возможно, должен работать ';', но он воспринимается как разделитель команд.

    java  -classpath lib/path/junit-4.8.2.jar:./test_bin  org.junit.runner.JUnitCore com.qwertovsky.helloworld.TestCalculator
    JUnit version 4.8.2
    ....
    Time: 0,031
    OK (4 tests)

 

Создадим библиотеку

Класс Calculator оказался полезным и может быть использован во многих проектах. Перенесем всё, что касается класса Calculator в отдельный проект.

    HelloWorld
    '---bin
    '---src
        '---com
            '---qwertovsky
                '---helloworld
                    '---HelloWorld.java
    Сalculator
    '---bin
    '---src
    '   '---com
    '       '---qwertovsky
    '           '---calculator
    '               '---Calculator.java
    '               '---operation
    '                   '---Adder.java
    '---test
        '---com
            '---qwertovsky
                '---calculator
                    '---TestCalculator.java
    

Измените также назавания пакетов в исходных текстах. В HelloWorld.java нужно будет добавить строку

    importcom.qwertovsky.calculator.Calculator;

Компилируем.

    cd Calculator
    javac -sourcepath src -d bin src/com/qwertovsky/calculator/Calculator.java

Делаем архив jar

    jar cvf calculator.jar -C bin .
    added manifest
    adding: com/(in = 0) (out= 0)(stored 0%)
    adding: com/qwertovsky/(in = 0) (out= 0)(stored 0%)
    adding: com/qwertovsky/calculator/(in = 0) (out= 0)(stored 0%)
    adding: com/qwertovsky/calculator/Calculator.class(in = 497) (out= 373)(deflated 24%)
    adding: com/qwertovsky/calculator/operation/(in = 0) (out= 0)(stored 0%)
    adding: com/qwertovsky/calculator/operation/Adder.class(in = 441) (out= 299)(deflated 32%)

С помощью ключа -C мы запустили программу в каталоге bin.

Надо узнать, что у библиотеки внутри


Можно распаковать архив zip-распаковщиком и посмотреть, какие классы есть в библиотеке.
Информацию о любом классе можно получить с помощью дизассемблера javap.

    javap -c -classpath calculator.jar com.qwertovsky.calculator.Calculator
    Compiled from "Calculator.java"
    public class com.qwertovsky.calculator.Calculator extends java.lang.Object{
    public com.qwertovsky.calculator.Calculator();
      Code:
       0:	aload_0
       1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
       4:	return

    public int sum(int[]);
      Code:
       0:	new	#2; //class com/qwertovsky/calculator/operation/Adder
       3:	dup
       4:	invokespecial	#3; //Method com/qwertovsky/calculator/operation/Adder."<init>":()V
       7:	astore_2
       8:	aload_1
       9:	astore_3
       10:	aload_3
       11:	arraylength
       12:	istore	4
       14:	iconst_0
       15:	istore	5
       17:	iload	5
       19:	iload	4
       21:	if_icmpge	42
       24:	aload_3
       25:	iload	5
       27:	iaload
       28:	istore	6
       30:	aload_2
       31:	iload	6
       33:	invokevirtual	#4; //Method com/qwertovsky/calculator/operation/Adder.add:(I)V
       36:	iinc	5, 1
       39:	goto	17
       42:	aload_2
       43:	invokevirtual	#5; //Method com/qwertovsky/calculator/operation/Adder.getSum:()I
       46:	ireturn
    }

Из результата видно, что класс содержит кроме пустого конструктора, ещё один метод sum, внутри которого в цикле вызывается метод add класса Adder. По завершении метода sum, вызывается Adder.getSum ().
Без ключа -c программа выдаст только список переменных и методов (если использовать -private, то всех).

    javap -private  -classpath calculator.jar com.qwertovsky.calculator.operation.Adder
    Compiled from "Adder.java"
    public class com.qwertovsky.calculator.operation.Adder extends java.lang.Object{
        private int sum;
        public com.qwertovsky.calculator.operation.Adder();
        public com.qwertovsky.calculator.operation.Adder(int);
        public void add(int);
        public int getSum();
    }

 

Лучше снабдить библиотеку документацией

Изменим для этого класс калькулятора.

packagecom.qwertovsky.calculator;

importcom.qwertovsky.calculator.operation.Adder;

/**
 * Калькулятор, который умеет складывать
 * @authorQwertovsky
 *
 */
publicclassCalculator
{
    /**
     * Определение суммы слагаемых
     * @parama массив слагаемых
     * @returnсумма
     */
    publicintsum(int... a)
    {
        Adder adder=newAdder();
        for(inti:a)
        {
            adder.add(i);
        }
        returnadder.getSum();
    }
}

Документацию можно создать следующей командой. При ошибке программа выдаст список возможных опций.

    mkdir doc
    javadoc -d doc -charset utf-8  -sourcepath src -author -subpackages com.qwertovsky.calculator

В результате получиться следующее
Calculator_388_292

Можно подписать jar-архив

Если требуется подписать свою библиотеку цифровой подписью, на помощь придут keytool и jarsigner.
Генерируем подпись.

    keytool -genkey -keyalg rsa -keysize 2048 -alias qwertokey -keystore path/to/qwerto.keystore
    Enter keystore password:
    Re-enter new password:
    What is your first and last name?
      [Unknown]:  Valery Qwertovsky
    What is the name of your organizational unit?
      [Unknown]:  Qwertovsky
    What is the name of your organization?
      [Unknown]:  Qwertovsky
    What is the name of your City or Locality?
      [Unknown]:  Tver
    What is the name of your State or Province?
      [Unknown]:  Tverskaya obl.
    What is the two-letter country code for this unit?
      [Unknown]:  RU
    Is CN=Valery Qwertovsky, OU=Qwertovsky, O=Qwertovsky, L=Tver, ST=Tverskaya	obl., C=RU correct?
      [no]:  y
    Enter key password for <qwertokey>
            (RETURN if same as keystore password):
    Re-enter new password:


Генерируем Certificate Signing Request (CSR)

    keytool -certreq -file path/to/qwertokey.crt -alias qwertokey -keystore path/to/qwerto.keystore

Содержимое полученного файла отправляем в центр сертификации. От центра сертификации получаем сертификат. Сохраняем его в файле (например, qwertokey.cer) и импортируем в хранилище

    keytool -import -trustcacerts -keystore path/to/qwert.keystore -alias qwertokey -file path/to/qwertokey.cer

Подписываем jar-архив

    jarsigner -keystore path/to/qwerto.keystore calculator.jar qwertokey

Файл qwertokey.cer отправляем всем, кто хочет проверить архив. Проверяется он так

    jarsigner -verify -verbose -certs -keystore path/to/qwerto.keystore calculator.jar

 

Использование библиотеки


Есть программа HelloWorld, которая использует библиотечный класс Calculator. Чтобы скомпилировать и запустить программу, нужно присоединить библиотеку.
Компилируем

    cd HelloWorld
    javac -sourcepath src -d bin -classpath path/to/calculator.jar src/com/qwertovsky/helloworld/HelloWorld.java

Запускаем

    java -classpath bin:path/to/calculator.jar com.qwertovsky.helloworld.HelloWorld

 

Собираем программу

Это можно сделать по-разному.

Первый способ

 

    cd HelloWorld
    echo main-class: com.qwertovsky.helloworld.HelloWorld>manifest.mf
    echo class-path: lib/calculator.jar >>manifest.mf
    mkdir lib
    cp path/to/calculator.jar lib/calculator.jar
    jar -cmf manifest.mf helloworld.jar  -C bin .

Здесь есть тонкости.
В строке

    main-class: com.qwertovsky.helloworld.HelloWorld

не должно быть пробелов в конце.
Вторая тонкость описана в [3]: в этой же строке должен стоять перенос на следующую строку. Это если манифест помещается в архив сторонним архиватором.
Программа jar не включит в манифест последнюю строку из манифеста, если в конце не стоит перенос строки.
Ещё момент: в манифесте не должно быть пустых строк между строками. Будет выдана ошибка «java.io.IOException: invalid manifest format».

При использовании команды echo надо следить только за пробелом в конце строки с main-class.

Второй способ

 

    cd HelloWorld
    echo class-path: lib/calculator.jar >manifest.mf
    mkdir lib
    cp path/to/calculator.jar lib/calculator.jar
    jar -cmef manifest.mf com.qwertovsky.helloworld.HelloWorld  helloworld.jar  -C bin .

В данном способе избегаем ошибки с пробелом в main-class.

Третий способ

 

    cd HelloWorld
    mkdir lib
    cd lib
    jar -xvf path/to/calculator.jar com/
      created: com/
      created: com/qwertovsky/
      created: com/qwertovsky/calculator/
     inflated: com/qwertovsky/calculator/Calculator.class
      created: com/qwertovsky/calculator/operation/
     inflated: com/qwertovsky/calculator/operation/Adder.class
    cd ..
    cp  -r bin/* lib/
    jar -cef com.qwertovsky.helloworld.HelloWorld  helloworld.jar  -C lib .
    rm -r lib

Включили код нужной библиотеки в исполняемый файл.

Запуск исполняемого jar-файла

Файл calculator.jar исполняемым не является. А вот helloworld.jar можно запустить.
Если архив был создан первыми двумя способами, то рядом с ним в одном каталоге должна находится папка lib с файлом calculator.jar. Такие ограничения из-за того, что в манифесте в class-path указан путь относительно исполняемого файла.

    cd Calculator
    ls ../HelloWorld/lib
    calculator.jar
    java -jar ../HelloWorld/helloworld.jar

При использовании третьего способа нужные библиотеки включаются в исполняемый файл. Держать рядом нужные библиотеки не требуется. Запускается аналогично.

    java -jar ../HelloWorld/helloworld.jar

 

Как быть с приложениями JavaEE

Аналогично. Только библиотеки для компиляции нужно брать у сервера приложений, который используется. Если я использую JBoss, то для компиляции сервлета мне нужно будет выполнить примерно следующее

     javac -classpath path/to/jboss/common/lib/jboss-servlet*.jar  -d ./classes src/com/qwertovsky/app/servlets/MenuSt.java


Структура архива JavaEE-приложения должна соответствовать определенному формату. Например

    my.ear
    `---META-INF
    |   `---manifest.mf
    `---lib
    |   `---mylib.jar
    `---my.war
    |   `---META-INF
    |   |   `---manifest.mf
    |   `---WEB-INF
    |   |   `---lib
    |   |   |   `---myweblib.jar
    |   |   `---classes
    |   |   |   `---com
    |   |   |       `---...
    |   |   `---web.xml
    |   `---index.html
    |   `---<остальноевеб-содержимое (страницы, изображения)>
    `---myejb.jar

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

Надеюсь, данная статья станет для кого-нибудь шпаргалкой для работы с Java в командной строке. Данные навыки помогут понять содержание и смысл Ant-скриптов и ответить на собеседовании на более каверзные вопросы, чем «Какая IDE Вам больше нравится?».