Pokazywanie postów oznaczonych etykietą Eclipse. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Eclipse. Pokaż wszystkie posty

czwartek, 27 października 2011

Wtyczki do Eclipse’a wspomagające naukę TDD


Aby zacząć naukę TDD (Test-driven development) wiele nie trzeba. Wystarczy ulubione środowisko programistyczne IDE (Idea / NetBeans / Eclipse) i narzędzie do testowania (JUnit / TestNG / xUnit). I teraz albo czytamy mądre książki (polecam "TDD by Example" Kenta Becka) jednocześnie kodując, lub szukamy odpowiednich informacji w necie, albo szukamy kogoś doświadczonego, aby pokodzić w parach. No i można już uskuteczniać TDD. Tylko skąd wiadomo, że robimy to dobrze? Czy pamiętamy o wszystkich krokach? Może czasem zapominamy zrefaktoryzować kod? Czy rzeczywiście mamy rytm red, green, refactor?

W celu śledzenia i późniejszej oceny jak nam poszło TDD warto zainstalować w Eclipse’ie plugin Pulse. Kiedy pierwszy raz o nim usłyszałem (i tu podziękowania dla Bartka), pomyślałem sobie: po co komu plugin do TDD? Pewnie to jakaś ściema lub zbędny bajer. I rzeczywiście jest to bajer, ale całkiem użyteczny.

Plugin Pulse tworzy wykres naszej aktywności, zaznaczając kiedy nasze testy fail’owały, kiedy były zielone i kiedy robiliśmy refaktoring. Na stronie plugin’a można zobaczyć ciekawy filmik prezentacyjny możliwości.

W celu przetestowania plugin’u wziąłem się wiec za PrimeFactorsKata, gdyż tej Katy wcześniej nie robiłem. Efekt poniżej:



Początkowo napisałem testy dla jedynki i dwójki (pierwsze dwie czerwone kropki). Gdy napisałem test dla 4ki to się skapnąłem, że skoro jest to rozkład na czynniki pierwsze, to powinienem zwracać listę wartości, a nie pojedynczą wartość. Trzeba było wiec sygnaturę metody zmienić.

Następnie szły kolejne testy, czasem w miarę potrzeby jakiś refaktoring. Jako refaktoring jest rozpoznawane użycie któregoś z automatycznych mechanizmów, np. Extract Method (Alt + Shift + M), czy Rename (Alt + Shift + R). Niestety, jak robimy to ręcznie, to nie jest to wykrywane przez plugin. Ale w sumie niedziwne, gdyż wówczas każda edycja kodu by generowała niebieskie punkty na wykresie.

Gdy już nie chciało mi się już szukać (ani liczyć) kolejnych testowych wartości, które bym dodał do testów, przerwałem katę. Nie napisałem w pełni funkcjonalnej metody, gdyż pewnie dla jakiś kolejnych dużych wartości mogło by to nie działać. Ale nie o to chodzi w ćwiczeniach typu CodeKata by dojść do końca (bez skojarzeń;) ), a o poznanie jakiejś nowej techniki programistycznej, lub wyrobieniu sobie dobrych nawyków.

Wracając jeszcze z Warsjawy, co opisywałem we wpisie: Warsjawa 2011 już za nami, jechałem pociągiem i wziąłem się za BowlingGameKata. Tutaj już miałem sporo czasu na implementację. Nie było to moje pierwsze podejście do tego problemu, więc już jakoś w podświadomości było wiadomo, jak to rozwiązać. Ćwiczyłem dodatkowo tworzenie notatek, na temat tego, jakie testy trzeba jeszcze napisać i jakie są już napisane. Wykres z wtyczki Pulse poniżej:



Zacząłem równo o 16.00 i początkowo pisałem proste testy dla następujących przypadków (liczby to ilości kręgli zbitych w danym rzucie):
  • 0 - pierwszy zielony test.
  • 1- pierwszy czerwony i już po nim jakiś refaktoring.
  • 1, 3 - drugi czerwony test.
  • 1, 3, 4, 5 - któryś z zielonych testów po 16.10.
  • Pierwszy bonus: Spare – zacząłem o 16.14 i wtedy postanowiłem zrobić większy refaktoring, tj. wprowadzić design obiektowy. Skończyłem go o 16.30 i 5 minut później już mi śmigał pierwszy test dla Spare’a. Do 16.40 jeszcze coś refaktoryzowałem, co nawed plugin zarejestrował.
  • Double Spare  - zacząłem chwile po 16.40 i trochę musiałem podebugować do 16.55. Ostatecznie okazało się, że w teście źle przeliczyłem oczekiwaną wartość. 
  • Strike – o 17.03 skończyłem implementację drugiego bonusu.
  • Double Strike – skończyłem implementację o 17.33. W międzyczasie musiałem zmienić przedział w pociągu, gdyż skończył się prąd w gniazdku :P
  • Spare, Strike – skończyłem o 17.39 (miałem błąd w danych testowych)
  • Strike, Spare– skończyłem o 17.41(j.w.). Następnie chciałem napisać implementację Full Strike (czyli ciągle rzucamy 10 za pierwszym razem). Ale po tym jak test zfail’ował, dopisałem do mojej listy TODO jeszcze inne, prostsze testy dotyczące końcówki gry (ostatnia ramka) i zacząłem od nich implementację.
  • Spare w ostatniej ramce – skończyłem o 17.54.
  • Strike w ostatniej ramce – skończyłem o 18.00.
  • Double Strike w przedostatniej (i ostatniej) ramce skończyłem o 18.03.
  • Full Strike (ciągle rzucamy 10) – działał od razu po odkomentowaniu testu, gdyż wcześniejszymi testami zapewniłem odpowiednie działanie ostatniej ramki. Na koniec jeszcze usunąłem duplikacje z kodu ekstrachując odpowiednie metody.

Pierwszy raz doprowadziłem tą Katę do końca i jestem z niej zadowolony. Teraz moją implementację trzeba porównać z rozwiązaniem wujka Boba.

Kolejnym pluginem udostępnianym przez @iamhappyprog na stronie www.happyprog.com jest TDGotchi. Dzięki kolejnym wynikom testów zbieramy punkty i rozwiajamy nasze „zwierzątko”.

Podczas PrimeFactorsKata udało mi się uzbierać 5 punktów, przy bowlingu zapomniałem spojrzeć.



Przy zamknięciu i otwarciu widoku, punkty się resetują. możemy jeszcze obserwować nasze zwierzątko / ikonke jak się zmienia, gdy zminimalizujemy widok.

Ostatnim plugin’em tego samego autora jest PairHero. Wspiera on Pomodoro Technique i ping pong programing.  Za przechodzące testy i refaktoring dostajemy punkty, a za szybkie zmiany kierownik – pilot można dodatkowo pomnożyć te punkty. Fajne jeśli akurat ćwiczmy Ping Pong'a, szybkie zmiany, lub po prostu programowanie w parach.Przykładowy screenshot poniżej.



Z przedstawionych plugin’ów najbardziej polecam pierwszy, gdyż wg. Mnie niesie największą wartość i pozwala na dobrą retrospekcje jak i porównanie wykresu z innymi developerami. Pozwala również nam zauważyć, jak często wykonujemy automatyczny refaktoring, ile czasu potrzebujemy na przejście red -> green itp.

A jak wygląda Twój puls?

sobota, 28 maja 2011

Podświetlanie wystąpień zmiennych w kodzie w Eclipse

Jakiś czas temu mój Eclipse zwariował i nie chciał podświetlać wszystkich wystąpień zmiennych w kodzie Javy, gdy na jakiejś zmiennej był ustawiony kursor. Moja praca stała się bardziej uciążliwa, gdyż trudniej mi było przeczytać / zrozumieć nie swój kod.

Jako że nie znam dokładnie środowiska, chwilę się naszukałem co może być nie tak. Wszystko można zmienić w menu Window -> Preferences -> Java -> Editor -> Mark Occurrences.



Warto mieć tu wszystko włączone, aby widzieć co gdzie w kodzie występuje. Przykład poniżej:



Kursora bezpośrednio nie widać, ale proszę mi wierzyć, że jest on ustawiony na obiekt separator w linii 14. Dzięki temu w linii 16 widzimy podświetlenie tej samej zmiennej.

Podsumowując powiem jeszcze, ze pisząc ten post zrozumiałem, dlaczego w pewnym momencie to podświetlanie mi zniknęło. Otóż chcą sobie przypomnieć skrót klawiaturowy do wyświetlania Outline’a aktualnej klasy w oknie z kodem, omyłkowo wcisnąłem Alt + Shift + O, co jak się okazało wyłącza podświetlanie tych zmiennych. Natomiast skrót do wyświetlania Outline’a klasy (czyli zawartości) to Ctrl + O. Podwójne wciśnięcie tej kombinacji, to wyświetlenie całej zawartości klasy, wraz z klasami bazowymi.

Tyle na dziś.

Żródło:
Eclipse: Mark Occurrences for Quick Visual Help

czwartek, 28 kwietnia 2011

Asocjacje plików w Eclipse

Mój poprzedni wpis Podświetlanie zmian kodu w Eclipsie, w którym pokazałem pewną wskazówkę ułatwiająca naszą codzienną pracę, był bardzo często czytany. Z portalu dworld.pl, który blogrolluje (nie wiem na ile to dobre słowo) ciekawe okołojavowe wpisy z całej polskiej blogosfery, miałem ponad 100 przekierowań. Byłem, więc w czołówce najpopularniejszych wpisów z ostatniego miesiąca. Podbudowany tym wydarzeniem postanowiłem pójść za ciosem i przedstawić kolejną przydatną (może nie koniecznie ciekawą) opcję ze środowiska Eclipse. Pomysł na to podrzucił mi kolega z pracy - Sebastian - któremu za pomoc z tego miejsca dziękuję. Ułatwiło to trochę moją codzienną pracę.

Często się zdarza, ze w naszej pracy mamy nie tylko do czynienia z kodem, ale także z grafiką. Nawet proste strony internetowe zawierają zawsze jakąś grafikę. Najłatwiej ją nam wyszukać i otworzyć do edycji z pomocą opcji Open Resource (Ctrl + Shift + R). Widzimy wtedy okienko jak poniżej:


Niestety chcąc dokonać edycji, nie zawsze chcemy użyć do tego domyślnej aplikacji z systemu operacyjnego. Wybranie opcji Open With i znalezienie żądanego programu, nie powoduje, że nasz wybór będzie zapamiętany. A szkoda.


Chcąc zmienić domyślny edytor, np. grafiki, należy wejść w menu Window -> Preferences -> General -> Editors -> File Associacions. Dodajemy interesujące nas rozszerzenie i aplikację, którą chcemy urzywać do edycji.


W ramach ciekawostki powiem jeszcze, że MS Paint jest umieszczony w katalogu Windows\System32, gdyby ktoś szukał. Ciekawe położenie. Wygląda na to, że narzędzie to jest bardzo ważne w systemie i na pewno jest mocno używane przez resztę Windowsowych kluczowych funkcjonalności. Kojarzę, że we wcześniejszych wersjach, było chyba trochę lepiej, tzn. gdzieś indziej leżał ten program. Degradacja architektury?

wtorek, 5 kwietnia 2011

Podświetlanie zmian kodu w Eclipsie

Jeśli chodzi o preferowane środowiska programistyczne, to nie jestem ani zagorzałym fanem Eclipse, ani NetBeans'a. Każde z nich ma swoje wady i zalety, każde sprawdza się inaczej w różnych zadaniach. Muszę w końcu na poważnie usiąść do poznawania IntelliJ IDEA i może wtedy sprawa ulubionego IDE się wyjaśni. Tymczasem chcę opisać pewną funkcjonalność, do której się przyzwyczaiłem w NetBeans'ie, a której mi trochę brakowało w Eclipsie. Pokażę również, jak można ją aktywować w tym drugim środowisku.

Funkcjonalność o której wspomniałem to zaznaczanie fragmentu kodu, który został zmodyfikowany (lub dodany). W NetBeans'ie wyglada to tak:


Po lewej stronie, między numeracją linii a kodem, widzimy dwa paski: niebieski i zielony. W Eclipsie trochę mi tego brakowało, ale ostatnio poszukując rozwiązania czegoś tam, przypadkiem trafiłem na potrzebną mi opcję. Podświetlanie zmian w kodzie można włączyć w Preferences w Quick Diff. Najlepiej śledzić zmiany w stosunku do repozytorium, w moim przypadku SVN'a.


Przyda się jeszcze tuning kolorów. Te z NetBeanas'a całkiem mi pasują. Zmiany są kolorowane na (RGB) 160 200 255 (#A0C8FF), a dodane linijki na (RGB) 150 255 150 (#96FF96). Wprowadzając te kolory do Eclipse, okazuje się, że są one trochę inne niż w NetBeans'ie (słabiej widoczne, zlewają się z szarym paskiem).


Nie wiem czemu, ale środowisko (Eclipse Galilieo) trochę inaczej wyświetla / interpretuje te kolory. Po kilku eksperymentach z wartościami RGB, doszedłem do zbliżonych kolorów. Zmiany należy ustawić na 18 119 255 (#1277FF), a dodane fragmenty na 0 255 0 (#00FF00). Efekt jest następujacy.


No już lepiej :) Co prawda, już na tym prostym przykładzie widać, że te dwa środowiska, w różny sposób interpretują wprowadzane zmiany, czasami trochę błędnie wyświetlając, co zostało zmienione, a co dodane. No ale trudno. Ważne, że teraz widać, w której części pliku się coś zmieniło i czy dane zmiany są nam dalej potrzebne. Mi to pomaga przeglądając zmiany przed commitem.

wtorek, 5 października 2010

Mechanizm RMI (Remote Method Invocation) w praktyce cz. 3

Dotychczas w poprzednich artykułach (część 1 i część 2) przedstawiłem jak zbudować prostą aplikację przy użyciu RMI, gdzie klient wywołuje metody serwera i serwer wywoluje metody klienta. Dotychczas wszystko uruchamialiśmy na jednej maszynie (localhost). Spróbujmy czy zadziała wcześniej napisana przez nas aplikacja przy próbie uruchomienia na dwóch komputerach.

Moje komputery to laptop o adresie 192.168.0.2 i PC 192.168.0.1. Na PCie odpalę serwer, a na laptopie klienta. Kopiujemy projekty na odpowiednie maszyny i pierwsze co to należy zmienić ścieżki dostępowe w skryptach uruchomieniowych. Bez tego otrzymamy zapewne jeden z błędów opisywanych w poprzednich artykułach.

No dobra ruszamy. Odpalamy serwer na PCie - wszystko działa (bo na nim przygotowywalem poprzednie artykuły). Odpalamy klienta na laptopie i:

javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: 192.168.0.2; nested exception is:
        java.net.ConnectException: Connection refused: connect]


Czyli aplikacja kliencka próbuje się połączyć z serwerem działającym na laptopie. Trzeba więc zmodyfikować adres pod który aplikacja próbuje się łączyć. Otwieramy więc MyClientMain.java w Eclipse i modyfikujemy linię definiującą adres URL:

String url = "rmi://192.168.0.1";

Uruchamiamy ponownie klienta i znów ten sam błąd. Jednak to nie tu szukaliśmy. Przyglądajac się bardziej stosowi wywołań, można zauważyć że wyjątek został rzucony przez MyClientMain.java:19. Zobaczmy co się tam dzieje:

context.bind("rmi:MyClientObject", myClientImpl);

Aaaa, czyli aplikacja kliencka nie może zarejestrować obiektu, gdyż rmiregistry zostało uruchomione na innej maszynie. Poświęciłem sporo czasu aby rozwikłać ten problem. Z tego co wyczytałem nie można zarejestrować obiektu, który ma być zdalny na maszynie innej niż localhost. (Moge się mylić w tym momencie, wiec jak ktoś wie lepiej niech pisze!) Po co więc ten argument w metodzie bind() do podania adresu? A no po to, że na jednej maszynie możemy mieć uruchomionych kilka rmiregistry działających na innych portach.

Wracając do aplikacji to jeśli chcemy klientem wywoływać metody serwera i serwer chce wywoływać metody klienta i ma to działać na osobnych maszynach, to musimy troche przerobić aplikację. Przede wszystkim klient musi posiadać uruchomienione własne rmiregistry, gdzie będzie wystawiał swoje zdalne obiekty. Dodatkowo serwer musi jakoś poznać IP z jakim ma się połaczyć, więc warto go mu przekazać (np. jako wywołanie zdalnej metody).

Ok najpierw zmiany u klienta. W skrypcie uruchomieniowym programu klienta należy dopisac linie uruchamiającą rmiregistry zanim uruchomimy właściwą aplikację:

start rmiregistry

Następnie w klasie MyClientMain należy zmodyfikować wywołanie zdalnej metody:

String clientAdres = InetAddress.getLocalHost().getHostAddress();
String str = myRemoteObject.getDescription(clientAdres);

Teraz argument przekazywany do metody będziemy traktować jak adres IP klienta, aby serwer wiedział z kim się połaczyć.

Teraz czas na serwer. Zmieniamy wywołanie metody szukającej zdalny obiekt klienta na następujący kod:

MyClientInt myClientInt = (MyClientInt) 
        context2.lookup("rmi://" + text + "/MyClientObject");

gdzie text jest argumentem przekazywanym do metody.

Uruchamiamy i wszystko działa poprawnie :) Możemy cieszyc się aplikacją RMI działającą na osobnych maszynach. Tym samym kończę cykl artykułów o RMI (chyba że jeszcze coś ciekawego przyjdzie mi do głowy, lub pojawi się zapotrzebowanie na rozwiązanie jakiegoś problemu). Wszelkie uwagi mile widziane.

Więcej informacji:
Mechanizm RMI (Remote Method Invocation) w praktyce cz. 1
Mechanizm RMI (Remote Method Invocation) w praktyce cz. 2

niedziela, 3 października 2010

Mechanizm RMI (Remote Method Invocation) w praktyce cz. 2

W poprzedniej części tego artykułu zatytułowanej Mechanizm RMI (Remote Method Invocation) w praktyce cz. 1 pokaząłem jak napisać prostą aplikację typu klient / serwer. Teraz spróbujemy rozbudować tamten projekt tak, aby serwer wywoływał metody klienta. Nie jest to typowa sytuacja wykorzystania tego mechanizmu, ale jest to dobra sytuacja dydaktyczna. Jako kod bazowy będziemy korzystać z projektu przygotowanego w poprzedniej części kursu.

W projekcie klienta dodajmy zdalny interfejs:

package com.blogspot.mstachniuk.example.rmiclient;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface MyClientInt extends Remote {

    void showInfo(String info) throws RemoteException;
}

Nalezy pamiętać aby metody interfejsu deklarowały wyjątek RemoteException. W przeciwnym wypadku podczas tworzenia obiektu dostaniemy wyjatek: ExportException powodowany przez: IllegalArgumentException.

Stwórzmy dalej implementację tego interfejsu, analogicznie jak w poprzednim wpisie:

package com.blogspot.mstachniuk.example.rmiclient;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class MyClientImpl extends UnicastRemoteObject
        implements MyClientInt {

    private static final long serialVersionUID = 1L;

    public MyClientImpl() throws RemoteException {
        super();
    }

    @Override
    public void showInfo(String info) {
        System.out.println("Info: " + info);
    }
}

i dodajmy do MyClientMain linijki rejestrujące obiekt (zaraz po utworzeniu context'u):

MyClientImpl myClientImpl = new MyClientImpl();
context.bind("rmi:MyClientObject", myClientImpl);

Teraz należało by po stronie serwera napisać kod, który by wyołał metodę klienta. Dodajmy poniższy kod w metodzie getDescription(String text) klasy MyServerImpl tuż przed instrukcją return:

String url = "rmi://localhost/";
try {
    Context context2 = new InitialContext();
    MyClientInt myClientInt = (MyClientInt) 
            context2.lookup(url + "MyClientObject");
    myClientInt.showInfo("Info od serwera");
} catch(Exception e) {
    e.printStackTrace();
}

Wówczas gdy klient wywoła zdalną metodę getDescription() serwer wywoła metodę klienta i dopiero zwróci wynik do klienta. Nie jest to może najszczęśliwszy przykład, al chodzi tu o zaprezentowanie działania mechanizmu. Jako że serwer jest uruchamiany wcześniej niż kod klienta, to serwer musi poczekać na moment w którym zdalny obiekt klienta będzie już dostępny. Dlatego próbę wywołania zdalnej metody klienta umieściłem w metodzie getDescription().

No dobra, importujemy to czego potrzebuje nasz wklejony kod (swoją drogą Eclipse kilkakrotnie w tym momencie mi się zawiesiło) i kopiujemy odpowiednie interfejsy (tj. MyClientInt). Następnie uruchamiamy i otrzymujemy masę wyjątków po stronie klienta. Trzeba dodać do skryptu uruchamiającego informację o codebase:

-Djava.rmi.server.codebase=file:/D:/Java_programy/RMIClient/bin/

Teraz otrzymujemy dwa wyjątki - po stronie klienta AccessControlException, a po stronie serwera UnmarshalException, spowodowany przez EOFException.

Zmodyfikujmy więc spowrotem plik polityki client.policy w projekcie klienta, aby zezwolić na wszystko:

grant {
 permission java.security.AllPermission;
};

Uruchamiamy i działa:) Czyli zabezpieczenia nie pozwalały na odpowiednią komunikację. Chcąc ustawić tylko tyle ile musimy, powyższy plik musimy zmodyfikować w następujący sposób:

grant {
 permission java.net.SocketPermission
 "*:1024-65535", "connect, accept";
};

Napiszę jeszcze trochę o wyjątkach. W przypadku jabyśmy podali zły adres zdalnego obiektu otrzymalibyśmy  wyjątek javax.naming.NoInitialContextException. W przypadku gdy nazwa na zdalny obiekt jest zajęta (np. gdy odpalimy dwie instancje klienta), otrzymamy javax.naming.NameAlreadyBoundException. Istnieje jeszcze pewnie wiele sposobów aby coś zepsuć.

Jak widać technologia RMI daje możliwość łatwego wywoływania zdalnych metod w kodzie, tylko że trzeba się trochę namęczyć z całą otoczką uruchamiania tych aplikacji. Przedstawione tutaj rozwiązanie nie jest idealne, o czym napiszę w kolejnej części kursu.

Więcej informacji:
Mechanizm RMI (Remote Method Invocation) w praktyce cz. 1

sobota, 2 października 2010

Mechanizm RMI (Remote Method Invocation) w praktyce cz. 1

Dzisiaj chciałbym Wam przedstawić mechanizm wywoływania zdalnych metod (ang. Remote Method Invocation, RMI). Spotkałem się z nim podczas studiów na jednym z przedmiotów. Pamiętam, że znajomi męczyli się bardzo, aby napisać prostą aplikację wykorzystującą RMI. Narzekali przy tym, że to niewykorzystywane jest, że są nowsze, lepsze metody realizacji podobnej funkcjonalności, że .NET itd. Jako że zaczął się niedawno rok akademicki mam nadzieję, że moje wywody pomogą niektórym w ogarnięciu tematu.

No dobra, ale przejdźmy do rzeczy. Mechanizm RMI dodano do Javy w wersji 1.1. W późniejszych wersjach pojawiały się jakieś modyfikacje tego mechanizmu, ułatwiające trochę pisanie. My napiszemy prostą aplikację typu klient / serwer. Klient będzie miał za zadanie wywołać metodę serwera. Projekt wykonam w środowisku Eclipse (choć nie jest to moje ulubione środowisko).

Tworzymy nowy (Ctrl+N) Java Project w Eclipse, jako Project name podajemy np. RMIServer. Dodatkowo zaznaczę, że projekt trzymam w katalogu (czy też mam tak ustawione Workspace): D:\Java_programy\ - przyda nam się to na później. Zaczniemy od zdefiniowania interfejsu który nasz serwer będzie udostępniać na zewnątrz. Klikamy na src w projekcie prawym przyciskiem myszy i New -> Interface. Wpisujemy nazwę pakietu (w moim przypadku: com.blogspot.mstachniuk.example.rmiserver) i nazwę interfejsu - ja dałem: MyServerInt. Przyrostek Int pochodzi od Interface.

W utworzonym interfejsie definiujemy metody, które będziemy udostępniać na naszym serwerze. Ja utworzyłem jedną metodę, która jako argument przyjmuje String i również go zwraca. W przypadku obiektów sprawa się trochę komplikuje, więc nie będę jej opisywał. Nasz interfejs musi rozszerzać inny interfejs: java.rmi.Remote. Dodatkowo każda metoda musi mieć zadeklarowane, że rzuca wyjątek java.rmi.RemoteException. Jest on rzucany, przy niepowodzeniu operacji wywołania zdalnej metody, przy zerwaniu połączenia itp. Poniżej kod mojego opisanego interfejsu:



No dobra, to teraz napiszmy kod, który będzie implementował przedstawioną powyżej funkcjonalność. Utwórzmy klasę MyServerImpl (przyrostek Impl od Implementation). Będzie on rozszerzał klasę java.rmi.server.UnicastRemoteObject - dla wygody. Można też bez tego - tylko wtedy sami musimy utworzyć obiekt serwera. Nasza klasa dodatkowo będzie implementować nasz wcześniej zdefiniowany interfejs. Przykładowy kod poniżej:



Linia definująca pole serialVersionUID jest do tego aby środowisko Eclipse dało nam spokój (tzn. aby nie wyswietlał sie żółty wykrzyknik przy nazwie klasy). Bez tego też zadziała. Wujek Bob w książce "Czysty kod. Podręcznik dobrego programisty" zaleca jednak aby samemu nie deklarować pola serialVersionUID, a pozwolić kompilatorowi na jego automatyczne wygenerowanie. Ma to znaczenie przy ewentualnej deserializacji różnych wersji klas.

Konstruktor jest wymagany i musi deklarować wyjatek RemoteException, gdyż konstruktory UnicastRemoteObject również deklarują ten wyjątek i może się zdarzyć, że będzie problem z utworzeniem zdalnego obiektu.

Chcąc wywołać zdalną metodę, musimy zarejestrować naszą klasę w rejestrze RMI. Posłuży nam do tego klasa MyServerMain. Utwórzmy więc taką klasę w naszym projekcie. To co musimy w niej zrobić to zarejestrować nasz obiekt pod jakąś nazwą. W tym przypadku będzie to nawa MyRemoteObject i obiekt klasy MyServerImpl. Kod poniżej:



Teraz już możemy spróbować uruchomić nasz serwer. Wciśnięcie Run (Ctrl+F11) w Eclipse powoduje wyjątki. Trzeba skorzystać z linii poleceń. Będę posługiwał się bezpośrednio komendami (Windows) aby lepiej można było zrozumieć co się w aplikacji dzieje.

Na początek utówrzmy sobie plik runServer.bat w katalogu projektu (u mnie: D:\Java_programy\RMIServer). Umieśćmy w nim taką zawartość:



Na początek należy odpalić rmiregistry, czyli rejestr początkowy RMI. Poprzedzmay go komendą start, aby otworzył nam się w osobnym oknie i kolejna komenda mogła się wykonać. Następnie uruchamiamy już naszą aplikację. Podajemy flagę -cp aby wskazać gdzie nasze skompilowane klasy leżą i następnie nazwę klasy (wraz z pakietem).

I tu zaczyuna sie pierwszy problem. Dostajemy spory stos wyjątków. Pośród nich można dostrzeć:



Rozwiązanie jakie kiedyś znalazłem, to można ustawić właściwość (ang. Property) java.rmi.server.codebase. Chodzi o to, że rmiregistry nie wie gdzie jest bytecode, z którego ma korzystać i trzeba mu to jakoś powiedzieć.

Wspomniane property możemy ustawić na 2 sposoby. Pierwszy z nich to wywołanie System.setProperty(key, value), a drugi to odpowiednie wywołanie z lini poleceń:
-Dkey=value

Zmodyfikujmy więc nasz skryp uruchomienowy:



Dobra, działa (na razie). Teraz sie zajmijmy klientem. Musi on korzystać z Menadzera bezpieczeństwa RMI, jeśli chcemy ładować kod ze zdalnego serwera. Dla projektu klienta tworzymy osobny projekt o nazwie RMIClient. Tworzymy w nim pakiet (w moim przypadku com.blogspot.mstachniuk.example.rmiclient) i umieszczamy w nim nową klasę MyClientMain. Po zainicjowaniu menażera bezpieczeństwa, musimy się dostać do zdalnego obiektu. Przykładowy kod klasy klienta poniżej:




Aby Eclipse nam nie krzyczało, że nie wie co to MyServerInt, skopiujmy więc ten plik z projektu serwera do projektu klienta, pamiętając aby umieścić go w tym samym pakiecie (nazwa pakietu jest nierozłączną nazwą klasy). Gdybyśmy plik umiescili w innym pakiecie lub zmienili nazwę klasy otrzymalibyśmy wyjątek ClassCastException.

Sprawdzamy czy coś działa. Uruchamiamy najpierw serwer (za pomocą naszego skryptu), a następnie klienta (z poziomu Eclipse). Dostajemy wyjątek java.security.AccessControlException, rzucany przez metdę: lookup(). Metoda ta próbuje odnaleść zdalny obiekt. Okzuje się bowiem, że RMISecurityManager zabrania nawiązywania połączenia w sieci. Musimy więc utworzyć plik polityki bezpieczeństwa. W tym celu tworzymy plik o nazwie client.policy w katalogu bin projektu naszego klienta. W pliku tym umieszczamy następującą zawartość, dającą nam nieograniczony dostęp do wszystkiego:



Podczas wdrożenia aplikacji trzeba bedzie plik ten zmodyfikować, w myśl zasady nie dawać więcej niż trzeba. Czas napisać skrypt uruchomieniowy dla klienta (katalog projektu klienta: D:\Java_programy\RMIClient):



Niestety dalej ten sam błąd. Rozwiązaniem tego problemu moze być dodanie pełnej (bezwzglednej) ścieżki do pliku cient.policy. Modyfikujemy nasz skrypt:



Warto jednak plik policy przenieść do głównego katalogu projektu. Po co? A no podczas czyszczenia projektu w Eclipse zawartość bin jest usuwana i możemy stracić nasz plik. Modyfikujemy więc odpowiednio skrypt uruchomieniowy:



Warto też czasem sprawdzić w kodzie, czy udało nam się uzyskać odpowiednie pozwolenia:



Gdyby druga metoda rzuciła java.security.AccessControlException oznaczało by to, że nie udało się ustawić odpowiedniej polityki bezpieczeństwa, czyli pewnie ścieżka jest błędna.


Oczywiście u Was ścieżki mogą byc trochę inne i musicie je dopasować do tego gdzie trzymacie projekt. Odpalamy skrypt i... udało się. Nie ma żadnego wyjątku:) Tyle że nasz kod klienta za wiele nie robi. Dodajmy więc poniższe 2 liniki do kodu klienta (za wywołaniem lookup()):



W tym momencie ładnie widać, że operowanie na zdalnych obiektach jest tak samo proste jak operowanie na lokalnych. Wywołanie metody niczym się nie różni.
Odpalmy klienta. Na konsoli powiązanej z programem klienta powinniśmy zobaczyć wynik działania:

Wynik: getDescription: Ala ma kota

a na konsoli serwerowej:

MyServerImpl.getDescription Ala ma kota

Oznacza to, że zadziałało. Zmieńmy tylko zawartość pliku client.policy, na następującą:



Powyższy plik oznacza zezwolenie na uzyskanie połaczenia z dowlną maszyną na portach 1024 - 65535. Domyślnie RMI siedzi na porcie 1099, a obiekty serwera mogą korzystać z wyższych portów. Chcąc być bardziej restrykcyjnym zamiast gwiazdki * możemy podać localhost.

Podsumowując, stworzyliśmy prosty serwer i klienta RMI. Przedstawiłem klika wyjątków, na które można się natknąć i podałem sposób radzenia sobie z nimi. Po więcej informacji odsyłam do ksiazki Core Java 2 Techniki Zaawansowane, a także do dokumentacji i tutoriali na stronie Sun'a. Z tego co mi wiadomo istnieje jeszcze wtyczka do Eclipse'a ułatwiająca pisanie aplikacji typu RMI. Ja osobiście nie korzystałem, więc nie wiem jak ona działa.

Więcej informacji:
[1] Tutorial na stronie Oracle: http://download.oracle.com/javase/tutorial/rmi/index.html
[2] Wprowadzenie do RMI w Javie 2 by Seweryn Hejnowicz:
http://www.ii.uni.wroc.pl/~prz/200405/2005lato/java/rmi/referat_rmi.htm
[3] Tworzenia aplikacji rozproszonej RMI by dr inż. Tomasz Kubik:
http://tomasz.kubik.staff.iiar.pwr.wroc.pl/dydaktyka/Java/JavaWyk06-RMI-TK.pdf
[4] Java. Techniki zaawansowane. Wydanie VIII
http://helion.pl/ksiazki/java_techniki_zaawansowane_wydanie_viii_cay_s_horstmann_gary_cornell,javtz8.htm (ja czytałem starsze wydanie i bardzo ładnie było wszystko opisane)
[5] Czysty kod. Podręcznik dobrego programisty:
http://helion.pl/ksiazki/czysty_kod_podrecznik_dobrego_programisty_robert_c_martin,czykod.htm (odnośnie serialVersionUID)