Jedną z dużych zalet języka Java jest fakt, że został w nim zdefiniowany bardzo przejrzysty mechanizm klasyfikacji zewnętrznych interfejsów programistycznych i zarządzania nimi. Porównajmy te rozwiązania z większością innych języków, w których symbole można znajdować w samej bibliotece języka lub wielu innych bibliotekach, przy czym nie obowiązują tu żadne reguły nazewnictwa[79]. Interfejs programistyczny składa się z jednego lub większej liczby pakietów, pakiety z kolei składają się z klas, a klasy z metod i pól. Pakiety może tworzyć każdy, przy czym obowiązuje tu jedno ograniczenie — nikt nie jest w stanie tworzyć pakietów, których nazwy rozpoczynają się od czterech liter „java”. Pakiety o nazwach java
oraz javax
są zarezerwowane dla twórców języka Java zatrudnionych w firmie Oracle, a prace nad nimi są prowadzone w oparciu o proces społeczności języka Java — JCP (ang. Java Community Process). W czasie gdy język Java został udostępniony, w skład jego interfejsu programistycznego wchodziło około tuzina pakietów, z których większość wciąż jest stosowana, choć ich wielkość wzrosła niemal czterokrotnie. Niektóre z tych pakietów przedstawiłem w Tabela 21-1.
Od tego czasu do języka zostało dodanych wiele nowych pakietów, jednak ich początkowa struktura dosyć dobrze wytrzymała próbę czasu. W niniejszym rozdziale pokażę, w jaki sposób można tworzyć własne pakiety oraz ich dokumentację, a następnie przedstawię kilka zagadnień związanych z różnymi sposobami udostępniania pakietów na różnych platformach systemowych.
Tabela 21-1. Podstawowa struktura pakietów języka Java
Przeznaczenie | |
---|---|
| Aplety wykonywane przez przeglądarki WWW |
| Graficzny interfejs użytkownika |
| Odczyt i zapis danych |
| Wbudowane klasy języka ( |
| Wsparcie dla obsługi adnotacji |
| Biblioteka operacji matematycznych |
| Obsługa sieci (gniazda) |
| „Nowe” (choć już nie takie nowe) mechanizmy obsługi operacji wejścia-wyjścia, bazujące na kanałach |
| Mechanizmy komunikacji z bazami danych |
| Obsługa i formatowanie dat, liczb oraz komunikatów |
| Java 8: nowoczesne API do obsługi dat i czasu (JSR 311) |
| Klasy pomocnicze (kolekcje, daty) |
| Obsługa wyrażeń regularnych |
| JNDI |
| Wsparcie dla operacji drukowania |
| Java 6: wsparcie dla mechanizmów skryptowych |
| Nowoczesny graficzny interfejs użytkownika |
Chcemy mieć możliwość importowania własnych klas bądź zorganizowania ich, dlatego też chcemy stworzyć nasz własny pakiet.
Należy umieścić instrukcję package
na początku każdego z plików zawierających kody źródłowe naszych klas, a następnie skompilować je, umieszczając w wywołaniu kompilatora opcję -d
; można także skorzystać z odpowiedniego programu narzędziowego lub zintegrowanego środowiska programistycznego.
Instrukcja package
musi być pierwszą instrukcją umieszczoną w pliku źródłowym, nawet instrukcje importu należy umieszczać za nią. Sama instrukcja powinna określać pełną nazwę pakietu. Według ogólnie przyjętej konwencji nazwy pakietów odpowiadają nazwom domenowym ich twórców zapisanym w odwrotnej kolejności. Na przykład mój adres domenowy to darwinsys.com i dlatego nazwy większości moich pakietów rozpoczynają się od łańcucha znaków darwinsys.com
, po którym umieszczana jest nazwa projektu. Klasy pomocnicze wykorzystywane w niniejszej książce zostały umieszczone w pakiecie com.darwinsys
, opisanym w „1.5. Pobieranie przykładów dołączonych do tej książki i korzystanie z nich”, a każdy plik źródłowy wchodzący w skład tego pakietu rozpoczyna się od instrukcji, takiej jak ta pokazana poniżej:
package com.darwinsys.util;
Demonstracyjne klasy dołączone do tej książki i umieszczone w katalogu /javacooksrc/main nie zostały rozmieszczone zgodnie z tą zasadą. Umieściłem je w pakietach, których nazwy odpowiadają tematyce rozdziałów lub pakietom java.*
, z którymi są powiązane. Na przykład w pakiecie lang
umieściłem kody prezentujące podstawowe zagadnienia związane z językiem Java, w pakiecie struct
— przykłady do rozdziału poświęconego strukturom danych (Rozdział 7.), w pakiecie threads
— przykłady do rozdziału poświęconego wątkom (Rozdział 22.) i tak dalej. Mam przy tym nadzieję, że jeśli Czytelnik będzie chciał wykorzystać te kody we własnej aplikacji, umieści je w „prawdziwych” pakietach!
Należy pamiętać, że po umieszczeniu instrukcji package
w plikach źródłowych zarówno kompilator, jak i środowisko wykonawcze Javy będą oczekiwać, że skompilowane pliki klasowe (.class) zostaną umieszczone w odpowiednim miejscu (czyli w strukturze katalogów, których nazwa odpowiada pełnej nazwie pakietu umieszczonej w jednym z katalogów podanych w zmiennej środowiskowej CLASSPATH
). Na przykład plik klasowy klasy com.darwinsys.util.FileIO
musi nosić nazwę FileIO.class, ale nie może być umieszczony bezpośrednio w jednym z katalogów wymienionych w zmiennej środowiskowej CLASSPATH
, lecz w pliku com/darwinsys/util/FileIO.class, a cała ta struktura musi się znajdować w jednym z katalogów podanych w zmiennej CLASSPATH
lub w jednym z archiwów przechowywanych w tych katalogach. Podczas kompilacji klas należących do pakietu zazwyczaj (choć właściwie jest to niemal niezbędne) umieszcza się w wywołaniu kompilatora opcję -d
. Za tą opcją należy podać nazwę katalogu, w którym trzeba utworzyć wynikowe drzewo katalogów (przy czym bieżący katalog jest oznaczany przy użyciu kropki (.
)). Aby na przykład skompilować wszystkie pliki .java umieszczone w bieżącym katalogu i utworzyć przy tym odpowiednią strukturę katalogów z plikami klasowymi (kontynuując poprzedni przykład, byłaby to struktura ./com/darwinsys/util), należałoby użyć następującego polecenia:
java -d . *.java
które powoduje utworzenie ścieżki (na przykład: com/darwinsys/util/) w bieżącym katalogu i umieszcza wszystkie skompilowane pliki klasowe w utworzonym katalogu util. Powyższe rozwiązanie znacznie ułatwia kompilowanie programu w przyszłości, jak również upraszcza tworzenie archiwów (opisane w „21.4. Stosowanie programu archiwizującego jar”).
Oczywiście, w przypadku korzystania z narzędzia do budowania, takiego jak Ant (patrz „1.6. Automatyzacja kompilacji przy użyciu programu Ant”) lub Maven (patrz „1.7. Automatyzacja zależności, kompilacji, testowania i wdrażania przy użyciu programu Apache Maven”), niezbędne ustawienia zostaną wprowadzone w pliku konfiguracyjnym (Ant) lub też zostaną wykonane prawidłowo bez naszej ingerencji (w razie korzystania z programu Maven); zatem nie będziemy musieli pamiętać o tym, by ciągle to robić.
Należy zwrócić uwagę, że we wszystkich nowoczesnych środowiskach języka Java klas, które nie należą do żadnego pakietu (czyli są umieszczone w „pakiecie anonimowym”), nie można podawać w instrukcjach importu, choć inne klasy należące do tego samego pakietu mogą się do nich odwoływać.
Znamy pojęcie określane jako „wielokrotne stosowanie kodu” i chcielibyśmy je promować, ułatwiając innym programistom wykorzystanie naszego kodu.
Należy wykorzystać system dokumentacji Javadoc. Wstawiaj komentarze podczas pisania kodu.
Javadoc jest jednym z wielkich wynalazków powstałych w pierwszych latach stosowania języka Java. Podobnie jak wiele innych bardzo dobrych rozwiązań, nie został on w całości wymyślony przez twórców Javy — już wcześniejsze projekty, takie jak Literate Programming Donalda Knutha, łączyły kod źródłowy programu oraz jego dokumentację w jednym pliku źródłowym. Jednak twórcy Javy w doskonały sposób wykorzystali tę ideę i zrobili to we właściwym czasie. Javadoc jest dla klas języka Java tym samym, czym dokumentacja man dla systemów Unix lub Windows Help dla aplikacji przeznaczonych dla systemów Windows: to standardowy format, którym każdy potrafi się posługiwać; każdy też oczekuje, że zawsze będzie dostępny i stosowany przez innych. Należy go poznać, zastosować, stworzyć dokumentację, po czym działać długo i owocnie (cóż, być może nie zawsze). A cała dokumentacja języka Java i jego interfejsu programistycznego, która jest używana podczas pisania programów? Czy firma Sun wynajęła setki pisarzy, którzy ją stworzyli? Nie, w języku Java takie rzeczy robi się w inny sposób. Twórcy języka Java już podczas pisania kodu źródłowego umieszczają w nim komentarze dokumentujące, a później — w momencie udostępniania nowej wersji języka — przetwarzają tysiące publicznych klas przy użyciu programu Javadoc, generując dokumentację, która jest następnie udostępniana wraz z JDK. Wszyscy programiści tworzący klasy przeznaczone do wykorzystania przez innych mogą, powinni, a w rzeczywistości muszą postępować tak samo.
Jedyną rzeczą, jaką należy zrobić, aby móc skorzystać z systemu dokumentacyjnego Javy, jest umieszczanie specjalnych komentarzy dokumentujących w plikach źródłowych. Komentarze te zaczynają się od znaku ukośnika i dwóch gwiazdek (/**
), kończą gwiazdką i znakiem ukośnika, a należy je umieszczać bezpośrednio przed definicją dokumentowanej klasy, metody lub pola. Komentarze dokumentujące znajdujące się w jakichkolwiek innych miejscach są ignorowane.
Istnieje grupa słów kluczowych poprzedzanych znakiem @
, które w określonych sytuacjach można umieszczać w komentarzach dokumentujących. Niektóre z nich są dodatkowo zapisywane w nawiasach klamrowych. Słowa te, dostępne w wersji Java 8, przedstawiłem w Tabela 21-2.
Tabela 21-2. Słowa kluczowe stosowane w systemie dokumentującym Javadoc
Zastosowanie | |
---|---|
| Personalia autora. |
| Wyświetla tekst tą samą czcionką, którą prezentowany jest kod, bez dodatkowych interpretacji. |
| Powoduje wygenerowanie ostrzeżenia o odrzuconym kodzie. |
| Odwołuje się do głównego katalogu struktury wygenerowanej dokumentacji. |
| Inna nazwa |
| Umożliwia odziedziczenie dokumentacji po najbliższej klasie bazowej lub interfejsie bazowym. |
| Generuje wewnątrzwierszowy odnośnik do innej klasy lub składowej. |
| Działa podobnie jak |
| Wyświetla tekst bez żadnej dodatkowej interpretacji. |
| Podaje nazwę oraz znaczenie argumentu (można stosować wyłącznie w dokumentacji metod). |
| Wartość wynikowa. |
| Generuje odnośnik odwołujący się do dokumentacji innej klasy lub składowej. |
| Opisuje pole, którego wartość może być serializowana. |
| Opisuje kolejność i typy danych w formie serializowanej. |
| Opisuje pole, którego zawartość jest serializowana. |
| Wersja JDK, w której metoda lub pole zostało wprowadzone (wykorzystywane głównie przez twórców języka Java). |
| Klasa zgłaszanego wyjątku oraz warunki powodujące jego zgłoszenie. |
| Wyświetla wartość tego i innych pól stałych. |
| Identyfikator wersji kodu. |
Przykład 21-1 przedstawia niezbyt realistyczny kod źródłowy programu, w którym zostały wykorzystane niemal wszystkie słowa kluczowe z Tabela 21-2. Wyniki przetworzenia tego kodu przez program Javadoc zostały przedstawione na Rysunek 21-1.
Przykład 21-1. /javadoc/JavadocDemo.java
public class JavadocDemo extends JPanel { /** * Tworzymy graficzny interfejs użytkownika. * @throws java.lang.IllegalArgumentException w razie uruchomienia * w niedzielę. */ public void JavadocDemo() { // Tworzymy i dodajemy przycisk, który jeszcze niczego nie robi. Button b = new Button("Witam"); add(b); // Powiązanie przycisku z komponentem. // Bardzo kapryśny przykład pokazujący, czego absolutnie nie // należy robić. if (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) { throw new IllegalArgumentException("W niedzielę nie działam!"); } } /** paint() to metoda klasy Component biblioteki AWT, jest ona * wywoływana, kiedy należy przerysować zawartość komponentu. Nasz * komponent wyświetla w oknie jedynie kolorowe prostokąty. * * @param g Obiekt java.awt.Graphics, którego używamy do wywoływania * wszystkich metod graficznych. */ public void paint(Graphics g) { int w = getSize().width, h = getSize().height; g.setColor(Color.YELLOW); g.fillRect(0, 0, w/2, h); g.setColor(Color.GREEN); g.fillRect(w/2, 0, w, h); g.setColor(Color.BLACK); g.drawString("Witamy w Javie", 50, 50); } }
Program Javadoc działa dobrze nawet w przypadku pojedynczych klas, jednak jego prawdziwe możliwości uwidaczniają się w przypadku całych pakietów lub nawet grup pakietów. Można na przykład podać plik zawierający informacje o pakiecie, który zostanie dołączony do wygenerowanej dokumentacji. Program Javadoc generuje dokumentację zawierającą precyzyjne połączenia wewnętrzne oraz zewnętrzne, takie same jak te, które można znaleźć w standardowej dokumentacji języka Java. Program posiada kilka opcji podawanych w wierszu wywołania. Osobiście zazwyczaj używam opcji -author
oraz -version
, które powodują umieszczenie w generowanej dokumentacji informacji o autorze i wersji kodu; jak również opcji -link
, określającej położenie standardowej dokumentacji Javy, z którą będą tworzone odpowiednie połączenia.
Rysunek 21-1 przedstawia dokumentację wygenerowaną na podstawie klasy JavadocDemo
w wyniku wykonania następującego polecenia:
$ javadoc -author -version JavadocDemo.java
Należy pamiętać, że jeden z (wielu) wygenerowanych plików będzie mieć tę samą nazwę co klasa oraz rozszerzenie .html. A zatem jeśli stworzymy aplet oraz dokument HTML o tej samej nazwie, służący do wyświetlania apletu, to dokument ten zostanie zastąpiony plikiem wygenerowanym przez program Javadoc, i to bez żadnego ostrzeżenia. Z tego względu twórcom apletów sugerowałbym korzystanie z opcji -d
katalog
, aby poinformować program Javadoc, gdzie ma umieszczać generowane pliki, i aby nie zapisywał ich w katalogu z kodami źródłowymi; opcja ta działa tak samo jak analogiczna opcja programu javac
. Ewentualnie można także zmienić rozszerzenie strony wyświetlającej aplet na .htm.
Program Javadoc ma także wiele innych opcji, które można stosować w wierszu poleceń. Jeśli generowana dokumentacja jest przeznaczona do naszego własnego użytku, to można użyć opcji -link
, by wskazać, gdzie została zainstalowana dokumentacja JDK, dzięki czemu możliwe będzie wygenerowanie odnośników do standardowych klas Javy (takich jak String
, Object
itd.). Jeśli jednak dokumentacja ma być rozpowszechniana, to opcję -link
można pominąć lub dodać do niej adres URL odpowiedniej strony dokumentacji Java API udostępnionej w witrynie firmy Oracle. Informacje na temat wszystkich dostępnych opcji tego programu można znaleźć w jego internetowej dokumentacji.
W większości przypadków wyniki generowane przez program Javadoc są całkowicie zadowalające. Można jednak stworzyć własną klasę Doclet
, dzięki której program Javadoc pozwoli na sprawdzanie poprawności dokumentacji, konwersję dokumentacji Javy na format MIF lub RTF lub na wykonywanie wielu innych operacji. Podane przykłady są wykorzystywane w praktyce, więcej informacji na temat zastosowań tego typu oraz ich przykłady można znaleźć w standardowej dokumentacji narzędzia javadoc
bądź w witrynie firmy Oracle poświęconej Javie. Można także zajrzeć na witrynę http://www.doclet.com i przejrzeć zgromadzoną na niej nieco starą, lecz wciąż użyteczną bibliotekę docletów oraz wszelkich innych narzędzi związanych z generowaniem dokumentacji w Javie.
Dokumentacja generowana przez program Javadoc jest przeznaczona dla programistów używających stworzonego przez nas kodu. Użytkownicy aplikacji z graficznym interfejsem użytkownika zapewne znacznie bardziej będą sobie chwalić standardowe pliki pomocy. Do ich tworzenia i wykorzystania w programie służy interfejs programistyczny JavaHelp, którego nie opisywałem w niniejszej książce. Wyczerpujące informacje na jego temat można znaleźć w książce Creating Effective JavaHelp Kevina Lewisa wydanej przez wydawnictwo O’Reilly (http://shop.oreilly.com/product/9781565927193.do), którą powinien przeczytać każdy programista zajmujący się tworzeniem graficznych interfejsów użytkownika w programach pisanych w Javie. Istnieje jeszcze inna użyteczna specyfikacja, o której nieco zapomniano podczas przejmowania firmy Sun przez Oracle — jest ona obecnie rozwijana przez społeczność java.net, a informacje o niej można znaleźć na stronie http://javahelp.java.net.
Na podstawie kodu źródłowego chcemy wygenerować nie tylko dokumentację, lecz także dodatkowe zasoby. Chcemy oznaczyć kod, tak by umożliwić kompilatorowi przeprowadzenie dodatkowej weryfikacji.
Cieszące się nieustannym powodzeniem otwarte narzędzie XDoclet (http://xdoclet.sourceforge.net/xdoclet/index.html), które początkowo zostało stworzone w celu generowania uciążliwych klas pomocniczych oraz deskryptorów wdrażania używanych przez powszechnie krytykowaną platformę EJB2, sprawiło, że pojawiła się potrzeba wprowadzenia podobnego mechanizmu w standardowej wersji języka Java. W efekcie opracowano adnotacje Javy. Mechanizm adnotacji korzysta ze składni nieco przypominającej interfejsy, w której zarówno deklaracje adnotacji, jak i ich zastosowanie wymagają podania nazwy poprzedzonej znakiem @
. Zgodnie z twierdzeniami projektantów tego mechanizmu zapis ten został wybrany dlatego, że przypominał „znaczniki Javadoc — istniejący już wcześniej tymczasowy pierwowzór adnotacji w języku Java”. Javadoc można określić jako tymczasowy wyłącznie w takim znaczeniu, że jego znaczniki rozpoczynające się od znaku @
nigdy nie zostały w pełni zintegrowane z samym językiem, a większość z nich była ignorowana przez kompilator, choć nie dotyczy to znacznika @depreciated
, który kompilator zawsze rozpoznawał i uwzględniał (patrz „1.9. Komunikaty o odrzuconych metodach”).
Adnotacje można odczytywać w trakcie działania programu przy wykorzystaniu API odzwierciedlania, co pokazałem w „23.9. Stosowanie i definiowanie adnotacji”, w której zaprezentowałem także, w jaki sposób można definiować swoje własne adnotacje. Adnotacje mogą być również odczytywane po kompilacji, jak to jest w przypadku takich narzędzi jak generatory używane przez technologie RMI i EJB (bądź też innych narzędzi, które dopiero zostaną wymyślone, może nawet przez Ciebie, drogi Czytelniku!).
Także kompilator Javy, program javac
, odczytuje adnotacje podczas kompilacji, aby pobrać z nich dodatkowe informacje.
Na przykład często popełnianym błędem jest przeciążenie metody, kiedy tak naprawdę chodziło o jej przesłonięcie; błąd ten można popełnić bardzo łatwo, gdyż wystarczy podać w deklaracji metody nieprawidłowe typy argumentów. W ramach przykładu przeanalizujmy przesłonięcie metody equals
klasy Object
. Jeśli przez pomyłkę zostanie ona zapisana w następujący sposób:
public boolean equals(MyClass obj) { ... }
to w efekcie powstanie jej przeciążona wersja, która najprawdopodobniej nigdy nie zostanie wywołana — wywoływana będzie domyślna wersja metody zdefiniowana w klasie Object
. Aby uniemożliwić popełnianie błędów tego typu, w pakiecie java.lang
zastosowano adnotację Override
. Nie wymaga ona podawania żadnych dodatkowych parametrów, a należy ją umieścić bezpośrednio przed deklaracją metody. Poniżej został przedstawiony stosowny przykład:
/lang/AnnotationOverrideDemo.java
/** * AnnotationOverrideDemo - Prosty przykład zastosowania metadanych do * sprawdzenia, czy metoda z klasy bazowej jest przesłaniana * (a nie przeciążana). Ta klasa udostępnia metodę. */ abstract class Top { public abstract void myMethod(Object o); } /** Prosty przykład zastosowania metadanych do * sprawdzenia, czy metoda z klasy bazowej jest przesłaniana * (a nie przeciążana). W tej klasie metoda klasy bazowej ma * zostać przesłonięta; kod celowo jednak zawiera błąd, aby * pokazać działanie nowoczesnych kompilatorów. */ class Bottom { @Override public void myMethod(String s) { // MOŻNA OCZEKIWAĆ BŁĘDU KOMPILACJI // Tu coś robimy... } }
Próba skompilowania tego programu spowoduje zgłoszenie błędu informującego, że metoda nie przesłania metody z klasy bazowej, choć adnotacja informuje o tym, że powinna. Jest to krytyczny błąd uniemożliwiający skompilowanie kodu:
C:> javac AnnotationOverrideDemo.java AnnotationOverrideDemo.java:21: method does not override a method from its superclass @Override public void myMethod(String s) { // MOŻNA OCZEKIWAĆ BŁĘDU KOMPILACJI ^ 1 error C:>
Chcemy stworzyć własny plik archiwalny Javy (JAR) zawierający klasy naszego pakietu (bądź dowolną inną kolekcję plików).
Program jar
jest standardowym narzędziem służącym do tworzenia archiwów. Archiwa w Javie pełnią tę samą funkcję co biblioteki wykorzystywane w niektórych innych językach programowania. Standardowe klasy Javy są zazwyczaj pobierane z archiwów, o czym można się przekonać, uruchamiając program HelloWorld
z opcją -verbose
:
java -verbose HelloWorld
Tworzenie archiwów jest bardzo prostym zadaniem. W wywołaniu programu jar
można podać kilka opcji. Najpopularniejszą z nich jest c
, nakazująca utworzenie nowego archiwum, t
— tworząca spis treści archiwum, oraz x
— pobierająca zawartość archiwum. Nazwa pliku archiwum jest określana przy użyciu opcji -f
. Po opcjach podawane są nazwy plików i katalogów, jakie należy umieścić w archiwum. Poniżej podałem przykład wywołania programu jar
:
jar cvf /tmp/MyClasses.jar .
Kropka umieszczona na końcu polecenia jest bardzo ważna i oznacza bieżący katalog. Powyższe polecenie tworzy archiwum zawierające wszystkie pliki z bieżącego katalogu oraz jego podkatalogów, po czym zapisuje je w pliku /tmp/MyClasses.jar.
Niektóre z zastosowań plików JAR wymagają, aby był w nich dostępny jeden dodatkowy plik, nazywany manifestem, w którym znajduje się lista zawartości archiwum oraz atrybuty wszystkich umieszczonych w nim plików. Atrybuty te są zapisywane jako pary nazwa: wartość
, podobnie jak nagłówki wiadomości poczty elektronicznej, zawartość plików właściwości (patrz „7.12. Zapisywanie łańcuchów znaków w obiektach Properties i Preferences”) i tak dalej. Niektóre atrybuty są wymagane w konkretnym zastosowaniu plików JAR, inne natomiast są opcjonalne. Na przykład „21.5. Uruchamianie programu zapisanego w pliku JAR” przedstawia sposób wykonywania programu głównego bezpośrednio z archiwum. Aby rozwiązanie takie było możliwe, w manifeście należy umieścić nagłówek Main-Program
. Można także tworzyć i stosować własne, niestandardowe nagłówki, takie jak:
MySillyAttribute: true MySillynessLevel: high(5'11'')
Informacje te zapisuje się w pliku o dowolnej nazwie, powiedzmy mainfest.stub[80], którego nazwa jest podawana w wywołaniu programu jar
za pomocą opcji -m
. Program jar
umieszcza przekazane atrybuty w tworzonym pliku manifestu:
jar -cv -m manifest.stub -f /tmp/com.darwinsys.util.jar .
Zarówno program jar
, jak i inne związane z nim narzędzia dodają do pliku manifestu dodatkowe informacje, na przykład listę wszystkich plików umieszczonych w archiwum.
W przypadku używania takich narzędzi jak Maven (patrz „1.7. Automatyzacja zależności, kompilacji, testowania i wdrażania przy użyciu programu Apache Maven”) plik JAR dla projektu można utworzyć automatycznie — wystarczy użyć polecenia mvn package
.
Chcemy rozprowadzać jeden duży plik JAR zawierający wszystkie klasy tworzące naszą aplikację i uruchamiać ją bezpośrednio z tego pliku.
Należy stworzyć plik JAR, którego manifest będzie zawierać wiersz Main-Class:
, i uruchamiać aplikację, umieszczając w wywołaniu programu java
opcję -jar
.
Polecenie java
posiada opcję -jar
, której użycie nakazuje uruchomienie programu zapisanego w pliku JAR. W przypadku zastosowania tego sposobu uruchamiania programu wszystkie klasy konieczne do jego działania także będą poszukiwane w tym samym pliku JAR. Ale skąd Java będzie wiedzieć, jaki program ma uruchomić? Cóż, musimy go wskazać. W tym celu należy stworzyć plik zawierający jeden prosty wpis przypominający ten przedstawiony poniżej; należy przy tym pamiętać, że w nazwach atrybutów uwzględniana jest wielkość liter, a po dwukropku koniecznie należy umieścić znak odstępu:
Main-class: com.jakasdomena.HelloWorld
Taki plik można zapisać na przykład pod nazwą manifest.stub, zakładając, że program HelloWorld
ma być uruchamiany w danym pakiecie. Poniższe polecenia pozwolą przygotować aplikację i uruchomić ją z pliku JAR:
C:> javac HelloWorld.java C:> jar cvmf manifest.stub hello.jar HelloWorld.class C:> java -jar hello.jar Witaj, świecie C:>
Teraz można umieścić plik JAR w dowolnym miejscu i uruchamiać go w ten sam sposób. Katalogu, w którym został umieszczony plik JAR, nie trzeba dodawać do zmiennej środowiskowej CLASSPATH
, podobnie jak nie trzeba podawać nazwy uruchamianej klasy.
W platformach systemowych wyposażonych w graficzny interfejs użytkownika takie aplikacje można uruchamiać także poprzez dwukrotne kliknięcie pliku JAR. Rozwiązanie to działa przynajmniej w systemach Mac OS X oraz Windows, w których zostało zainstalowane środowisko wykonawcze Javy.
W systemie Mac OS X można skorzystać z programu Jar Bundler (dostępnego w katalogu /usr/share/java/Tools/JarBundler.app). Program ten udostępnia graficzny interfejs użytkownika pozwalający na określanie opcji ustawianych przez mój pakiet MacOSUI
(patrz „14.18. Korzystanie z rozszerzonych możliwości pakietu Swing w systemie Mac OS X”), jak również zmiennej CLASSPATH
i kilku innych atrybutów. Okno tego programu zostało przedstawione na Rysunek 21-2.
W pliku JAR można także umieszczać aplety wraz z dodatkowymi używanymi przez nie plikami. Taki plik JAR można udostępnić na serwerze WWW zamiast pliku klasowego apletu. W takim przypadku w kodzie strony HTML należy umieścić element podobny do tego: <applet code="MyClass" archive="MyAppletJar.jar" ...>
.
Po umieszczeniu pliku JAR na serwerze WWW zamiast pliku klasowego apletu należy się do niego odwołać w znaczniku applet
w kodzie strony WWW. W tym celu w znaczniku tym należy umieścić atrybut w postaci archive="nazwa_pliku_JAR"
.
W pliku JAR można także umieszczać zasoby używane przez aplet, takie jak obrazy GIF. W kodzie apletu zasoby te można pobierać za pomocą metody getResource()
, zamiast pobierać je bezpośrednio z serwera WWW; patrz krok 5. w „21.11. Java Web Start”.
Należy się upewnić, że klasa spełnia wszystkie wymagania stawiane komponentom JavaBeans, a następnie stworzyć plik JAR zawierający plik klasowy, manifest oraz pomocnicze informacje.
W języku Java istnieje kilka rodzajów komponentów określanych mianem komponentów JavaBeans:
Komponenty wizualne wykorzystywane w programach służących do tworzenia interfejsów graficznych aplikacji (to właśnie nimi zajmiemy się w tym rozdziale).
Zwyczajne stare obiekty Javy (ang. Plain Old Java Objects, POJO) bądź też komponenty przeznaczone do wielokrotnego wykorzystania.
Wersja języka Java przeznaczona do zastosowań korporacyjnych udostępnia komponenty EJB (ang. Enterprise JavaBeans), JSP JavaBeans, zarządzane komponenty JSF Bean oraz CDI Beans, dysponujące możliwościami pozwalającymi na tworzenie aplikacji o zasięgu korporacyjnym. Tworzenie i wykorzystanie komponentów tego typu jest znacznie bardziej złożone od tworzenia pozostałych typów komponentów JavaBeans, dlatego też problematyka komponentów EJB nie została przedstawiona w tej książce. Gdyby się jednak okazało, że musisz poznać możliwości funkcjonalne komponentów EJB oraz nauczyć się sposobów ich tworzenia i wykorzystania, to polecam książkę Java EE 7 Essentials napisaną przez Aruna Guptę i wydaną przez wydawnictwo O’Reilly.
Także platforma Spring Framework używa terminu „Bean” do określenia obiektów, którymi jest w stanie zarządzać.
Wszystkie cztery wymienione powyżej rodzaje komponentów łączą jednolite zasady nazewnictwa. Wszystkie operacje na właściwościach publicznych muszą być wykonywane za pośrednictwem specjalnych metod umożliwiających określenie wartości właściwości oraz jej odczytanie. Dla właściwości typu Typ
o nazwie Prop
trzeba utworzyć dwie metody (koniecznie należy zwrócić uwagę na wielkość liter w ich nazwach):
public Typ getProp(); public void setProp(Typ);
Jedyne dopuszczalne odstępstwo od tego wzorca dotyczy argumentów typu boolean
lub Boolean
, w których metoda pobierająca wartość zazwyczaj nosi nazwę isProp()
, a nie getProp()
.
Na przykład wszystkie komponenty należące do pakietów AWT oraz Swing, posiadające tekstowe etykiety, definiują następujące metody:
public String getText(); public void setText(String newText);
Ten wzorzec projektowy bazujący na metodach set
i get
należy stosować w metodach kontrolujących działanie komponentu. Dla zapewnienia spójności warto go wykorzystywać także w pozostałych klasach, które nie mają być komponentami JavaBeans. Tak zwane „kontenery komponentów” wykorzystują introspekcję (opisaną w Rozdział 23.) w celu odszukania par metod set
i get
, niektóre z nich dodatkowo używają ich do stworzenia edytorów właściwości dla komponentów. Zintegrowane środowiska programistyczne, które są w stanie obsługiwać komponenty JavaBeans, udostępniają edytory dla wszystkich standardowych typów (kolorów, czcionek, etykiet i tak dalej). Ich możliwości można jednak wzbogacić lub zmodyfikować, dodając odpowiednią klasę BeanInfo
.
Poniżej podałem minimalne warunki konieczne, jakie musi spełniać klasa, aby mogła być stosowana jako komponent JavaBeans w środowiskach programistycznych dysponujących narzędziami do tworzenia graficznego interfejsu użytkownika:
Klasa musi posiadać konstruktor bezargumentowy.
Klasa powinna wykorzystywać metody set
i get
.
Plik klasowy danej klasy należy umieścić w pliku JAR, używając w tym celu programu jar
(zgodnie z informacjami podanymi w „21.7. Umieszczanie komponentów w plikach JAR”).
Należy zauważyć, że komponent JavaBeans, który nie ma żadnych określonych wymogów odnośnie do klasy, po której ma dziedziczyć, lub interfejsów, które ma implementować, jest określany jako POJO — stary, zwyczajny obiekt Javy. Większość nowoczesnych platform Javy akceptuje takie komponenty, zamiast wymuszać (jak to bywało wcześniej) określoną strukturę dziedziczenia (na przykład w platformie Struts 1 komponenty musiały dziedziczyć po klasie org.struts.Action
) lub wymagać implementacji określonych interfejsów (na przykład technologia EJB2 wymaga implementacji interfejsu javax.ejb.SessionBean
).
W dalszej części receptury przedstawiłem komponent LabelText
, który może stanowić cenne i przydatne narzędzie dla wszystkich osób tworzących aplikacje z graficznym interfejsem użytkownika. Komponent ten łączy w sobie etykietę oraz pole tekstowe, dzięki czemu ułatwia tworzenie interfejsu użytkownika. Wśród plików dołączonych do książki można znaleźć program testowy, którego interfejs graficzny składa się z trzech komponentów LabelText
. Jego wygląd przedstawiłem na Rysunek 21-3.
Kod komponentu LabelText
przedstawiłem na Przykład 21-2. Warto zwrócić uwagę, że komponent można serializować, a niemal wszystkie jego publiczne metody są zgodne z paradygmatem zakładającym wykorzystanie metod set
i get
. W przeważającej większości przypadków działanie metod set
i get
ogranicza się do wywołania odpowiednich metod etykiety lub pola tekstowego. W zasadzie nie można wiele więcej powiedzieć na temat tego komponentu, jednak stanowi on dobry przykład agregacji oraz tworzenia komponentów.
Przykład 21-2. /com/darwinsys/swingui/LabelText.java
// package com.darwinsys.swingui; public class LabelText extends JPanel implements java.io.Serializable { private static final long serialVersionUID = -8343040707105763298L; /** Komponent etykiety. */ protected JLabel theLabel; /** Komponent pola tekstowego. */ protected JTextField theTextField; /** Używana czcionka. */ protected Font myFont; /** Tworzymy obiekt bez wartości początkowych. * Aby klasa mogła być komponentem JavaBeans, MUSI mieć konstruktor * bezargumentowy. */ public LabelText() { this("(Test etykiety)", 12); } /** Tworzymy obiekt z etykietą o podanej treści i polem tekstowym * o domyślnej wielkości. */ public LabelText(String label) { this(label, 12); } /** Tworzymy obiekt z etykietą o podanej treści i polem tekstowym * o podanej wielkości. */ public LabelText(String label, int numChars) { this(label, numChars, null); } /** Tworzymy obiekt z etykietą o podanej treści, polem tekstowym * o podanej wielkości i "dodatkowym" komponentem. * @param label Wyświetlany tekst. * @param numChars Wielkość pola tekstowego. * @param extra Trzeci komponent, taki jak przycisk Anuluj. * Może być równy null, w takim przypadku zostaną wyświetlone * jedynie etykieta i pole tekstowe. */ public LabelText(String label, int numChars, JComponent extra) { super(); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); theLabel = new JLabel(label); add(theLabel); theTextField = new JTextField(numChars); add(theTextField); if (extra != null) { add(extra); } } /** Pobieramy wyrównanie etykiety w poziomie. */ public int getLabelAlignment() { return theLabel.getHorizontalAlignment(); } /** Określamy wyrównanie etykiety w poziomie. */ public void setLabelAlignment(int align) { theLabel.setHorizontalAlignment(align); } /** Pobieramy tekst wyświetlany aktualnie w polu tekstowym. */ public String getText() { return theTextField.getText(); } /** Określamy tekst, który będzie wyświetlony w polu tekstowym. */ public void setText(String text) { theTextField.setText(text); } /** Pobieramy tekst etykiety. */ public String getLabel() { return theLabel.getText(); } /** Określamy tekst etykiety. */ public void setLabel(String text) { theLabel.setText(text); } /** Określamy czcionkę używaną w obu komponentach. */ public void setFont(Font f) { // Wywołanie super() wykonywane w konstruktorze tej klasy // może spowodować wywołanie metody setFont() (z metody // Swing.LookAndFeel.installColorsAndFont), jeszcze zanim // utworzymy nasz komponent, obchodzimy ten problem. if (theLabel != null) theLabel.setFont(f); if (theTextField != null) theTextField.setFont(f); } /** Dodajemy ActionListener, aby otrzymywać zdarzenia akcyjne z pola tekstowego. */ public void addActionListener(ActionListener l) { theTextField.addActionListener(l); } /** Usuwamy ActionListener z pola tekstowego. */ public void removeActionListener(ActionListener l) { theTextField.removeActionListener(l); } }
Po skompilowaniu powyższej klasy można ją umieścić w pliku JAR.
Należy umieścić komponent w pliku JAR (co sprowadza się do utworzenia archiwum JAR zawierającego plik klasowy komponentu oraz plik manifestu).
Choć większość kontenerów odwołuje się obecnie do komponentów POJO, korzystając z mechanizmów odzwierciedlania, to może się zdarzyć, że będziemy chcieli umieścić nasz komponent JavaBeans w niezależnym pliku JAR, by móc łatwiej go rozpowszechniać. W takim pliku JAR, oprócz skompilowanych plików klasowych komponentu, musi się znaleźć prototyp manifestu zawierający przynajmniej dwa przedstawione poniżej wiersze tekstu, choć można tam podać także informacje o twórcy oraz prawach autorskich:
Name: LabelText.class Java-Bean: true
Zakładając, że informacje te zostaną umieszczone w pliku LabelText.stub, komponent można będzie przygotować do rozpowszechniania, wykonując polecenie jar
(patrz „21.4. Stosowanie programu archiwizującego jar”). Ponieważ plik JAR musi zawierać pliki klasowe umieszczone w odpowiednich katalogach odpowiadających pakietowi, do jakiego dane klasy należą (patrz „21.1. Tworzenie pakietu”), oraz ze względu na umieszczenie komponentu LabelText
w pakiecie com.darwinsys.swingui
(patrz „1.5. Pobieranie przykładów dołączonych do tej książki i korzystanie z nich”), przed utworzeniem pliku archiwum przeszedłem do odpowiedniego katalogu, a następnie w poleceniu jar
podałem pełną ścieżkę dostępu do pliku klasowego (plik .stub może się znajdować w dowolnym miejscu, ja jednak umieściłem go w tym samym katalogu, w którym jest plik źródłowy, aby później nie mieć problemów z jego odszukaniem; dlatego też także do niego musiałem się odwołać, podając pełną ścieżkę dostępu):
$ cd $js/darwinsys/src $ jar cvfm labeltext.jar com/darwinsys/swingui/LabelText.stub \ com/darwinsys/swingui/LabelText.class added manifest adding: com/darwinsys/swingui/LabelText.class(in=1607) (out=776)(deflated 51%) $
Oczywiście, podczas rzeczywistych prac nad programowaniem chcielibyśmy zautomatyzować cały ten proces, używając przy tym odpowiednich narzędzi, takich jak Ant (patrz „1.6. Automatyzacja kompilacji przy użyciu programu Ant”) lub Maven (patrz „1.7. Automatyzacja zależności, kompilacji, testowania i wdrażania przy użyciu programu Apache Maven”).
Teraz można już zainstalować labeltext.jar jako komponent JavaBeans. Niemniej jednak z ciekawości sprawdźmy zawartość utworzonego pliku JAR. Opcja x
programu jar
pozwala na pobranie całej zawartości wskazanego archiwum:
$ jar xvf labeltext.jar
created: META-INF/
extracted: META-INF/MANIFEST.MF
extracted: com/darwinsys/swingui/LabelText.class
$
Plik MANIFEST.MF jest tworzony na podstawie pliku manifestu (w naszym przypadku jest to plik LabelText.stub); sprawdźmy jego zawartość:
$ more META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.4.2_03 (Apple Computer, Inc.)
Java-Bean: true
Name: LabelText.class
Jak widać, z plikiem manifestu nie stało się nic ciekawego — pojawiły się w nim jedynie dwa dodatkowe wiersze tekstu. Niemniej teraz nasza klasa jest już gotowa do wykorzystania jako komponent JavaBeans. Aby wykorzystać ją w programach służących do tworzenia graficznego interfejsu użytkownika, należy skopiować przygotowany plik JAR do katalogu beans bądź zainstalować go przy użyciu odpowiedniego kreatora.
Chcemy umieścić serwlet oraz inne zasoby w pojedynczym pliku, który po zapisaniu na serwerze WWW umożliwiałby wykonywanie naszej aplikacji.
Należy stworzyć plik archiwum sieciowego (WAR), wykorzystując w tym celu program jar
. Bądź też, zgodnie z informacjami podanymi już wcześniej, skorzystaj z programu Maven, używając w wywołaniu opcji packing=war
.
Serwlety są komponentami wykonywanymi przez serwery WWW. Aby ułatwić ich instalację na serwerach, istnieje możliwość umieszczania serwletów w plikach archiwalnych. W specyfikacji Servlet API występuje pojęcie aplikacji sieciowej, którą tworzy zbiór dokumentów HTML, JSP, serwletów oraz innych zasobów. Poniżej przedstawiłem przykładowe elementy, które mogą tworzyć strukturę katalogów takiej aplikacji:
Strony WWW
Katalog serwera
Deskryptor
Katalog, w którym są umieszczane serwlety i wszelkie inne klasy wykorzystywane przez serwlety oraz dokumenty JSP
Katalog, w którym są umieszczane pliki JAR oraz wszelkie inne klasy wykorzystywane przez klasy umieszczone w katalogu WEB-INF/classes
Po przygotowaniu plików w powyższy sposób wystarczy umieścić je w archiwum, posługując się programem jar
:
jar cvf MyWebApp.war .
Następnie można umieścić stworzony w ten sposób plik WAR na serwerze WWW. Informacji dotyczących konkretnych sposobów umieszczania plików WAR na serwerach i korzystania z nich należy szukać w dokumentacji używanego serwera WWW.
Chcemy, aby naszą aplikację można było instalować na wielu różnych platformach systemowych oraz by mogli to robić użytkownicy, którzy jeszcze nie uzyskali doktoratów z instalacji oprogramowania.
Instalacja oprogramowania nie jest zadaniem trywialnym. Użytkownicy systemów Unix preferujący wykonywanie operacji z poziomu wiersza poleceń będą szczęśliwi, mogąc własnoręcznie rozpakować archiwum i określić ścieżkę (zmienną środowiskową PATH
) konieczną do poprawnego działania programu. Jeśli jednak chcemy, by nasze aplikacje były dostępne dla szerszych kręgów użytkowników, konieczne będzie zastosowanie jakiegoś prostszego rozwiązania. Na przykład „wskaż i kliknij”. Istnieje kilka narzędzi podejmujących próbę ułatwienia procesu instalacji. Najlepsze spośród nich są w stanie tworzyć ikony uruchamiające program w systemach Mac OS, Windows, a nawet w niektórych środowiskach graficznych wykorzystywanych w systemach Unix (takich jak CDE, KDE bądź GNOME).
Osobiście uzyskiwałem całkiem zadowalające rezultaty, posługując się komercyjnym programem InstallAnywhere firmy Zero G Software. Program ten jest w stanie sprawdzić, czy na komputerze jest zainstalowana wirtualna maszyna Javy (JVM), oraz może przeprowadzać instalację normalną i sieciową (czyli pozwala na bezpośrednie zainstalowanie programu bądź też na skopiowanie go z podanej witryny WWW). Program ten kilkakrotnie zmieniał właścicieli, a obecnie należy do firmy Flexera Software (http://www.flexerasoftware.com).
Program InstallShield przez długi czas był liderem wśród programów instalacyjnych przeznaczonych dla systemów Windows, jednak w świecie użytkowników języka Java zawsze miał on więcej konkurentów.
W „21.11. Java Web Start” przedstawiłem Java Web Start — opracowaną przez firmę Sun technologię instalacji oprogramowania bazującą na wykorzystaniu internetu.
Moje własne wczesne próby stworzenia niezależnego programu instalacyjnego dla oprogramowania pisanego w Javie można znaleźć na GitHubie w formie projektu yajinstaller (ang. Yet Another unfinished Java Installer — jeszcze jeden niedokończony instalator Javy). Jeśli Czytelnikowi uda się go usprawnić, to proszę o podesłanie pliku z różnicami lub wygenerowanie żądania ściągnięcia.
Katalog zawierający pliki aplikacji powinien mieć strukturę opisaną w tej recepturze. Można także skorzystać z odpowiednich narzędzi, takich jak komercyjne programy instalacyjne (patrz „21.9. „Zapisz raz, instaluj wszędzie””) lub kreator Export Mac OS Application dostępny w środowisku Eclipse.
Aby jakiś program mógł być pełnoprawną aplikacją systemu Mac OS X, musi zostać przygotowany zgodnie z konkretnym formatem instalacyjnym, który na szczęście jest całkiem prosty. Każda aplikacja systemu Mac OS X — bez względu na język, w jakim została napisana — jest instalowana w niezależnym katalogu nazywanym „pakietem aplikacji” (ang. Application Bundle), którego nazwa powinna się kończyć ciągiem znaków .app. To preferowany sposób instalowania aplikacji w systemie Mac OS X. W odróżnieniu od zwyczajnych plików JAR, pakiety aplikacji są wyświetlane jako ikony zarówno w programie Finder („eksploratorze”), jak i w innych aplikacjach, można je zapisywać na pasku Doku w celu uruchamiania przy użyciu jednego kliknięcia, można również kojarzyć z nimi rozszerzenia plików (dzięki czemu po dwukrotnym kliknięciu takiego pliku lub otworzeniu go zostanie uruchomiona odpowiednia aplikacja, a w niej otworzony kliknięty plik).
Rysunek 21-4 przedstawia strukturę plików w katalogu prostej aplikacji napisanej w Javie.
Jak widać, pakiet aplikacji składa się z katalogu Contents zawierającego dwa katalogi podrzędne: MacOS oraz Resources. Pierwszy z nich zawiera plik wykonywalny, a w przypadku aplikacji napisanych w Javie jest to plik JavaApplicationStub, natywny program używany w systemie Mac OS do uruchamiania aplikacji napisanych w Javie (wchodzi on w skład pakietu Developer Tools). Plik Contents/Resources/*.icns zawiera ikony przeznaczone do wyświetlania (w różnych rozdzielczościach) w programie Finder. Plik ten można utworzyć, używając programu IconComposer (dostępnego w katalogu /Developer/Applications/Utilities/Icon Composer.app). Katalog Contents/Resources/Java zawiera skompilowane pliki klasowe aplikacji, jak również (ewentualnie) pliki JAR. Plik Contents/Info.plist scala ze sobą wszystkie elementy aplikacji, określając nazwy różnych plików, typy plików, które aplikacja może otwierać, oraz wszelkie inne informacje na jej temat.
Lepsze, komercyjne narzędzia do tworzenia programów instalacyjnych (opisane w „21.9. „Zapisz raz, instaluj wszędzie””) są w stanie wygenerować taką strukturę plików i katalogów samodzielnie. Można to także zrobić przy użyciu programu narzędziowego Ant. Również Eclipse 3.0 (w wersji Milestone 7) potrafi generować aplikacje dla systemu Mac OS X. Wystarczy wybrać Project w widoku Navigator, w menu Export wskazać opcję Export Mac OS X application, a następnie w dwóch kolejnych oknach podać informacje dotyczące na przykład miejsca, gdzie należy umieścić aplikację (patrz Rysunek 21-5). W przypadku stosowania programu Ant lub Eclipse będziemy zapewne chcieli skorzystać z opcji Disk Copy, by utworzyć plik dmg (obraz dysku) naszego katalogu. Pliki dmg mogą być pobierane przez innych użytkowników systemu Mac OS X i zazwyczaj są automatycznie rozpakowywane po pobraniu, co pozwala odtworzyć gotowy pakiet aplikacji.
Pakiety aplikacji zostały opisane w Rozdział 7. książki Mac OS X for Java Geeks autorstwa Willa Iversona wydanej przez wydawnictwo O’Reilly (http://shop.oreilly.com/product/9780596004002.do). Książkę tę polecam wszystkim programistom używającym języka Java, którym zależy na pisaniu dobrych aplikacji przeznaczonych do użycia w systemie Mac OS X, a zwłaszcza dla osób rozpowszechniających aplikacje, które chciałyby zrobić dobre wrażenie na innych użytkownikach systemu Mac OS X.
Technologia Java Web Start łączy w sobie łatwość obsługi wynikającą z wykorzystania przeglądarek WWW z systemem zabezpieczeń wzorowanym na rozwiązaniach stosowanych podczas obsługi apletów (które jednak można zmieniać dla poszczególnych aplikacji) oraz możliwością błyskawicznego pobierania uaktualnionych wersji programu; dodatkowo jednak powala na uruchamianie normalnych aplikacji na komputerze użytkownika.
Java Web Start (JWS) jest technologią służącą do pobierania aplikacji przez internet. Rozwiązanie to nie ma nic wspólnego z apletami, które wymagają specjalnych metod i są wykonywane przez przeglądarki WWW. JWS pozwala na wykonywanie standardowych aplikacji dysponujących graficznym interfejsem użytkownika. Technologia ta jest przeznaczona dla osób poszukujących łatwości dostępu do aplikacji, jaką zapewniają przeglądarki WWW z pełnymi możliwościami funkcjonalnymi typowymi dla standardowych aplikacji. Z punktu widzenia użytkownika cały proces wykorzystania technologii Java Web Start ma następującą postać: na stronie WWW umieszczane jest połączenie wskazujące aplikację, którą użytkownik chce uruchomić; jeśli na komputerze użytkownika już wcześniej zostało zainstalowane oprogramowanie JWS (zagadnienie to opisałem w dalszej części receptury), to wystarczy kliknąć odpowiednie połączenie (nazwijmy je umownie „Uruchom”), a po kilku chwilach lub minutach (zależnie od szybkości łącza) aplikacja zostanie pobrana i uruchomiona. Rysunek 21-6 przedstawia powitalną winietę wyświetlaną po kliknięciu połączenia Run wskazującego stworzoną przeze mnie aplikację JabaDex.
Po pomyślnym zakończeniu pobierania aplikacji zostanie ona uruchomiona. W nieco „skondensowany” sposób przedstawiłem to na Rysunek 21-7.
Dla wygody użytkownika pliki JAR oraz wszelkie inne zasoby konieczne do wykonania aplikacji są przechowywane w pamięci podręcznej. Dzięki temu można później uruchomić aplikację (nawet bez połączenia z internetem), posługując się specjalnym programem służącym do uruchamiania aplikacji JWS. Rysunek 21-8 przedstawia program Java Web Start Application Manager informujący o możliwości uruchomienia aplikacji JabaDex. Technologia JWS pozwala także na tworzenie skrótów do „aplikacji JWS” i umieszczania ich na pulpicie systemu bądź uruchamiania aplikacji z menu Start; oczywiście jeśli tylko używany system operacyjny na to pozwala.
Podstawowe czynności, jakie należy wykonać, aby przygotować aplikację do rozpowszechniania przy użyciu technologii JWS, przedstawiłem w poniższej notatce.
Przeanalizujmy teraz szczegółowo powyższe czynności. Pierwszym krokiem jest umieszczenie aplikacji w jednym lub kilku plikach JAR. Sposoby korzystania z programu jar
zostały opisane we wcześniejszej części niniejszego rozdziału. Główny plik JAR powinien zawierać klasy tworzące aplikację oraz wszelkie inne zasoby, takie jak pliki właściwości, obrazy i tak dalej.
W witrynie WWW oprócz pliku JAR z samą aplikacją należy także udostępnić inne pliki JAR zawierające wszelkie dodatkowe interfejsy programistyczne, takie jak JavaMail, com.darwinsys.util
i tak dalej. Można także zamieścić programy stworzone w innych językach, choć ich poprawne działanie będzie zależeć od używanej platformy systemowej.
Opcjonalnie można także podać ikony, które będą reprezentować publikowaną aplikację w formacie JWS. Ikony te powinny być zapisane w formatach GIF lub JPEG i mieć wymiary 64×64 piksele.
Kolejnym etapem jest stworzenie pliku opcji JNLP zawierającego informacje o aplikacji. Pliki JNLP to zwyczajne pliki XML. Oficjalną specyfikację plików JNLP można znaleźć pod adresem http://java.sun.com/products/javawebstart/download-spec.html. Plik, którego użyłem w celu umożliwienia wykonywania aplikacji JabaDex przy użyciu technologii Java Web Start, zawiera jedynie podzbiór wszystkich dostępnych elementów XML. Niemniej jednak w zasadzie można go zrozumieć bez żadnych dodatkowych wyjaśnień. Kod tego pliku przedstawiłem na Przykład 21-3.
Przykład 21-3. JabaDex.jnlp
<?xml version="1.0" encoding="utf-8"?>
<!-- JNLP File for JabaDex Application -->
<jnlp spec="1.0+"
codebase="http://www.darwinsys.com/jabadex"
href="jabadex.jnlp">
<information>
<title>JabaDex Personal Information Manager Application</title>
<vendor>Darwin Open Systems</vendor>
<homepage href="/"/>
<description>JabaDex Personal Information Manager Application
</description>
<description kind="short">A simple personal information manager.
</description>
<icon href="images/jabadex.jpg"/>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<resources>
<j2se version="1.5"/>
<jar href="jabadex.jar"/>
<jar href="com-darwinsys-util.jar"/>
</resources>
<application-desc main-class="JDMain"/>
</jnlp>
W razie konieczności należy zmodyfikować listę typów MIME serwera WWW w taki sposób, aby pliki JNLP były zwracane jako dane typu application/x-java-jnlp-file
. Sposób, w jaki należy to zrobić, zależy tylko i wyłącznie od używanego serwera WWW, choć wszystko powinno się sprowadzić do określenia typu skojarzonego z rozszerzeniem .jnlp.
Kolejną czynnością, jaką należy wykonywać tylko w razie konieczności, jest zmodyfikowanie aplikacji w taki sposób, aby pobierała obiekt ClassLoader
i zamiast bezpośredniego odczytywania plików uzyskiwała dostęp do zasobów, posługując się jego metodami getResource()
. Obrazki oraz wszelkie inne zasoby należy pobierać właśnie w taki sposób. Aby na przykład jawnie pobrać plik właściwości, można by się posłużyć metodami getClassLoader()
oraz getResource()
w sposób przedstawiony na Przykład 21-4.
Przykład 21-4. /packaging/GetResourceDemo (fragment kodu)
// Odnajdujemy obiekt ClassLoader użyty do pobrania programu. // Należy go uważać za jedyny poprawny obiekt tego typu dla naszej // aplikacji. ClassLoader loader = this.getClass().getClassLoader(); // Wywołanie metody getResource() w celu otworzenia pliku. InputStream is = loader.getResourceAsStream("widgets.properties"); if (is == null) { System.err.println("Nie można odczytać pliku właściwości"); return; } // Stworzenie obiektu Properties. Properties p = new Properties();
// Wczytanie pliku właściwości do obiektu Properties. try { p.load(is); } catch (IOException ex) { System.err.println("Pobranie właściwości nie powiodło się: " + ex); return; } // Wyświetlenie zawartości w celu potwierdzenia wczytania. p.list(System.out);
Warto zauważyć, że w tym przypadku metoda getResource()
zwraca obiekt java. net.URL
, a metoda getResourceAsStream()
— obiekt InputStream
.
Aby możliwości funkcjonalne uruchamianej aplikacji nie były ograniczane (a zapewne w przypadku uruchamiania standardowych aplikacji będzie to ze wszech miar pożądane), konieczne będzie podpisanie pliku JAR. Proces cyfrowego podpisywania pliku JAR został szczegółowo opisany w „21.12. Podpisywanie plików JAR”. Jeśli aplikacja będzie chciała korzystać z pełnych możliwości, a jej pliki JAR nie zostaną podpisane, to na ekranie zostanie wyświetlony smutny komunikat informacyjny przedstawiony na Rysunek 21-9.
W przypadku samodzielnego podpisania plików JAR (na przykład przy użyciu testowego certyfikatu) użytkownik uruchamiający aplikację zobaczy ostrzeżenie przedstawione na Rysunek 21-10.
Ostatnią czynnością, jaką należy wykonać, jest umieszczenie w witrynie połączenia wskazującego na plik JNLP przygotowany z myślą o udostępnianej aplikacji oraz, ewentualnie, połączenia umożliwiającego pobranie oprogramowania JWS. Oprogramowanie to jest udostępniane w formie skompilowanego programu wykonywanego przez przeglądarkę jako „aplikacja pomocnicza”. Użytkownik musi je pobrać i zainstalować, nim będzie mógł uruchamiać aplikacje rozpowszechniane za pomocą JWS. Niezbędny program w skompilowanej formie jest dostępny na stronie poświęconej technologii Java Web Start. Teoretycznie rzecz biorąc, w razie konieczności można by stworzyć własną implementację tej aplikacji, bazując na informacjach podanych w specyfikacji JNLP.
W rzeczywistości, jeśli oprogramowanie JWS jest już zainstalowane na komputerze użytkownika, to połączenie umożliwiające pobranie tego oprogramowania nie jest potrzebne. Jeśli jednak oprogramowanie nie zostało jeszcze zainstalowane, to połączenie Uruchom nie będzie działać poprawnie. Na stronie Developer’s Guide można znaleźć informacje opisujące, jak wykorzystać skrypty działające po stronie klienta (JavaScript lub VBScript), aby na stronie było widoczne tylko jedno z tych połączeń. Połączenie Uruchom musi wskazywać na plik JNLP:
Jeśli zainstalowałeś oprogramowanie JWS, możesz <a href=""jabadex.jnlp>uruchomić aplikację JabaDex</a>. W przeciwnym razie <a href="http://www.oracle.com/technetwork/java/javase/javawebstart/">zdobądź więcej informacji na temat tej technologii</a>.
Teraz aplikację będzie już można pobierać i uruchamiać za pośrednictwem internetu!
Stronę poświęconą technologii Java Web Start można znaleźć pod adresem http://www.oracle.com/technetwork/java/javase/javawebstart/index.html.
Uzyskaj lub samemu stwórz certyfikat cyfrowy, a następnie podpisz pliki JAR, posługując się programem jarsigner
.
Pliki JAR można podpisywać cyfrowo, co pozwala na określenie tożsamości ich twórcy. Rozwiązanie to przypomina cyfrowe podpisywanie witryn WWW: użytkownicy wiedzą już, że nie należy podawać ważnych informacji, takich jak numery kart kredytowych, jeśli ikonka kłódki w przeglądarce informuje, że witryna nie jest podpisana cyfrowo. Proces cyfrowego podpisywania plików JAR wykorzystuje zabezpieczające mechanizmy programistyczne stanowiące integralną część jądra platformy Java 2. Pliki JAR należy podpisywać, gdy zawierają one aplety (patrz Rozdział 16.) lub aplikacje JWS (patrz „21.11. Java Web Start”). W obu przypadkach wykorzystywany jest ten sam program — jarsigner
— należący do standardowej wersji języka Java 2.
Gdy tworzona aplikacja jest gotowa do opublikowania, należy wystąpić z prośbą o wydanie certyfikatu do jednej z agencji komercyjnych. Oczekując na otrzymanie właściwego certyfikatu, można testować aplikację, posługując się testowym certyfikatem stworzonym własnoręcznie. Poniżej opisałem czynności, jakie należy wykonać, aby podpisać plik JAR przy użyciu certyfikatu testowego:
Stworzenie nowego klucza i zapisanie go w „magazynie kluczy”:
keytool -genkey -keystore myKeystore -alias myself
Opcja alias
o wartości myself
jest w tym przypadku konieczna — ma ona przypominać, że klucz został stworzony samodzielnie i nie należy go używać w produkcyjnej wersji programu.
Program zażąda podania informacji o nowym kluczu. Poprosi o podanie hasła, które będzie chronić „magazyn kluczy”, a następnie o nazwisko, dział, firmę, miasto, stan, kraj oraz kilka innych informacji. Informacje te zostaną zapisane w nowym pliku „magazynu kluczy”, który zostanie umieszczony na dysku.
Stworzenie nowego samodzielnie podpisanego certyfikatu testowego:
keytool -selfcert -alias myself -keystore myKeystore
W tym momencie konieczne będzie podanie hasła do „magazynu kluczy”, po czym program keytool
wygeneruje certyfikat.
W tym momencie można sprawdzić, czy wcześniejsze czynności zostały wykonane poprawnie. Najlepiej zrobić to, wyświetlając zawartość „magazynu kluczy”:
keytool -list -keystore myKeystore
Poniżej zostały przedstawione przykładowe wyniki wykonania tego polecenia:
Keystore type: jks Keystore provider: SUN Your keystore contains 1 entry: myself, Mon Dec 11:05:27 EST 2000, keyEntry, Certificate fingerprint (MD5): 56:9E:31:81:42:07:BF:FF:42:01:CB:42:51:42:96:B6
Podpisanie pliku JAR przy użyciu wygenerowanego certyfikatu testowego.
Jarsinger -keystore myKeystore test.jar myself
Program jarsigner
zmodyfikuje zawartość katalogu META-INF podpisywanego pliku JAR, dodając do niego informację o certyfikacie oraz cyfrowe podpisy wszystkich elementów umieszczonych w archiwum.
Podpisanie archiwum może zabrać nieco czasu, zależnie od szybkości procesora, liczby plików umieszczonych w archiwum oraz innych czynników. W efekcie uzyskujemy jednak podpisany cyfrowo plik JAR, który będzie można wykorzystywać do rozpowszechniania apletów, aplikacji uruchamianych przy użyciu technologii Java Web Start oraz we wszystkich innych mechanizmach wymagających podpisanych archiwów.
Więcej informacji na temat podpisywania cyfrowego oraz zezwoleń można znaleźć w książce Java Security Scotta Oaksa wydanej przez wydawnictwo O’Reilly (http://shop.oreilly.com/product/9780596001575.do). Z kolei najlepszym źródłem informacji na temat narzędzi JDK, o których wspominałem w tym rozdziale, jest dokumentacja dostarczana wraz z używaną wersją JDK.
[79] To ostatnie stwierdzenie nie do końca jest prawdą. W systemach Unix, przynajmniej w języku C, istnieje rozróżnienie pomiędzy normalnymi plikami dołączanymi a plikami przechowywanymi w podkatalogu sys, a nazwy pól wielu struktur rozpoczynają się od jednej bądź dwóch liter i znaku podkreślenia, na przykład pola struktury hasła mają następujące nazwy: pw_name
, pw_password
, pw_home
i tak dalej. Niemniej jednak i tak nie można tego w żaden sposób porównać ze spójną strukturą nazewnictwa pakietów stosowaną w Javie.
[80] Niektórzy lubią używać takich nazw jak MyPackage.mf, tak by było jasne, czego dotyczy plik manifestu; rozszerzenie pliku jest dowolne, nie musi to być .mf, jednak stosowanie właśnie tego rozszerzenia stanowi dobry, zwyczajowy sposób oznaczania plików manifestów.