Wspomagacze dla Javy 8

Posted 2 CommentsPosted in java

W dzisiejszym wpisie omówię kilka bibliotek, dzięki którym pisanie w Javie staje się przyjemniejsze :) Jeśli czujesz, że barokowy kod Javy, jest… barokowy, ale nie możesz (lub nie wolno Ci) pisać w Scali – to ten wpis jest dla Ciebie. Biblioteki o których mowa to: javaslang, AssertJ oraz Lombok.

Co prawda od momentu wydania pierwszej wersji Javy 8 minęło już sporo czasu, ale nadal nie wszyscy mają ten komfort, aby móc wykorzystywać ją komercyjnie. Ja na szczęście mam taką możliwość, więc postanowiłem opisać kilka wspomagaczy, za sprawą których pisanie w Javie jest przyjemniejsze 😉 A odnośnie przyjemności pisania kodu, to Scala jest dla mnie nadal niedoścignionym wzorcem. Ale jeśli tak dalej pójdzie, to może nawet w czystej Javie w okolicach JDK10..11 będzie już całkiem znośnie.

Javaslang wprowadza do Javy 8 dodatkowe możliwości w programowaniu funkcyjnym. Osoby zaznajomione ze Scalą od razu odnajdą się w API, które udostępnia javaslang. Co prawda nie będzie tak fluent jak w Scali, ale jeśli mamy projekt w JDK8, a nie możemy sobie pozwolić na Scalę, to javaslang sprawdzi się idealnie.

Poniżej przedstawię przykłady i porównania różnych wersji kodu, który robi to samo. Jako założenie przyjmuję, że wszelkie filtrowania będą robione na poziomie kodu Java, dzięki temu łatwiej będzie mi pokazać różnice. Z góry uprzedzam – żeby nie było pytań dlaczego nie robię tego na innej warstwie, np. w WHERE …. 😉

Przykład 1: filtrowanie po mieście, z którego pochodzi osoba.

Pierwsza wersja to kod w czystej Javie, z czasów sprzed Javy 8. Jest długo, sporo linii się marnuje.

Druga wersja to kod wykorzystujący możliwości Javy 8. Jednak w oczy rzuca się stream() oraz collect(toList()), które nadal zbędnie wydłużają kod. Jest to jeden z moich największych zarzutów dla Javy 8, zaraz po method reference z ::.

Trzecia wersja to wykorzystanie możliwości javaslang. Jest to najkrótsza wersja, i jednocześnie najbardziej czytelna. Jedyne co początkowo może razić to, że należy dokonać konwersji z java.util.List na javaslang.collection.List (metoda ofAll). Następnie jest już dużo krócej. Jednak wraz z tym, jak coraz większe partie kodu zaczną korzystać z API javaslang, liczba takich konwersji szybko się zmniejsza.

 

Przykład 2: wykonanie akcji, dla wszystkich osób, które mają hobby. Pole hobby jest opcjonalne, może być nullem. Założenie: nie mogłem użyć Optional :)

Pierwsza wersja to czysta Java 8. W oczy kłuje konieczność wielokrotnego wołania metody getHobby().

Druga wersja, czyli wykorzystania javaslang. Wykorzystanie potęgi Option. Jeśli hobby było zdefiniowane, Option stanie się Some i dla każdego hobby wykona akcję. Jeśli hobby nie było zdefiniowane – Option będzie None, i żadna akcja nie zostanie podjęta.

Trzecia wersja to zamiana lambdy na method reference z JDK8. Nie dało się chyba bardziej zaciemnić kodu, niż tymi dwoma dwukropkami. Dla porównania, w Scali dało się to zrobić czytelnie, dwie wersje do wyboru:

Obie wersje (z filtrowaniem oraz Option) są dużo bardziej zwięzłe niż w Javie, a także czytelniejsze :)

Ponadto, javaslang znacznie zwiększa możliwości functional interface. Mamy curried(), memoized(), tupled()… Należy tylko pamiętać, żeby zamiast Javowych interface używać tych z javaslang – one i tak rozszerzają te javowe, a dają dużo więcej możliwości. Przykład:

W javaslang zawsze możemy zawołać funkcję z liczbą wypełnionych argumentów mniejszą niż zadeklarowane. W czystej Javie 8 nie mamy takiej możliwości. Daje nam to znacznie więcej możliwości komponowania praktycznego i czystego API.

Kolejna rzecz, o której warto wspomnieć, to Tuple. Przydatne, jeśli na szybko trzeba poskładać byt, który przechowuje wartości, ale nie mamy pod ręką klasy, które się do tego nadaje:

Match, czyli funkcjonalność analogiczna do pattern matching ze Scala. Najpierw jednak pokażmy kod w smutnej Javie:

Switch, a więc trzeba pamiętać o break, a także obsłużeniu default. Ale akurat default to wymusi na nas final.

Teraz wersja z if:

Jest już lepiej, ale zobaczmy jak będzie wyglądać tak samo działający kod z wykorzystaniem Match:

Jest to zdecydowanie najbardziej czytelna wersja. Należy jednak pamiętać o korzystaniu z Supplier (czyli: () ->), dzięki któremu wykonanie instrukcji będzie lazy.

Przedstawiłem tylko kilka z wielu możliwości javaslang. Zachęcam do zapoznania się z tą biblioteką we własnym projekcie.

 

AssertJ jest biblioteką, która sprawia, że asercje w testach można pisać w czytelniejszy sposób. Prawie tak samo fajny jak w ScalaTest. Jak możemy przeczytać na stronie projektu, biblioteka ta jest rozszerzoną i ulepszoną wersją projektu Fest – powstała jako jego fork. Jako ciekawostkę dodam, że podczas mojej pracy z AssertJ znalazłem bug – od czasu jego zgłoszenia do bug truckera projektu do momentu rozwiązania problemu minęło mniej niż 24 godziny :) Taki szybki czas reakcji również pozytywnie świadczy o projekcie i zachęca do dalszego korzystania.

Pierwszy przykład – sprawdzenie czy lista jest pusta. Już sam JUnit daje nam kilka możliwości, ale API AssertJ jest zdecydowanie czytelniejsze:

Sprawdzenie przefiltrowanych danych:

Wynik powinien zawierać tylko osoby, które są z Lublina. Asercja zapisana za pomocą AssertJ jest czytelniejsza, i łatwiejsza w zrozumieniu. Łatwiej widać, jaki cel miał autor tego testu, co chciał sprawdzić i jakich warunków oczekuje. Dla porównania w Scali i tak będzie najkrócej, ale niemniej czytelnie:

Przykład drugi – sprawdzenie czy zadany ciąg znaków spełnia wymagania:

Kolejny raz asercje napisane w AssertJ są łatwiejsze w zrozumieniu – od razu widać co jest przedmiotem tego testu, nie ma niejednoznaczności. Dodatkowym atutem, jest większy feedback z wiadomości w przypadku, gdy test zakończy się failem.

W AssertJ możemy również w prosty sposób zmienić zachowanie się asercji, tak aby zawsze wykonały się wszystkie sprawdzenia – a nie do pierwszego faila. Nazywa się to soft assertions. Przykład:

Należy tylko pamiętać o wywołaniu assertAll() na końcu metody testowej. Ale w przypadku JUnit i na to jest rozwiązanie:

 

Ostatnia z bibliotek nie jest związana bezpośrednio z Javą 8, ale w kontekście całości tego wpisu pasuje idealnie.  Czym jest Lombok? Lombok jest biblioteką umożliwiającą ograniczenie ilości pisanego kodu. Dzięki Lombokowi zamiast tracić czas, na żmudne pisanie getterów, setterów i konstruktorów możemy go spędzić bardziej produktywnie – pisząc prawdziwy kod.

Aby móc korzystać z Lomboka, oprócz wpisu w pomie potrzebujemy także pluginu do naszego IDE. Zarówno dla IntelliJ jak i NetBeans mogę śmiało powiedzieć, że plugin działa poprawnie. Natomiast jeśli chodzi o Eclipse – na stronie Lomboka możemy znaleźć informację, że także dla tego IDE jest plugin, ale z niego nie korzystałem, więc nie wypowiem się czy działa poprawnie. Dzięki pluginom do IDE normalnie działa podpowiadanie składni. Należy tylko pamiętać, aby włączyć w IDE przetwarzanie adnotacji podczas (w IJ jest to ustawienie „Enable annotation processing”). Jednak przejdźmy do przykładów. Poniżej mamy klasę Person, napisaną w czystej Javie:

Jak można łatawo zauważyć, tylko 5 linii kodu ma znaczenie, natomiast pozostałe kilkadziesiąt to pusty kod, który jedynie pobiera lub ustawia wartość. Dodajmy teraz Lomboka: na początek jedynie zamiast getterów i setterów:

Klasa uległa znacznemu skróceniu, a kod nadal działa tak samo. Teraz zastąpmy konstruktory:

Finalnie mamy klasę, która posiada takie same własności, działa tak samo, ale kodu jest znacznie mniej. Czy jest czytelniej? To zależy. Początkowo może być trudno przyzwyczaić się do takiej formy, ale z czasem staje się to coraz bardziej czytelne. Adnotacje Lomboka pozwalają na konfigurowanie niektórych ustawień – można m.in. modyfikować zasięgi widoczności:

Podsumowując: Lombok to dobry sposób na zmniejszenie ilości nudnego kodu. Jeśli jednak możemy to napisać w Scali, to będzie jeszcze krócej i czytelniej:

Podsumowanie

Nie miałem na celu pokazania pełnych możliwości omawianych bibliotek. Moim celem było jedynie wykazanie, że powyższe biblioteki idealnie realizują zadania, do których zostały powołane. Podepnijcie je więc do swoich projektów już dziś, i cieszcie się jeszcze czystszym kodem :)

Facebooktwittergoogle_plusredditpinterestlinkedinmailFacebooktwittergoogle_plusredditpinterestlinkedinmail

Krótka historia o GlassFish4 i BeanManager

Posted Leave a commentPosted in java

W aplikacji Java EE6, nad którą pracuję, wykorzystuję CQRS. W związku z tym korzystam z dosyć rozbudowanej floty Commandów i Handlerów. Aplikacja na starcie wyszukuje wszystkie Handlery i zapisuje mapowanie Command na Handler. Aby uzyskać listę wszystkich handlerów korzystam z BeanManagera. Samo pozyskanie instancji BeanManagera odbywa się w następujący sposób:

Gdy mamy już BeanManagera możemy przejść do sedna sprawy. Oto CommandHandler:

Sama rejestracja handlerów wygląda w ten sposób:

Kod bez problemów działał na JBossie oraz Resinie. Po przesiadce na GlassFisha 4 zaczęły się schody – metoda getBeans zaczęła zwracać pusty zbiór. Korzystanie z aplikacji stało się niemożliwe, gdyż nie działało nawet logowanie. Musiałem więc przyjrzeć się temu kodowi bliżej.

Klasa LoginHandler implementuje CommandHandler. Dodatkowo pamiętamy, że początkiem wszystkiego w Javie jest klasa Object, tak więc mamy taką oto hierarchię:

Posiłkując się IntelliJ oraz debugiem za pomocą getBeans(Object.class) uzyskałem dostęp do wszystkich zarejestrowanych beanów. Na tej liście znalazłem także moje problematyczne beany.

Zagłębiając się w problem natrafiłem na to, że metoda getTypes() rzeczonego handlera zwraca 3 wartości:

debug-pt1

Pomimo obecności CommandHandler na pozycji pierwszej, nie jest ona brana pod uwagę podczas wołania beanManager.getBeans(CommandHandler.class). Wynika to z tego, że CommandHandler przyjmuje typy generyczne, a GlassFish4 inaczej niż JBoss i Resin traktuje takie klasy.

Rozwiązanie, które udało mi się znaleźć to wprowadzenie typu nadrzędnego (bez generyków) wobec CommandHandler.

h2

Teraz należało jeszcze zmodyfikować metodę odnajdującą handlery:

I wszystko działa jak należy.

Facebooktwittergoogle_plusredditpinterestlinkedinmailFacebooktwittergoogle_plusredditpinterestlinkedinmail