Spotykamy je wszędzie – korzystając z oprogramowania do czatowania, zaawansowanych aplikacji e-biznesowych oraz w bankowości internetowej. Wciąż pozostają potężnym narzędziem, nawet pomimo faktu, że w najprostszych zastosowaniach najczęściej ustępują obecnie miejsca Flashowi oraz XHTML-owi sprzęgniętemu z AJAX-em. Aplety – bo o nich mowa – obok serwletów i JSP należą do najważniejszych dla webmastera sposobów wykorzystania Javy.
Aplet (ang. applet) to niewielki program uruchamiany po stronie klienta – najczęściej przez przeglądarkę internetową. Od serwletu różni się tym, że ten drugi uruchamiany jest na serwerze i użytkownik otrzymuje jedynie wynik działania serwletu. Obecnie najczęściej do pisania apletów wykorzystuje się Javę, choć nie jest to jedyna możliwość.
Pewną popularnością cieszyły się kilka lat temu aplety napisane jako kontrolki ActiveX. Zostały one jednak wyparte przez Javę, głównie ze względu na ich małą mobilność (ActiveX współpracował wyłącznie z przeglądarką Internet Explorer), a także dużą liczbę krytycznych luk bezpieczeństwa w samej technologii ActiveX. Jak to działa? Po wejściu na stronę z apletem, przeglądarka pobiera z serwera plik apletu skompilowanego do postaci kodu bajtowego. Następnie jest on przekazywany do maszyny wirtualnej Javy i tam uruchamiany.
Od tego momentu aplet zadziała podobnie jak zwykły program Javy uruchamiany pod kontrolą maszyny wirtualnej. Ta ostatnia nie potraktuje go jednak jak zupełnie samodzielną aplikację javową. Aplet znajdzie się w specjalnym bezpiecznym środowisku, które zostanie szerzej opisane w dalszej części artykułu.
Aplety piszemy jak każdy inny program w Javie. Trzeba jedynie pamiętać o pewnych niuansach związanych z ich programowaniem. Nie musisz się jednak obawiać, że to wiedza tajemna – przybliżenie tych właśnie sekretów jest celem niniejszego artykułu.
Wybierz narzędzia
Do stworzenia programu będziesz mógł wykorzystać dowolne środowisko programistyczne dedykowane językowi Java – a w szczególności to, którego używałeś dotąd. W naszym artykule pokażemy,
jak stworzyć aplety z wykorzystaniem narzędzia NetBeans IDE (oficjalnie wspieranego przez producenta Javy, firmę Sun). Jako dobrą alternatywę można również polecić narzędzie Eclipce (http://www.eclipse.org), do którego stworzenia przyczynił się z kolei IBM. Ponadto w sieci bez problemu znajdziesz jeszcze kilka innych środowisk wartych uwagi.
Aplety można pisać w dowolnej z dotychczas wydanych wersji języka Java. Jednak aby nie sięgać zbyt daleko w głąb historii, warto budować aplikacje dedykowane dla Java Runtime Environment
(JRE) w wersji 1.5 lub 1.6. Oznaczać to będzie skorzystanie w trakcie programowania z odpowiadających im Java Development Kit (JDK). Środowisko JRE pobrane ze stron firmy Sun(http://www.java.com) zadba także o instalację odpowiednich wtyczek w przeglądarkach internetowych – to bezpośrednio za ich sprawą możliwe będzie uruchomienie apletu.
Warto wiedzieć, że platforma JRE 1.6 (czyli Java 6.0) jest jeszcze narzędziem stosunkowo młodym – finalna wersja wypuszczona została zaledwie pół roku temu. Niestety wprowadza ona szereg niekompatybilności z JRE 1.5, za sprawą których część apletów napisanych dla 1.5 może przestać działać.
Jest to powód, dla którego obecnie mogą występować komplikacje z upowszechnieniem się wtyczki w wersji 1.6. Przykładowo jeden z większych banków detalicznych zaleca dalsze korzystanie z JRE 1.5. W \”szóstce\” jego system po prostu nie zadziała.
Warto zatem mieć na uwadze powyższe problemy,
wybierając wersję Javy. Nie należy też jednak przesadzać. Fakt, że część twórców nie przystosowała się jeszcze do zmiany na nowszą wersję, nie może być powodem, dla którego na zawsze już mielibyśmy pozostać przy JRE 1.5. Ponieważ Java 6.0 wprowadza wiele wygodnych usprawnień, coraz więcej programistów będzie spoglądać właśnie w jej kierunku.
Szczegółowy opis instalacji NetBeans IDE wraz z odpowiednim środowiskiem JDK znajdziesz w ramce.
Własny czat
Aby przybliżyć sposób programowania apletów, stworzymy przykładową aplikację. Będzie to prosty czat pozwalający na wymianę wiadomości pomiędzy osobami zalogowanymi. Oprócz okna wprowadzania komunikatu oraz okna rozmowy, aplet prezentować będzie również listę obecnych w danym momencie użytkowników.
Czat jest programem stworzonym w architekturze \”klient-serwer\”. Potrzebuje części klienckiej do działania po stronie użytkownika – będzie to przede wszystkim interfejs naszego systemu. Właściwa komunikacja pomiędzy poszczególnymi rozmówcami zostanie jednak zapewniona przez część serwerową. Byłoby najlepiej, aby aplikacja serwerowa była uruchomiona jako daemon – stale nasłuchiwała na określonym porcie, czekając na przychodzące połączenia. Po ich nadejściu połączenie mogłoby być utrzymywane przez cały czas obecności użytkownika na czacie. Pozwoliłoby to na obustronne inicjowanie wymiany danych i ograniczyło ilość zbędnego ruchu.
Takie podejście wymaga jednak możliwości pozostawienia na serwerze usługi w trybie daemona przez dowolnie długi czas. Ponieważ jednak większość usług hostingowych limituje czas działania skryptów, można zdecydować się na inną metodę. Aplet będzie łączyć się z serwerem HTTP, za sprawą którego uruchomiony zostanie skrypt zarządzający częścią serwerową. Nie umniejsza to funkcjonalności czata, jednak klient będzie musiał regularnie nawiązywać kolejne połączenia, aby sprawdzić, czy nie ma nowych komunikatów dla niego – sam serwer nie będzie bowiem mieć możliwości inicjowania połączenia.
Inaczej mówiąc, w pierwszym przypadku serwer i klient umawiają się, że będą sobie wzajemnie
o wszystkim mówić, gdy tylko dostaną jakąś informację do przekazania. W drugim przypadku klient ma za zadanie regularnie wypytywać serwer o to, czy ma on jakąś informację do przekazania.
Samo oprogramowanie czata po stronie serwera może być napisane w dowolnym języku programowania, który dostawca hostingu udostępnił z poziomu serwera HTTP. W szczególności może to być PHP, Perl albo Java – o ile istnieje taka możliwość. W artykule tym nie będziemy się szczegółowo przyglądać skryptom serwera czata – prosty przykład implementacji znajdziesz na dołączonej płycie CD.
Struktura apletu
Aplet nieznacznie różni się swoją strukturą od zwykłej aplikacji w Javie. W kwestii technicznej dziedziczy on po klasie {stala}java.applet.Applet{/stala}. Do swojego działania nie potrzebuje metody {stala}main(){/stala}.
Zamiast tego otrzymujemy do zaimplementowania szereg metod charakterystycznych dla apletu:
- {stala}init(){/stala} – jest to podstawowa metoda apletu, która musi zostać zaimplementowana. Zostanie wywołana tylko raz, w momencie uruchomienia programu, jeszcze zanim zacznie on swoją normalną pracę. Metoda {stala}init(){/stala} może posłużyć m.in. do inicjowania klas z danymi, przygotowania interfejsu czy nawiązania połączeń sieciowych.
- {stala}start(){/stala} – zostanie wywołana po metodzie {stala}init(){/stala}, a także za każdym razem, gdy użytkownik aktywuje okno lub kartę strony, na której znajduje się aplet.
- {stala}stop(){/stala} – analogicznie do {stala}start(){/stala}, metoda ta znajduje swoje zastosowanie podczas deaktywacji w przeglądarce karty, na której znajduje się aplet lub całego okna przeglądarki.
- {stala}paint(){/stala} – metoda rysująca grafikę (głównie interfejs) w aplecie. Wywoływana jest na początku działania skryptu, a także za każdym razem, gdy konieczne jest przerysowanie okna apletu. Dzieje się tak w sytuacjach, gdy okno przeglądarki zostało choćby częściowo przesłonięte innym – w czasie ponownej aktywacji musi ono dokonać przerysowania siebie, a dla prawidłowego wykonania tej operacji wywołana zostanie także metoda {stala}paint(){/stala} apletu znajdującego się na stronie.
- {stala}destroy(){/stala} – metoda wywoływana na zakończenie działania apletu (w czasie jego zamykania). Pozwala apletowi w kulturalny sposób zakończyć wykonywane operacje, w szczególności pozamykać otwarte połączenia sieciowe i ręcznie zwolnić inne zasoby. Jej implementowanie nie jest konieczne – zasoby i tak zostaną zwolnione w sposób automatyczny. Zastosowanie {stala}destroy(){/stala} ma jednak sens, gdy prawidłowe zakończenie działania apletu wymaga pewnych szczególnych czynności.
Warto podkreślić, że wykorzystując umiejętnie metody {stala}start(){/stala} i {stala}stop(){/stala}, możesz uruchamiać i zatrzymywać te elementy swojego apletu, które są zupełnie zbędne, gdy użytkownik nie zwraca na aplet uwagi. Aktualizacja sekundnika w tym czasie wydaje się mało konieczna, natomiast odtwarzanie dźwięków w sytuacji, gdy użytkownik jest zajęty czymś innym, może wręcz powodować jego rozdrażnienie.
Jest to świetny pomysł na wstrzymanie przede wszystkim operacji w sporym stopniu obciążających zasoby systemowe.
import java.applet.*;
public class MyCzat extends Applet {
public void init() {
// Operacje wykonywane podczas inicjowania apletu
}
}
Krok po kroku
{tlo_1}
1. Zainstaluj NetBeans IDE
Pobierz środowisko NetBeans IDE (http://www.netbeans.com/). Następnie zainstaluj najnowszy JDK 1.6 ze strony firmySun(http://java.sun.com/javase/downloads/index.jsp). Po uruchomieniu środowiska, wybierz z menu „File” opcję „New Project”. Zostanie uruchomiony kreator. Z kategorii „General” wybierz „Java Class Library”.
{/tlo}
{tlo_0}
2. Stwórz nowy projekt
Na następnej karcie zostaniesz poproszony o podanie nazwy projektu oraz lokalizacji folderu na dane. NetBeans IDE automatycznie utworzy w nim podfolder z nazwą projektu. Po zakończeniu działania kreatora zostanie utworzony pusty projekt.
{/tlo}
{tlo_1}
3. Dodaj nowy pakiet
W drzewie \”Projects\” odnajdź katalog \”Source Packages\”. Zawiera on wirtualny pakiet \”default packages\”. Tu znajdują się klasy niewrzucone do żadnego zdefiniowanego pakietu.Obecnie jednak zaleca się tworzenie dedykowanych
pakietów. Zrobisz to poprzez opcję \”New/Java Package\”.
{/tlo}
{tlo_0}
4. Stwórz nową klasę
Czas na stworzenie klasy dedykowanej naszemu apletowi. W tym celu prawym przyciskiem
myszy kliknij w utworzony w poprzednim punkcie pakiet. Z menu kontekstowego wybierz \”New/Japplet\”. Zostaniesz poproszony o podanie nazwy klasy oraz jej lokalizacji (wybierz \”Source Packages\”). Musisz także potwierdzić pakiet, w którym zostanie umieszczona klasa.
{/tlo}
{tlo_1}
5. Dodaj klasę JPanel
W zasadzie to już wszystko, czego potrzebujesz,
by zacząć tworzyć kod. Jednak na potrzeby
naszego projektu przeprowadź tę operację jeszcze raz. Tym razem tworząc nową klasę JPanel. W tym celu wybierz opcję \”New/JPanel Form\”. Po pojawieniu się krótkiego kreatora, w ClassName wpisz nazwę klasy tworzonego właśnie interfejsu (np. \”CzatForm\”), a także wybierz docelowy pakiet.
{/tlo}
{tlo_0}
6. Dołącz zewnętrzną bibliotekę
Gdybyś w czasie prac nad projektem musiał dodać zewnętrzną bibliotekę w formacie pliku jar, możesz to zrobić, korzystając z właściwości
projektu. Z drzewa projektów wybierz nazwę projektu i poprzez prawy przycisk myszy, z menu kontaktowego uruchom opcję \”Properties\”. Przejdź do zakładki \”Libraries\”, a następnie korzystając z \”Add JAR/Folder\”, wskaż lokalizację biblioteki. Pamiętaj, że nie zostanie ona automatycznie dołączona
do pliku wynikowego!
{/tlo}
Przygotuj interfejs
Różne oblicza Swinga
Wygląd interfejsu, który widzisz w NetBeans oraz ten, który zobaczysz w aplecie uruchomionym z poziomu przeglądarki WWW, może się różnić. Dzieje się tak, ponieważ Swing ma możliwość przyjmowania różnych wyglądów, w zależności od użytego tzw. \”Look And Feel\”.
Jest to swoista skórka, ale implementująca także zachowanie mające upodobnić interfejs do środowiska i systemu,
w którym pracuje dana aplikacja. Istnieje ponadto możliwość tworzenia własnych \”Look And Feel\”. NetBeans ma domyślnie aktywną skrókę \”Windows Look And Feel\”, natomiast aplet na stronie internetowej korzysta z \”gołej\” skróki swingowej. Stąd różnice w wyglądzie. Można to zmienić, nie będziemy tego jednak szerzej omawiać w tym miejscu – szczegóły znajdziesz
w internecie. Dla naszych celów różnice w wyglądzie nie będą zbyt znaczące.
Jeśli zgodnie ze wskazówkami utworzyłeś już nowy projekt apletowy, czas na przygotowanie interfejsu graficznego. Istnieje wiele możliwości jego tworzenia. Moglibyśmy rysować go \”ręcznie\”, korzystając z różnorodnych narzędzi dostarczanych przez podstawową bibliotekę graficzną Javie, czyli AWT. Ponieważ jednak tworząc aplet, nie jesteśmy ograniczeni bardzo restrykcyjnymi normami pamięci, postawimy na większą funkcjonalność oraz własną wygodę.
Do stworzenia interfejsu zaprzęgniemy bibliotekę Swing, dostarczaną razem z NetBeans IDE.
Na liście projektów wybierz klasę \”CzatForm\” i kliknij dwukrotnie. Zostaniesz automatycznie przeniesiony do graficznego edytora interfejsu, który w NetBeans IDE nosi nazwę Matisse. Możesz w tym miejscu umiejscowić dowolne komponenty z palety, wrzucając je do utworzonego przed momentem Jpanel.
Sam JPanel jest wygodnym elementem do budowania fundamentów apletowych interfejsów, ponieważ w przeciwieństwie do JForm nie dostarcza elementów i zachowań charakterystycznych dla okienek windowsowych, z belką tytułową na czele. Tego rodzaju funkcjonalność nie jest przecież pożądana w aplecie. JPanel to czysty komponent, bez żadnych dodatkowych udziwnień, który umożliwi umiejscowienie wewnątrz siebie innych komponentów.
Tworzenie interfejsu najlepiej rozpocząć od ustalenia rozmiaru stworzonego JPanel. Najprościej uczynisz to, dwukrotnie klikając na dolnej krawędzi JPanel. Zauważ też, że po kliknięciu na dowolny istniejący już w projekcie komponent (np. JPanel), masz dostęp do jego właściwości w oknie \”Properties\”.
W ten sposób ustawisz mnóstwo parametrów związanych z wyglądem oraz zachowaniem komponentu. W zrozumieniu działania każdego z nich pomoże wbudowana pomoc NetBeans (np. dymki pojawiające się po najechaniu na daną opcję), a także informacje dostępne w internecie.
Dla przykładu, odnajdź właściwość \”back-ground\” umożliwiającą zmianę koloru tła komponentu. Możesz wprowadzić wartość ręcznie lub skorzystać z pomocy narzędziowej, klikając w ikonkę obok. Spróbuj zmienić kolor czata na taki, jaki ci odpowiada.
Czas na dodanie kolejnych komponentów. Zauważ, że specyfika czata sprawia, iż nie będzie on zawsze wyglądać tak samo. Wyróżnimy dwie różne części interfejsu:
- część służąca do logowania – w tym miejscu użytkownik zostanie poproszony o podanie swojego loginu (nick)
- część główna – widok właściwy dla czata (to tutaj odbywać się będzie rozmowa)
Taki wieloelementowy interfejs można stworzyć, zmieniając układ elementów (tzw. layout) w komponencie. W tym celu w oknie Inspector kliknij prawym przyciskiem myszy na obiekcie JPanel, a następnie z menu kontekstowego wybierz \”Set Layout / Card Layout\”. \”Card Layout\” różni się od wcześniej używanego \”Free Design\” tym, że poszczególne komponenty układane wewnątrz tak zmodyfikowanego komponentu nadrzędnego będą układane w kolejnych kartach. Co najważniejsze, w danej chwili widoczna jest tylko jedna karta i to w taki sposób, że użytkownik nie jest świadomy istnienia pozostałych (nie jest to więc system kart znany np. z Firefoksa). Będziemy mieli jednak możliwość programowego przełączenia się pomiędzy kolejnymi kartami. W ten sposób zaimplementujesz różniące się od siebie części interfejsu. Będziesz je mógł przywoływać w zależności od potrzeb.
Dodaj teraz do komponentu nowy JPanel i ustaw jego właściwość \”CardName\” (wpisując np. \”Login\”). Dla przejrzystości zmień także nazwę komponentu (\”Change Variable Name\” z menu kontekstowego). Do tak przygotowanego JPanelu możesz wrzucać elementy właściwe dla interfejsu logowania. Gdy skończysz, utwórz w naszym nadrzędnym JPanelu (tym z \”Card Layout\”) kolejny Jpanel i także zmień jego \”CardName\” (wpisując np. \”Main\”). Tutaj umieść komponenty właściwe dla głównego okna. Możesz się przy tym wzorować na przykładzie umieszczonym na ilustracjach.
Uruchamianie aplikacji
Istnieją dwie możliwości przetestowania tego, co już dotychczas stworzyliśmy. Chcąc zobaczyć sam design, możesz wybrać w Matisse opcję \”Preview Design\”.
Aby jednak uruchomić aplet w środowisku zbliżonym do rzeczywistego, możemy skorzystać z narzędzia AppletViewer, emulującego przeglądarkę.
Wcześniej jednak musimy uruchomić nasz interfejs z poziomu głównej klasy apletu. Przejdź do kodu źródłowego klasy \”IMCzat\” i zmodyfikuj metodę {stala}init(){/stala} w sposób następujący:
public void init() {
getContentPane().add(new CzatForm());
}
W ten sposób utworzysz nową instancję klasy {stala}CzatForm(){/stala}, po czym podepniesz ją jako interfejs apletu. Zauważ, że domyślnie stworzony przez NetBeans aplet nie dziedziczy po podstawowej klasie {stala}java.applet.Applet{/stala}, ale po rozszerzonej {stala}javax.swing.JApplet{/stala}. Ważne jest, aby tworząc interfejs w Matisse, nie przeciążać metody {stala}paint(){/stala}. Będzie to sygnał dla wirtualnej maszyny Javy, że wiesz lepiej w jaki sposób przerysowywać nasz interfejs, a przecież po to skorzystałeś z narzędzia graficznego, aby zdać się na wygodę automatyzacji.
Czas na efekt twoich dotychczasowych działań. Edytując klasę \”IMCzat\”, naciśnij skrót Shift+F6 lub wybierz z menu opcję \”Run File\”. Rozpocznie się kompilowanie i budowanie projektu. Jeśli kompilacja przebiegnie bez błędów, po chwili powinieneś zobaczyć swój aplet w emulatorze AppletViewer.
Zaplanuj komunikację z serwerem
W tym miejscu czas na decyzję co do strategii komunikacji z serwerem. Istnieje wiele możliwości
realizacji tego zadania. Aby nie zwiększać niepotrzebnie rozmiaru apletu poprzez dodawanie do projektu zewnętrznych bibliotek, postawiliśmy sobie za cel korzystanie wyłącznie z natywnych rozwiązań dostarczanych w ramach JRE 1.6.
Zdecydowaliśmy, że komunikacja z serwerem będzie przebiegać z zastosowaniem \”gołych\” socketów. Ich obsługa w Javie jest i tak dość wygodna, a nasza transmisja z serwerem HTTP będzie należeć do na tyle prostych, że nie ma konieczności korzystania z bardziej wyrafinowanych rozwiązań. Jednak miłośnikom wysokopoziomowych bibliotek można polecić projekt HttpClient (http://jakarta.apache.org/commons/httpclient/), implementujący wszelkie funkcje, których można byłoby oczekiwać od połączenia z serwerem HTTP.
Sama komunikacja jest jednak tylko metodą przekazywania danych. Pozostaje pytanie, w jaki sposób wymieniać same dane? W związku ze spodziewanymi sporymi strukturami danych, przekazywanymi od serwera do klienta, warto spojrzeć na możliwości, jakie niesie z sobą XML. To uniwersalny format wymiany danych, szerzej opisany w odrębnej ramce. W dalszej części artykułu przekonasz się, że w Javie korzystanie z XML jest naprawdę łatwe!
Aby jednak nie komplikować komunikacji w drugim kierunku – tu przekazywane dane będą mieć bardzo prostą strukturę – zapytania do serwera klient będzie kierować w postaci metody POST. Jej implementacja nie należy do skomplikowanych. Możliwe byłoby także wysyłanie
danych w postaci XML (aby realizować obsługę XML obustronnie), jednak samodzielne kodowanie danych wysyłanych jako uploadowany plik w HTTP nie należy już do kwestii tak prostych i wskazane byłoby skorzystanie ze wspomnianej biblioteki HttpClient. Tego jednak chcielibyśmy uniknąć.
Przed rozpoczęciem dalszych prac upewnij się, że twój firewall nie będzie blokować komunikacji.
Klasa komunikacji z serwerem
Dla przejrzystości wszystkie klasy związane bezpośrednio z obsługą danych będziemy trzymać w osobnym pakiecie o nazwie \”data\”. Możesz go stworzyć w oknie \”Projects\”, wybierając z menu kontekstowego elementu \”Source Packages\” opcję \”New/Java Package\”. W nowym pakiecie utwórz od razu klasę \”Request\” (\”New/Java Class\”), w której zaimportuj poniższe klasy oraz wprowadź dwie właściwości: import java.io.*;
import java.util.*;
import java.net.*;
import javax.xml.bind.*;
public class Request {
private String apiURL = \"http://host.com/adres/api.php\";
private Object answer;
...
}
W klasie Request utwórz metodę {stala}Get(){/stala}. Przyjmie ona jako argument kolekcję tablicową klasy Param, z wykorzystaniem której przekażemy parametry do wysłania za pomocą metody POST do serwera.
public boolean Get(ArrayList param) {
// Deklarujemy konieczne obiekty
URL urlobj;
URLConnection urlc;
InputStream is;
OutputStream os;
...
}
Poniżej przedstawiam kolejne kluczowe elementy tej klasy. Dla zwiększenia przejrzystości usunięte zostały bloki try {} catch() {}, jednak w rzeczywistym programie należy pamiętać o przechwytywaniu
i obsłudze wyjątków.
urlobj = new URL(this.apiURL);
urlc = urlobj.openConnection();
Obiekt klasy URL pozwoli w łatwy sposób obsłużyć
adres URL podany jako String i bezpośrednio utworzyć z niego obiekt klasy URLConnection – obsługującej połączenie.
urlc.setDoOutput (true);
urlc.setDoInput (true);
urlc.setUseCaches (false);
Ważna jest modyfikacja kilku kluczowych dla właściwego funkcjonowania komunikacji ustawień.
os = urlc.getOutputStream();
Czas stworzyć obiekt klasy OutputStream, za którego pośrednictwem wygodnie wyślemy dane na serwer. Teraz nastąpi przygotowanie wysyłki ciągu parametrów POST. Wcześniej jednak utwórz bardzo prostą klasę Param, taką jak poniżej:
public class Param {
private String name;
private String value;
public Param(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return this.name;
}
public String getValue() {
return this.value;
}
}
Znając już tę klasę, możemy ją wykorzystać w dalszej części metody {stala}data.Request.Get(){/stala};
String name;
for (int i = 0; i < param.size(); i++) {
name = URLEncoder.encode(param.get(i).getName(), \"utf8\") + \"=\" + URLEncoder.encode(param.get(i).getValue(), \"utf8\") + \"&\";
os.write(name.getBytes());
}
Powyższa pętla przegląda wszystkie elementy kolekcji param, tworząc na podstawie metod {stala}getName(){/stala} oraz {stala}getValue(){/stala} string z żądaniem POST dla serwera. Zwraca uwagę funkcja URLEncoder.encode(String text, String encoding). Dba ona, aby kodowanie znaków było zgodne ze specyfikacją protokołu HTTP (znaki specjalne muszą być odpowiednio przekonwertowane).
XML w Javie
Istnieją dwa zasadnicze podejścia do tematu obsługi dokumentów XML z poziomu Javy. Pierwsze zakłada, że miłośnicy pełnej kontroli nad programem mogą znaleźć biblioteki ułatwiające zarządzanie dokumentem na niskim poziomie. Oznacza to odwoływanie się do poszczególnych elementów drzewa XML, ich atrybutów i programowe bieganie po strukturze drzewa.
Jednak w czasach, gdy od nowoczesnych zespołów programistycznych oczekuje się przede wszystkim jak najwyższej wydajności, sięga się po rozwiązania dużo wygodniejsze - narzędzia automatycznie konwertujące XML do gotowego do użycia obiektu (i odwrotnie). Całkowicie eliminuje to potrzebę dbania o detale związane z obsługą strukturalną dokumentów XML i doskonale realizuje najważniejszą koncepcję XML, jaką jest prostota i uniwersalność wymiany danych.
Posiadając takie wysokopoziomowe rozwiązania na interfejsach obu systemów wymieniających dane pomiędzy sobą, można umownie przyjąć, że XML staje się jedynie nośnikiem wymiany całych obiektów. Tym elementem, którym nie musisz się szczególnie przejmować. I o to chodzi.
Wprowadzono nawet specjalistyczną terminologię określającą te procesy:
- marshalling (zwany też \"Java to XML\") - dotyczy sytuacji, w której obiekt jest zamieniany na XML,
- unmarshalling (\"XML to Java\") - dotyczy sytuacji odwrotnej, czyli konstrukcji obiektu z dokumentu XML.
Należy jednak pamiętać, że pojęcia te nie odnoszą się wyłącznie do Javy oraz XML.
Do wyboru masz wiele bibliotek, które dostarczają wysokopoziomową obsługę XML. Godna polecenia jest szybka biblioteka XStream (http://xstream.codehaus.org/). Jest ona wygodna szczególnie do prostych zastosowań. Jednak my chcemy trzymać się założenia o niekorzystaniu z zewnętrznych bibliotek by uchronić się przed zwiększaniem wynikowego pliku jar z czatem. Na szczęście JDK 1.6 także zawiera doskonały system do (un)marshallingu - Java Architecture for XML Binding (w skrócie JAXB).
Utwórz plik jaxb.index
Aby JAXB automatycznie konwertował przychodzące klasy, konieczne jest utworzenie pliku jaxb.index w folderze pakietu. Wskazuje on, które z klas zostały dopuszczone do importu.
{tlo_1}
1. Krok pierwszy
Przejdź do okna „Files”. Jeśli go nie widzisz, można je wywołać poprzez menu „Window/Files” lub skrót Ctrl+2. Przejdź do katalogu „build\\
classes\\data”. Z menu kontekstowego wybierz „New/File Folder”. W kreatorze wskaż kategorię „Other”, a z niej opcję „Empty File”. W następnym kroku nadaj mu nazwę „jaxb.index”.
{/tlo}
{tlo_0}
2. Krok drugi
Zostaniesz przeniesiony do okna edycji nowo utworzonego pliku. Dodaj do niego w kolejnych liniach nazwy klas, na których import wydajesz zezwolenie. Każdą linię zakończ enterem.
{/tlo}{tlo_1}
3. Zapisz plik
W twoim projekcie powinno już działać importowanie wskazanych klas.
{/tlo}
Implementacja XML
JAXB potrafi wiele, łącznie z obsługą bardzo rozbudowanych dokumentów XML, możliwością tworzenia własnych {stala}classLoaderów{/stala} i obsługą XML Scheme. Na potrzeby tworzonego czata wystarczy jednak najprostsza implementacja. Będziemy realizowali
importowanie odpowiedzi XML otrzymanej po próbie zalogowania - zgodnej ze wzorcem przedstawionym w ramce.
Na początek utwórz w pakiecie \"data\" klasę, do instancji której chcesz importować dane:
public class AnswerLogin {
public boolean loginSuccess = false;
public String errorMsg = \"\";
public String token = \"\";
public ArrayList users;
public AnswerLogin() {
users = new ArrayList();
}
...
}
Atrybuty tej klasy nie mogą być prywatne - w najprostszej rozpatrywanej wersji uniemożliwi to JAXB zaimportowanie danych. Jednak pomimo to, zgodnie z ideą enkapsulacji warto dodatkowo utworzyć metody służące do pobierania poszczególnych
atrybutów.
Oto klasa User, która użyta została w kolekcji powyżej:
public class User {
public String nick = \"\";
public boolean moderator = false;
}
Pozostaje już tylko rozszerzenie metody {stala}Get(){/stala} w klasie Request o obsługę unmarshallingu (konieczne jest zaimportowanie javax.xml.bind.*). Dopisz poniższy fragment:
JAXBContext jc = JAXBContext.newInstance( \"data\" );
Unmarshaller u = jc.createUnmarshaller();
this.answer = u.unmarshal(is);
is.close();
Tworzymy nowy obiekt. Podczas tworzenia instancji klasy JAXBContext przekazujemy konstruktorowi informację, że będziemy bindować klasy z pakietu \"data\". Następnie przygotowujemy obiekt służący do celów unmarshallingu i rozpoczynamy
przetwarzanie danych za pomocą metody unmarshal().
Jest ona dość przeciążona, dostarczając rozmaite sposoby na dostarczenie dokumentu XML. W tym przypadku podajemy bezpośrednio InputStream - Unmarshaller zadba więc we własnym zakresie o odczytanie odpowiedzi ze strumienia danych otrzymanego z serwera.
Gdybyś chciał sparsować XML znajdujący się w Stringu, będziesz musiał uprzednio stworzyć obiekt klasy StreamReader. Istnieje też możliwość parsowania danych przez podanie jedynie nazwy pliku lub adresu URL źródła. Zauważ, że atrybutowi \"answer\" przypisaliśmy wcześniej typ Object. Celem tego zabiegu jest zachowanie uniwersalności klasy Request. Przy korzystaniu z niej trzeba będzie jednak przeprowadzić rzutowanie na właściwy typ danych.
Poniżej znajduje się przykład wykorzystania w całości klasy Request w celu komunikacji z serwerem. Jest to realizacja mechanizmu logowania, poprzez obsługę zdarzenia na przycisku obsługującym inicjującym ten proces:
private void jButtonZalogujMouseClicked-(java.awt.event.MouseEvent evt) {
Request req = new Request();
ArrayList param = new ArrayList();
param.add(new Param(\"action\", \"login\"));
// Pobieranie nicka z pola tekstowego
param.add(new Param(\"nick\", this.jTextFieldNick.getText()));
// Pobieranie i przetwarzanie danych
req.Get(param);
// Rzutowanie przetworzonego wyniku zapytania na typ AnswerLogin
AnswerLogin answer = (AnswerLogin)req.getAnswer();
if (answer.getLoginSuccess()) {
config.setToken(answer.getToken());
config.setNick(this.jTextFieldNick.getText());
ArrayList users = answer.getUsers();
actualizeUsersList(users);
((CardLayout)getLayout()).show(this, \"Main\");
} else {
this.jLabelLoginMsg.setText(answer.getErrorMsg());
}
}
Do powyższego kodu należałoby dodać jeszcze szeroką obsługę błędów. Zwróć uwagę na odwołanie do metody config, będącej instancją klasy {stala}data.Config{/stala}. To sposób na to, aby w jednym miejscu przechowywać istotne dla funkcjonowania programu parametry i w razie potrzeby przekazywać je do innych obiektów. Przedstawienie kodu tej klasy jednak pominiemy. Jest on bardzo prosty i można go znaleźć w źródłach projektu na płycie CD.
Zwraca także uwagę zastosowana konstrukcja:
((CardLayout)getLayout()).show(this, \"Main\");
Pozwala ona zmienić kartę interfejsu na prezentującą właściwy wygląd czata.
Wątki w Javie
Po przejściu do widoku czata pozostaje już tylko do rozwiązania problem synchronizacji bieżących danych z serwerem. Aplet musi regularnie ściągać z serwera zmiany w oknie rozmowy, a także w oknie z kontaktami. Tu również możesz wykorzystać XML i opracowaną już klasę Request. Jednak aby całość działała zgodnie z oczekiwaniami, potrzebujemy skorzystać z wątków, dzięki którym osiągniemy konieczną współbieżność i równoległe działanie interfejsu oraz mechanizmu synchronizacji.
Tu dobra wiadomość. Obsługa wątków w Javie jest bardzo wygodna, albowiem wszelkimi systemowymi problemami związanymi z tworzeniem i zarządzaniem wątkami zajmie się klasa Thread. Aby w najprostszy sposób przygotować własny wątek, musisz stworzyć klasę dziedziczącą po Thread i zaimplementować metodę run() - w której znajdzie się właściwy kod programu. Następnie tworzysz obiekt, będący intancją tej klasy i wywołujesz
metodę start(). To wszystko!
Oto najważniejsze metody udostępniane przez klasę Thread:
- {stala}start(){/stala} - inicjuje wątek, po czym wywołuje metodę {stala}run(){/stala}, w której znajduje się kod programisty. Przerwanie wykonywania metody {stala}run(){/stala} oznacza zakończenie wątku, dlatego wątki, które mają wykonywać zadania w sposób cykliczny, należy zapętlić, wywołując przy każdym przebiegu pętli sleep() z parametrem odpowiadającym czasowi wstrzymania wątku w milisekundach,
- {stala}stop(){/stala} - kończy wątek i zwalnia pamięć,
- {stala}suspend(){/stala} - wstrzymanie działania wątku,
- {stala}resume(){/stala} - powoduje wznowienie działania wątku po przerwaniu metodą {stala}suspend(){/stala},
- {stala}yield(){/stala} - również wstrzymuje działanie wątku, po czym następuje wymuszone przełączenie kontekstu (przydzielenie czasu procesora) do następnego wątku.
Dodatkowo istnieją także metody {stala}setPriority(){/stala} oraz {stala}getPriority(){/stala}, służące do zarządzania priorytetem
wątku, jak również dodatkowe mechanizmy do synchronizacji pomiędzy poszczególnymi wątkami.
Nie będziemy jednak tutaj ich omawiać.
Implementacja wątku
Aby zaimplementować pobieranie danych o nowych wypowiedziach z serwera, stworzymy klasę Synchronizer. Idea jest taka, aby wykorzystując
stworzoną już klasę Request, wysłać zapytanie o nowe dane. W odpowiedzi serwer odeśle dokument XML zgodny ze wzorcem.
Zostaną w nim przesłane:
- wszystkie nowe wypowiedzi, które pojawiły się od czasu poprzedniego zapytania,
- lista uczestników rozmowy, ale tylko jeśli nastąpiły
w niej zmiany.
Przykładowa implementacja klasy Synchronizer:
public class Synchronizer extends Thread {
private Configconfig;
private CzatForm gui;
public Synchronizer(CzatForm gui, Configconfig){
this.gui = gui;
this.config=config;
}
public void run() {
while (true) {
Request req = new Request();
ArrayList param = new ArrayList();
param.add(new Param(\"action\", \"synchronize\"));
param.add(new Param(\"nick\", config.getNick()));
param.add(new Param(\"token\", config.getToken()));
req.Get(param);
AnswerSynchronize answer = (AnswerSynchronize)req.getAnswer();
if (!answer.isAuthorized()) {
System.out.println(answer.getErrorMsg());
} else {
ArrayList messages = answer.getMessages();
ArrayList users = answer.getUsers();
if (users.size() > 0) {
gui.actualizeUsersList(users);
}
for (int i = 0; i < messages.size(); i++) {
this.gui.addMessage(messages.get(i).text + \"\n\");
}
}
sleep(1000);
}
}
}
Zauważ, że w konstruktorze metody przekazywana jest referencja do dwóch obiektów: interfejsu użytkownika, abyśmy bezpośrednio z klasy Synchronizer mogli inicjować modyfikację zawartości poszczególnych komponentów, a także do obiektu z danymi konfiguracyjnymi. Po odsłużeniu logowania został w tym obiekcie zapisany token, który jest potrzebny do autoryzacji przy każdym kolejnym zapytaniu.
Należałoby w tej klasie dodać obsługę błędów - w razie gdyby przez określoną ilość razy nie udało się nawiązać połączenia z serwerem, powinniśmy uznać połączenie za zerwane. A co za tym idzie, wyświetlić użytkownikowi ekran logowania wraz z komunikatem o błędzie i zakończyć działanie wątku.
Sama obsługa zapytania przebiega analogicznie do logowania. Dane są rzutowane do typu AnswerSynchronize.
Oto ta klasa:
public class AnswerSynchronize {
public boolean authorized = false;
public String errorMsg = \"\";
public ArrayList messages;
public ArrayList users;
public AnswerSynchronize() {
messages = new ArrayList();
users = new ArrayList();
}
...
}
W klasie {stala}Synchronize(){/stala} znalazły się odwołania do dwóch publicznych metod klasy CzatForm, których zadaniem jest zaktualizować interfejs o nowe dane.
Pierwsza z nich służy do dodania pojedynczej wypowiedzi lub wiadomości systemowej do okna rozmowy:
public void addMessage(String msg) {
this.jTextAreaWiadomosci.append(msg);
this.jTextAreaWiadomosci.setCaretPosition(
this.jTextAreaWiadomosci.getText().length());
}
Kolejna metoda powoduje aktualizację listy obecnych na czacie użytkowników.
public void actualizeUsersList(ArrayList users) {
DefaultListModel model = new DefaultListModel();
model.setSize(0);
for (int i = 0; i < users.size(); i++) {
model.add(model.size(), users.get(i).getNick());
jListObecni.setModel(model);
}
}
Na koniec pozostaje już tylko uruchomienie wątku. Odpowiednią instrukcję powinieneś dopisać
do metody obsługującej przycisk do logowania - w miejscu otrzymania pozytywnej autoryzacji.
Synchronizer synch = new Synchronizer(this, this.config);
synch.start();
Ponadto dobrze byłoby udostępnić obiekt synch jako metodę prywatną w klasie CzatForm.
Na zakończenie
Dodanie obsługi wysyłania wypowiedzi to już prosta sprawa. Wystarczy za pomocą Request wysłać zapytanie i jako jeden z parametrów POST przesłać wypowiedź. Nie oczekujemy w tym przypadku żadnej odpowiedzi, choć możesz dodatkowo
zaimplementować szerszą obsługę błędów po stronie serwera. Akcję taką można podczepić do przycisku wysyłającego oraz jako zdarzenie po naciśnięciu klawisza Enter.
To już wszystko, miłego czatowania!
Restrykcje bezpiecznego środowiska
Jeśli w tym momencie skompilujesz i zbudujesz aplet, a następnie spróbujesz go uruchomić, spotka cię przykra niespodzianka. Przy próbie połączenia się z serwerem zostanie zwrócony wyjątek systemu bezpieczeństwa. Dzieje się tak, ponieważ aplety - w przeciwieństwie do zwykłych aplikacji Java - pracują domyślnie w specjalnym bezpiecznym środowisku, które chroni aplikacje webowe przed wykonywaniem potencjalnie niebezpiecznych operacji na komputerze użytkownika.
Oto kilka najważniejszych restrykcji związanych z bezpiecznym środowiskiem:
- Nie jest możliwe uruchamianie innych programów na komputerze użytkownika. Jeśli programy te są konieczne dla przetworzenia odpowiednich danych, aplet może połączyć się z serwerem i uruchomić odpowiednik tego programu zdalnie.
- Nie dojdzie także do skutku żadne połączenie z serwerem innym niż macierzysty serwer, z którego został pobrany aplet (na który wskazuje classpath w dokumencie HTML). Ponadto należy przy połączeniu podać dokładnie tę samą nazwę domenową hosta wraz z subdomenami - nie zadziała połączenie z inną domeną lub subdomeną, nawet jeśli wskazują one na ten sam adres IP.
- Aplet nie ma dostępu do systemu plików po stronie użytkownika. Nie może wykonywać operacji
dyskowych, w tym odczytu i zapisu danych. Dlatego jeśli posiadamy dane, które należałoby przechować, lepszym pomysłem jest przekazanie ich do przechowania serwerowi. - Istnieją spore ograniczenia w dostępie do informacji systemowych, które mają zapobiec zbieraniu danych o systemie i jego użytkownikach
przez aplet. Otrzymujemy do dyspozycji tylko podstawowe informacje o środowisku, w którym pracuje wirtualna maszyna Javy. - Funkcjonują również pewne ograniczenia w obsłudze
klas - system weryfikacji uprawnień jest bardziej restrykcyjny.
Dobrze jest więc pisać aplety, które trzymają się wskazanych norm. Na szczęście nie oznacza to, że gdy aplet narusza system bezpieczeństwa, to nie ma ratunku. Istnieje możliwość podpisania pakietu jar z apletem za pomocą certyfikatu. Zakłada się, że pakiet podpisany to pakiet, za który ktoś bierze odpowiedzialność, a co za tym idzie - dodatkowe zabezpieczenia są zdejmowane. Aby jednak całość działała w pełni bezproblemowo, konieczne będzie posiadanie certyfikatu wydanego przez autoryzowanego wystawcę. A nie jest to tania usługa.
Istnieje alternatywa w postaci wygenerowania własnego certyfikatu. Jest to wprawdzie rozwiązanie
darmowe, ale mniej praktyczne, gdyż będzie powodować wyskakiwanie alertu zabezpieczeń w czasie połączenia użytkownika z apletem.
Podpisywanie pakietów
Aby wyjść z ograniczeń bezpiecznego środowiska, będzie konieczne podpisanie twojego apletu. W tym celu utworzymy twój indywidualny certyfikat, a następnie dodamy go do właściwego pliku JAR.
W tym celu przejdź w linii poleceń do katalogu bin w swoim środowisku Javy (np. \"C:\\Program Files\\java\\jdk1.6.0\\bin\"), a następnie wydaj polecenie:
public class User {
public String nick = \"\";
public boolean moderator = false;
}
Spowoduje to wygenerowanie klucza o nazwie \"keyname\". Klucz będzie przechowywany w domyślnym repozytorium. Możemy zmienić jego lokalizację poprzez skorzystanie z przełącznika \"keystore\". Przy pierwszym utworzeniu klucza zostaniesz poproszony o hasło do repozytorium (keystore). Zapamiętaj je, ponieważ będzie ci potrzebne każdorazowo do generowania kolejnych kluczy w tym repozytorium. Następnie będziesz już proszony o podanie odpowiedzi na pytania właściwe dla tworzonego certyfikatu.
Będą to kolejno:
- imię i nazwisko
- nazwa działu
- nazwa firmy (organizacji)
- miasto
- województwo
- dwuliterowy skrót kraju (np. PL)
Podanie tych danych nie jest obowiązkowe. Dobrym
zwyczajem jest podawanie informacji zgodnych
z rzeczywistością. Jeśli dane będą wyglądać na fałszywe, internauci mogą nawet zrezygnować z uruchomienia apletu! Na koniec podaj jeszcze hasło do tworzonego właśnie klucza. Będzie ci ono potrzebne każdorazowo do podpisywania apletów.
Twoje repozytorium jest domyślnie przechowywane
w katalogu domowym użytkownika (np. \"C:\\Documents and Settings\\
Listę aktualnych certyfikatów wyświetlisz poprzez:
keytool -list
Podpisania pliku jar z apletem dokonasz za pomocą narzędzia jarsigner:
jarsigner
Oznaczać to będzie podpisanie archiwum o ścieżce \"jarname\", przy korzystajaniu z klucza \"keyname\" z domyślnego repozytorium. Po zbudowaniu w NetBeans archiwum jar projektu znajduje się w katalogu głównym projektu (wybranym podczas jego tworzenia), w podfolderze o nazwie dist. Aby wybrać alternatywne repozytorium, ponownie skorzystaj z przełącznika \"keystore\".
W czasie podpisywania archiwum zostaniesz poproszony o hasło do klucza.
Podpisany aplet możesz uruchomić ręcznie poprzez appletviewer:
appletviewer
Przy każdorazowym uruchomieniu w przeglądarce pojawi się ostrzeżenie, że certyfikat jest nieautoryzowany.
Jedyny sposób na pozbycie się tej niedogodności to zakup certyfikatów wystawionych przez autoryzowanych wystawców (np. VeriSign czy Thawte).Jest to jednak inwestycja kosztowna. Pamiętaj także, że samodzielnie wygenerowany klucz jest ważny domyślnie przez 6 miesięcy. Po tym czasie należy wygenerować nowy klucz i ponownie podpisać aplet.