ПредишенСледващото

Някой каза, че историята се повтаря, първо като трагедия, втори път като фарс. Аз наскоро преживял това от първа ръка, когато трябваше да се инсталира Java-базирано приложение, изпълнен от клиента. Правил съм това много пъти и тя винаги е изпълнено с трудности. Грешки могат да съществуват навсякъде: в събранието на всички молба JAR-файла с, писане стартовите скриптове за DOS и Unix (и Cygwin), проверка на правилността на монтажа на всички на околната среда променливи в компютъра на потребителя. Ако всичко върви гладко, приложението се стартира и се изпълнява правилно. Ако нещо се обърка, и то обикновено се случва - резултатът изисква много часове на работа директно на клиента.

След последните разговори с вглъбен клиент чрез на ClassNotFound изключения, реших, че съм имал достатъчно. Отивам да се намери начин да се опаковат молбата ми в един JAR-файлове и давам на клиентите си един прост механизъм (подобно на Java -jar), за да го стартирате.

Резултатът е една JAR - една много проста софтуерно решение за опаковки, която използва по поръчка Java клас товарач за динамично зареждане на всички класове от един архив, при запазване на структурата на помощни JAR-файлове. В тази статия ще опиша процеса на разработване на една JAR, и след това да ви кажа как да го използва за разпространение на приложението си като един файл.

Преглед на One-JAR

Преди описанието детайлите на една JAR, нека първо обсъдим целите съм си постави. Реших, че архивът на One-JAR трябва:

  • Бъдете изпълним използването на механизма на Java -jar.
  • Да може да съдържа всички необходими файловете на приложението, което е, класове, така и ресурси в първоначалния им вид (не разопакован).
  • Има един прост вътрешна структура, която може да бъде сглобена, използвайки само една програма буркан.
  • Бъдете невидими за оригиналното приложение, а именно, оригиналното приложение, трябва да бъдат опаковани в една JAR архив без промяна.

Проблеми и решения

Най-големият проблем, който срещнах в процеса на разработване на една JAR, е как да се зареди JAR-файлове, които се съдържат в друг JAR-файл. Class Loader Java sun.misc.Launcher $ AppClassLoader. който работи в началото на Java -jar. може да направи само две неща:

  • Заредете класове и ресурси са в основата на JAR-файла.
  • Заредете класове и ресурси, открити в основите на код (програмния код), посочени от атрибут META-INF / MANIFEST.MF Клас-Пътя.

Освен това, той съзнателно игнорира настройките променлива среда за CLASSPATH или аргумент -СР команден ред. сте задали. Той също не знае как да се зареди класове и ресурси от JAR-файл съдържа в друг JAR-файл.

Ясно е, че аз трябваше да се предвиди всичко това, за да постигнете целите си в One-буркан.

Решение 1: Разширяване на подкрепата JAR-файлове

Моят първи опит за създаване на един изпълним JAR-файл ще направи очевидните и помощни JAR-файловете вътре в доставката на JAR-файла, който ще наричаме main.jar. Ако има клас, наречен com.main.Main приложения и на предположението, че това зависи от два класа com.aA (вътре a.jar) и com.bB (вътре b.jar), One-JAR файл ще изглежда така :

Информация този клас A.class първоначално в a.jar, както и първоначалното местоположение на B.class губи. И въпреки че изглежда дреболия, може да доведе до сериозни проблеми, че скоро ще покаже.

Една JAR и FJEP

Наскоро освободен една програма, наречена FJEP (FatJar Eclipse Plug-in) подкрепя изграждането на плоски JAR-файлове директно в Eclipse. Една JAR е интегрирана с FatJar да подкрепи прилагането на JAR-файлове, без поставянето им. Повече информация можете да намерите в раздел "Ресурси".

Разопаковане спомагателни JAR-файл във файловата система, за да създадете плоска структура може да отнеме доста време. Тя също така изисква оформление с програми като Мравката, за да разопаковате и повторно архив помощни класове.

Отделно от това малко смущение Аз бързо срещат две сериозни проблеми, когато са поставени в рамките на архивните спомагателни JAR-файлове:

  • Ако a.jar и b.jar съдържа ресурс с една и съща пътека (да речем, log4j.properties), кой ще изберете?
  • Какво бихте направили, ако лиценз за b.jar изрично изисква от вас да го разпространявате в модифицирана форма? Вие не можете да го разпространява по такъв начин, без да се нарушават условията на лицензията.

Усетих, че тези ограничения изискват различен подход.

Решение 2: МАНИФЕСТ Клас-Path

Реших да изследват механизма в Java -jar. който зарежда класовете, изброени в специален файл в архива на име META-INF / MANIFEST.MF. Клас-Path Посочвайки собственост. Надявах се да се получи възможност за добавяне на други файлове в оригиналния клас товарач. Ето как това може да изглежда като файл с едно JAR:

URLClassloader е базов клас sun.misc.Launcher $ AppClassLoader. поддържа доста загадъчен URL синтаксис, който ви дава достъп до ресурси вътре в JAR-файл. Синтаксисът изглежда така: буркан: файла: /fullpath/main.jar /a.resource !.

Теоретично, за запис във файла JAR, трябва да използвате нещо като буркан: файла: !! /fullpath/main.jar /lib/a.jar /a.resource. но за съжаление, това не работи. манипулатор протокол JAR-файл счита само последната сепаратор характер! "/" Както показалеца на JAR-файл.

Но този синтаксис съдържа ключа към окончателното ми решение "One-JAR".

Върши ли работа? Както и да е, поне така изглеждаше до тогава, докато се преместих main.jar файл на друго място и се опита да го стартирате. За да се сглоби main.jar съм създал поддиректория с име либералните и я поставете a.jar и b.jar файлове. За съжаление, класовете за кандидатстване товарач просто избират помощни JAR-файлове от файловата система. Той не се зарежда класове от JAR-вградена файлове.

JarClassLoader появата

На този етап, аз бях разочарован. Как бих могъл да подадат заявление, за да качите своите класове от ИЪ директория вътре своя собствена JAR-файл? Реших, че трябва да създадете потребителски клас товарач да вдигне такива натоварвания. Писане клас товарач - това не е задача, трябва да бъдат предприети с лека ръка. Въпреки че те не са толкова сложни всъщност клас товарачи имат такова огромно въздействие върху приложения управлява че става трудно за диагностициране и интерпретира грешки, когато те се появят. Въпреки подробно разглеждане на процеса на товарене на класовете е извън обхвата на тази статия (виж. "Ресурси"), ще се обсъдят основните понятия, за да бъдете сигурни, че ще получите максимално количество информация, от дискусията по-долу.

Заредете клас

Когато JVM се натъква на един обект, чийто клас е неизвестен, той предизвиква клас товарач. Работа клас товарач е да се намери байткод за класа (на базата на името му), а след това тези байтове в JVM, което ги свързва с останалата част от системата и предоставя нов клас работещ код. Ключовата класа в JDK е java.lang.Classloader и неговия метод loadClass. чиято структура е показана по-долу:

редна точка клас е ClassLoader loadClass () метод. Имайте предвид, че ClassLoader е абстрактен клас, но не декларират всички абстрактни методи. Това предполага, че методът на loadClass () е основният разглеждане. В действителност, той не е: да се върне към доброто старо време на JDK 1.1 classloaders, loadClass () е единственото място, където можете ефективно разширяване на ClassLoader, но тъй като JDK 1.2 е най-добре оставен сам и не пречи на изпълнението на нещо, за което той е проектиран, а именно:

  • Проверка дали класа вече е зареден.
  • Проверете дали товарач родител клас да се зареди един клас може.
  • Обадете findClass (String име), за да производна ClassLoader зареди класа.

изпълнение ClassLoader.findClass () трябва да се генерира нов изключение ClassNotFoundException. Както първия метод, който трябва да се съсредоточи върху при прилагането на специализиран клас товарач.

Когато JAR-файл не е JAR-файл?

Следващата ми стъпка беше да превъртите през всички записи JAR-файлове молбата ми и да ги заредите в паметта, както е показано на Обява 1:

Обява 1. повтаряне да намерите вградени JAR-файлове

Имайте предвид, че LIB_PREFIX инсталиран в ИЪ / бар. и MAIN_PREFIX - в основния / ред. Бих искал да цикъл да се зареди в паметта за използване байткод на класове, като се започне с ИЪ / или основен /. и игнорира всички други елементи на JAR-файла.

основната директория

Говорих за ролята на ИЪ поддиректорията /, но това, което този каталог основна /? Накратко: режим на делегация за classloaders изисква поставянето на главния клас com.main.Main в собствената си JAR-файл, така че той може да се намери библиотека класа (на която то зависи). Новият JAR-файла изглежда така:

В Обява 1 по-горе loadByteCode () метод получава поток от името на JAR-файл елемент, елементът зарежда байта в паметта и да ги прехвърля на две имена, в зависимост от това дали елементът е клас или ресурс. Това се илюстрира най-добре с един пример. Да предположим, че a.jar съдържа A.class клас и ресурс A.resource. В клас товарач One-JAR създава следната структура Карта на име JarClassLoader.byteCode. има един ключ-стойност чифт за класове и два ключа за ресурса:

Вътрешната структура на Фигура 1. Една JAR

Ако се вгледате внимателно в Фигура 1, виждаме, че записите клас са застопорени на базата на името на класа, както и елементите на ресурси са определени в зависимост от двойката имена: глобални имена и имена на местности. Този механизъм се използва за разрешаване на конфликта в имената на ресурси: ако двама библиотека JAR-файл определи ресурс с едно и също общо име ще бъде използвано от местните имена, основаващи се на комин рамките на обаждащия се. виж раздел "Ресурси" за повече информация.

Търсене класове

Спомнете си, че съм прекъснал в моя клас натоварване в метода на преглед findClass (). findClass () метод получава името на класа като тип String и трябва да се локализира и идентифицира байткод, че е името. От loadByteCode любезно изгражда Карта между името на класа и неговата байткод, за да се реализира това сега е много проста: вие трябва да намерите най-bytecodes въз основа на името на класа, и се обадете defineClass (). както е показано на Обявата 2:

Обявата 2. Фрагмент findClass ()

Изтегляне на ресурси

По време на разработването на една JAR findClass беше първото нещо, аз имам работа като доказателство за концепцията. Но когато започнах да се разпределят по-сложни приложения, разбрах, че аз трябва да се справят с натоварването на ресурсите, както и класове. Това е, когато работата започна да се провали. Определена в ClassLoader да замени най-подходящия метод за справка ресурси, аз избрах тази, с която бях най-познато. Виж Обявата 3:

Обявата 3. getResourceAsStream () метод

Бел на това място е трябвало да бъде звучене: аз просто не можех да разбера защо търсенето на ресурси, използвани URL адрес. Така че, аз игнорира това изпълнение и добавя моя собствена, показан на Обява 4:

Обява 4. Изпълнението на getResourceAsStream на метод () в едно JAR

Последното препятствие

Моят нов изпълнение на getResourceAsStream (). Струваше да свърши, докато не се опита да One-JAR приложение, което зарежда ресурс с помощта на URL адреса = object.getClass () getClassLoader () getResource () ..; тук заявлението се провали. Защо? Тъй като изпълнението на възстановим подразбиране ClassLoader URL а. Това е равно на нула, което избухна кода на обаждащия се.

От този момент нататък, всичко, объркан. Аз трябваше да разбера кои URL трябва да се използва за означаване на ресурса в рамките на JAR-файла в директорията / ИЪ. Трябва ли да бъде, например, буркан: файла: main.jar ИЪ / a.jar com.a.A.resource !!?

Аз се опитах всяка комбинация, която можех да си представя - никой от които не е работил. В буркан: синтаксис просто не поддържа вложени JAR-файлове, които ме поставят пред очевидно безнадеждна ситуация в моя подход към една буркан. Въпреки, че повечето приложения очевидно не използвайте ClassLoader.getResource. някои определено направи, и аз не бях щастлив изключение, като каза: "Ако вашето приложение използва ClassLoader.getResource () не можете да използвате една JAR".

И, накрая, решението.

Опитвате се да разберете синтаксиса на буркан. Спънах се механизъм, който използва Java Runtime Environment за да се покаже, занимаващо се със URL-префикс. Това е разковничето, че трябва да се реши проблема в findResource. Бих просто измисли моя собствен протокол префикс нарича onejar. тогава мога да съставят карта на нов префикс за манипулатор протокол, който ще се върне потока байт за ресурса, както е показано на Обява 5. Имайте предвид, че Обява 5 представлява код в два файла, JarClassLoader и нов файл с име COM / simontuffs / onejar /Handler.java.

Обявата 5. findResource и onejar протокол:

Bootstrapping JarClassLoader

В този момент, най-вероятно просто трябва един оставащ въпрос: как да вмъкнете JarClassLoader в стартовата последователност, така че да може да започне класове на натоварване от файла с едно JAR-напред? Подробно обяснение е извън обхвата на този член; но, са, както следва. Вместо да се използват главния клас com.main.Main като META-INF / MANIFEST.MF / Main-Class съм създал нов клас обувка com.simontuffs.onejar.Boot. която е посочена като основен атрибут-Class. Новият клас прави следното:

  • Създава нов JarClassLoader.
  • Той използва нов зареждане на ОС за com.main.Main на главния / main.jar (на базата на мета-INF на елемент / MANIFEST.MF Main-Class в main.jar).
  • Предизвиква com.main.Main.main (String []) (или друго име Main-Class. Изобразява в main.jar / MANIFEST.MF файл) се зареждат клас и използването на картографирането на главната () повикване. Посочен на командния ред аргументи One-JAR се предава на основен метод за кандидатстване без промяна.

В заключение

Ако сте далеч от всичко замаян, не се притеснявайте: С помощта на едно JAR е много по-лесно, отколкото се опитвам да разбера как работи. С появата на плъгин FatJar Eclipse Plug-in (вж. FJEP виж "Ресурси"), потребителите могат да създават Eclipse One-JAR-заявление чрез проверка на съветника. Зависимите библиотеки се поставят в директорията / ИЪ, основната програма и класовете са поставени в основната / main.jar, на META-INF / MANIFEST.MF файл се записва автоматично. Ако използвате JarPlug (отново, вижте секцията "Ресурси"), можете да погледнете вътре в JAR-файл и да го стартирате IDE.

Като цяло, една JAR е проста, но мощно решение на проблема за опаковане заявления за разпределение. Въпреки това, той не е подходящ за всички възможни сценарии. Например, ако вашето приложение използва стар стил клас товарач JDK 1.1, което не делегира на своя родител, този клас товарач се провали, когато търсят класове във вложено JAR-файл. Можете да се преодолее това препятствие, чрез създаване и внедряване на "обвивка" (опаковане) клас товарач за промяна на непокорния товарач, въпреки че това ще доведе до използване на техники байткод манипулация с помощта на такива инструменти като Javassist или Byte Код Инженеринг библиотека (BCEL).

Може да срещнете проблеми при използването на някои специализирани видове ClassLoader, използвани от вградените приложения и уеб-базирани сървъри. По-специално, може да имате проблем с клас товарачи незабавно не се търгуват на товарача майка, както и тези, които търсят за codebases в файловата система. В този случай, трябва да помогне, включени в механизма за една JAR чрез който може да се разшири членове JAR-файлове на файловата система. Този механизъм се контролира от атрибут на една JAR-Разширяване на файл META-INF / MANIFEST.MF. Алтернативно бихте могли да опитате да използвате манипулация байткод за промяна на класа товарач в движение, по време на работа, без да се нарушава целостта на спомагателните JAR-файлове. Ако отидете по този начин, за всеки отделен случай, може да се наложи специален "обвивка" клас товарач.

В секцията "Ресурси" за линкове за изтегляне на плъгините FatJar Eclipse Plugin и JarPlug, както и за допълнителна информация за One-буркан.

Изтегляне ресурси

Свързани теми

Подкрепете проекта - споделете линка, благодаря!