06 grudnia 2010

Odkrywcze podobieństwo Java SE i Java EE z CDI na czele

Każdy, kto programuje w Javie "siedzi" na poziomie Java Standard Edition, w skrócie Java SE, czy wręcz JSE. Obecna wersja to 6.0. Mamy do dyspozycji cały zestaw interfejsów i klas - wszystko objęte terminem Java SE API. Dokumentacja do aktualnej wersji znajduje się na stronach Java Platform, Standard Edition 6 API Specification. To powinno być oczywiste i należy do podstawowej wiedzy programisty Java.

Co jednak nie jest już tak oczywiste, to jak niewiele różni obecnie JSE od kolejnego zestawu Java API o nazwie Java Enterprise Edition, w skrócie Java EE, albo po prostu JEE. Zwracam Twoją uwagę na termin "kolejny zestaw Java API". Od lat siedzę przy obu zestawach - JSE i JEE, a jednak dopiero teraz dotarło do mnie, jak niewiele je różni - wszystko za sprawą środowiska uruchomieniowego, które określa zachowanie naszej aplikacji.

Do wersji JSE 5.0 wcale nie było oczywistym, że umiejętność programowania na platformie JSE jest równoznaczna z JEE. Nie grzebiąc za długo w historii JEE postawię tezę, że umiejętność posługiwania się JSE była dalece niewystarczająca od posługiwania się JEE. Wiele się być może nie zmieniło chyba, że nie mówimy o właściwym użyciu API (semantyce), a jedynie umiejętności użycia konstrukcji (składni). Tu widzę duże uproszczenia.

Od wersji JSE5 mamy adnotacje. Możliwość dopisywania metainformacji na różnym poziomie naszych bytów javowych - interfejsów, klas, pól i metod. Szybko zauważono ich zaletę i zaraz wprowadzono na JEE. I jakkolwiek tworzenie aplikacji na poziomie JSE zmieniło się nieznacznie, to w przypadku JEE postęp jest ogromny.

Poniżej klasa do uruchomienia na platformie JSE.
package pl.jaceklaskowski.blog;

public class BlogEntry {
    private String title;

    public BlogEntry(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    
    public BlogEntry create(String title) {
        return new BlogEntry(title);
    }
}
Od wersji JSE5 można dodawać do klasy metadane w postaci adnotacji. Dwoma z adnotacji dostarczanych w ramach zestawu JEE5 są @Entity oraz @Id.
package pl.jaceklaskowski.blog;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class BlogEntry implements Serializable {

    @Id
    private int id;
    private String title;

    protected BlogEntry() {
    }

    public BlogEntry(String title) {
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public BlogEntry create(String title) {
        return new BlogEntry(title);
    }
}
Poza dodaniem tych dwóch adnotacji pojawiły się również zmiany w samym kodzie klasy. I jakkolwiek samo użycie adnotacji nie wymaga ich, to użycie na platformie JEE może uczynić je obowiązkowymi do poprawnego użycia klasy. Aby ujawniła się ich obowiązkowość konieczne jest uruchomienie klasy w ramach platformy JEE, tj. serwera aplikacyjnego JEE (którego zadaniem jest udostępnienie usług respektujących wytyczne specyfikacji JEE) oraz użycie konstrukcji aktywujących daną funkcjonalność. Innymi słowy, samo uruchomienie w ramach serwera aplikacyjnego JEE nie jest równoznaczne z użyciem usługi, która interpretuje adnotacje. Nie zamierzam jednak tym razem wnikać w szczegóły i unikam wyjaśnień wskazując podręcznik The Java EE 6 Tutorial, a szczególnie rozdział Part VI Persistence. Obowiązkowa lektura dla każdego, któremu marzy się tworzenie aplikacji korporacyjnych w JEE, o których w kolejnych wpisach.

I te małe dodatki - metadane w postaci adnotacji - mnie dzisiaj zachwyciły. Podczas lektury artykułu w Sieci na temat tworzenia Web Services, olśniło mnie, że aplikując adnotacje do klasy i uruchamiając ją w ramach serwera aplikacyjnego dostajemy wiele przy tak niewielkim nakładzie pracy.

Przyjrzyj się poniższej klasie.
package pl.jaceklaskowski.blog;

public class BlogPanel {
    public Blog createBlog(String title) {
        return new Blog(title);
    }
}
Niewiele w niej cech aplikacji korporacyjnej, którą cechuje użycie bazy danych (często wielu równocześnie), dostęp do innych zasobów transakcyjnych, różne protokoły dostępowe (wszechobecny HTTP może być uzupełniany przez chociażby FTP czy IMAP) czy bezpieczeństwo. To tylko niektóre z możliwych usług serwera aplikacyjnego JEE.

Przyjrzyjmy się kolejnej wersji wcześniejszej klasy BlogPanel, która różni się jedynie adnotacją @Stateless.
package pl.jaceklaskowski.blog;

import javax.ejb.Stateless;

@Stateless
public class BlogPanel {
    public Blog createBlog(String title) {
        return new Blog(title);
    }
}
Tym razem, poza @Stateless, nic więcej się nie zmieniło. Czyżby?

Jeśli uruchomisz tę klasę na serwerze aplikacyjnym JEE, okaże się, że wykonanie metody createBlog(String title) pociągnie za sobą wzbudzenie monitora transakcji i uruchomienie dedykowanej transakcji, która rozpocznie się i zakończy z rozpoczęciem i zakończeniem wykonania ciała metody. Dodatkowo, każdemu będzie wolno wykonać tę metodę, ale samo sprawdzenie zostanie wykonane. Jak widać, użycie tak niewinnie wyglądającej adnotacji @Stateless może odmienić zachowanie aplikacji, a jej czas wykonania wydłuży się kosztem opakowania jej usługami serwera.

Tak wiele, tak niewielkim kosztem - odkrywcze podobieństwo między JSE a JEE.

W JEE6 pojawiło się rozszerzenie oferowanego API o pakiety javax.enterprise.inject, javax.enterprise.context oraz javax.enterprise.event, które materializują wytyczne specyfikacji JSR 299: Contexts and Dependency Injection for the Java EE platform, w skrócie CDI. Sama specyfikacja jest częścią Java EE 6 i przez to obowiązkowa w ramach serwerów aplikacyjnych JEE6 (tu należy zwrócić uwagę na wersję wspieranego zestawu JEE - musi być 6).

I tutaj było moje największe odkrycie, a w zasadzie faktyczne przetrawienie wszystkiego, co do tej pory usłyszałem na temat JEE6, EJB 3.1, JSF 2.0, JPA 2.0, Servlets 3.0 i kilku innych.

Wszystkie byty na platformie JEE6 są opisywalne przez adnotacje.

Z tego płynie bardzo istotna wiedza pozwalająca zrozumieć sedno działania serwera aplikacyjnego JEE i związywania usług do odpowiednich składowych (komponentów) naszej aplikacji. Wystarczy zaaplikować adnotację i jak za dotknięciem czarodziejskiej różdżki pojawi się automagicznie pożądana funkcjonalność (ortogonalna do funkcjonalności biznesowej, którą oprogramowujemy i tu jest właśnie miejsca dla naszej inwencji twórczej).

Skoro CDI to specyfikacja odnosząca się do definiowania kontekstu funkcjonowania bytu zwanego ziarnem zarządzanym (ang. managed bean) - wcześniej termin zarezerwowany przez specyfikację JSF) i to właśnie kontekst wyznacza jego cykl rozwojowy (ang. lifecycle) - przejścia między stanami, w których wyróżnionymi są utworzenie i zniszczenie obiektu, wtedy można postawić tezę, że wszystko, co mamy do dyspozycji jako programiści JEE6 można oprzeć na CDI.

W ten sposób Resin - kontener webowy - oferuje EJB 3.1 przez CanDI. Pewnie będzie można zauważyć podobny trend w innych serwerach aplikacyjnych.

CDI jest również możliwe do uruchomienia na platformie JSE6, ale w takiej konfiguracji będziemy musieli rolę serwera przejąć na siebie i oprogramować w ramach aplikacji. Nawet, jeśli CDI i JEE6 nie są doskonałe, to są standardem, który zmierza w dobrym kierunku. Po czasach rozterek JEE vs Spring Framework+Hibernate mam wrażenie, że wybór staje się bardziej oczywisty. Mnie to cieszy.

5 komentarzy:

  1. Weld oferuje gładką integrację z JSE.
    http://docs.jboss.org/weld/reference/1.0.0/en-US/html/environments.html#weld-se

    Już teraz EJB, to zwykłe pojo z odpowiednimi adnotacjami. W przyszłości może zniknąc pojęcie EJB, a w jego miejsce dostaniemy kilka dodatkowych adnotacji. JavaEE 7 moim zdaniem pójdzie w tym kierunku.

    Zamiast EJB @Statefull, można zastosować zwykły bean @SessionScoped, @Stateless to zwykły @RequestScoped. @Singleton to @ApplicationScoped. Do tego adnotacja np. @Transactional na poziomie klasy lub metody i mamy EJB :). Po co do wstrzykiwania EJB używać, @EJB, skoro standardowe @Inject wystarczy (wiem, teraz tez można użyć @Inject, ale po co dwa sposoby).

    Wszystko się upraszcza i to w dobry sposób, jest łątwiej i "czyściej", a możliwości zostają.

    OdpowiedzUsuń
  2. Natknąłem się niedawno na ciekawy opis "zmagań" z Weld:
    http://hwellmann.blogspot.com/2010/11/cdi-major-risk-factor-in-java-ee-6.html

    Podzielam zdanie autora, że jest to świeża implementacja, która wymaga dopracowania. Ja akurat nie wykorzystuje CDI na szeroką skalę (na razie). Czy zauważyłeś w swoich projektach podobne problemy z Weld-em??

    OdpowiedzUsuń
  3. Ja mam za mało (=zero) doświadczenia z Weldem i liczę na aptu. Aptu, przejmujesz pałeczkę ;-) Oczywiście, innych również uprasza się o pomoc.

    OdpowiedzUsuń
  4. Myślę że usunięcie EJB byłoby dobrym posunięciem. Tym bardziej że cykl życia SLSB nijako się ma do tych zdefiniowanych w CDI.

    OdpowiedzUsuń
  5. U nas (Niemcy) podjeto nieszczesna decyzje uzywac CDI i GF przed grudniem 2009, czyli zanim zostal nawet final. Wersja 3.0.1 byla nieuzywalna, np nie dzialalo pakowanie aplikacji jako EAR - jesli jar z JPA byl osobno niz jar EJB / CDI - CDI nie widzial wtedy JPA i nie umial wstrzykiwac. Pomijam tony (doslownie, tony!) bledow mniejszych oraz wiekszych. EclipseLink 2.x rowniez ma sporo bugow ktore albo sami naprawialismy, i czekalismy miesiace zanim zostana wlaczone oficjalnie, albo robilismy brudne workaroundy aby tylko dzialalo. Hibernate nie wchodzil w gre gdyz nie implementowal specyfikacji, a przynajmniej tych czesci ktorych wymagalismy, a i nadal ma olbrzymie problemy.
    Postanowilismy uzywac GF 3.1.x do developerki, poniewaz chcielismy moc testowac za pomoca EJB embedded - jesli ma sie aplikacje z wieloma modulami, tworzony jest tymczasowy EAR aby aplikacje zdeployowac, a jak wspomnialem 3.0.1 nie umie sobie z tym poradzic. Okazuje sie, ze embeded EJB w GF 3.1 zachowuje sie inaczej niz normalnie, i jest wiele wiele problemow z uzywaniem CDI, poczawszy od tego, ze chociazby nie mozna wstrzykiwac @Dependent beanow do EJB za pomoca konstruktora. Za pomoca setterow lub pol czasami dziala, czasami nie - przy czym ingeruje to w architekture, poniewaz bez constructor injection nie ma mozliwosci miec pol final, na ktorych nam zalezy, a field-injection sie kompletnie nie da testowac (ok, mozna wykorzystac refleksje, cool). Ile bledow zglosilem, ile zostalo olanych, nie miesci mi sie w glowie. I to bledy ktore istnialy rowniez w tzw wersji 'final'...
    Z perspektywy - popelnilismy wielki blad uzywajac CDI, a teraz brniemy dalej bo juz za pozno. Obejscia jakie robimy czasami przyprawiaja mnie o wymioty, takie to sa hacki. Dzieki takim hackom wiazemy sie z GF, bo pewnie pod JBoss czy innym nie beda dzialac, beda wymagane inne. To samo z EclipseLink (ktorego to nota bene i tak nie uzywamy z GF, tylko pakujemy swojego bo naprawilismy pare bugow i nie mozemy sie doczekac aby zostaly wlaczone do oficjalnego wydania) - mamy pare workaroundow ktore nas wiaza z nim, i zmian providera znaczy wybuch aplikacji (sprawdzane z Hibernate). Odradzam CDI, a przynajmniej w jego jedynej poki co odmianie jaka jest Weld poki co kazdemu. Nie dziala poprawnie i przewidywalnie ani w GF, ani nawet w JBoss.

    OdpowiedzUsuń