Komponens alapú szoftverfejlesztés EJB használatával
Csúcs Gergely (wizard@avalon.aut.bme.hu)
Dr. Charaf Hassan (hassan@avalon.aut.bme.hu)
BME, Automatizálási és Alk. Informatikai Tanszék
Fejlõdése során a Java nyelv mindig igyekezett a kor kihívásainak maradéktalanul megfelelni. Idõrendben haladva ezek a követelmények a következõk voltak:
Platformfüggetlenség
Elosztott mûködés lehetõsége
Komponensalapú szoftverfejlesztés lehetõsége
A szoftverek hordozhatóságának igénye nagyon régi. Tulajdonképpen tetszõleges magasszintû programozási nyelv teljesíti ezt az igényt, egy bizonyos szintig. Az adott nyelven készült program szinte hordozható (esetleg bizonyos típusos változók mérete ill. pontossága tartogathat meglepetéseket). Probléma akkor jelentkezik, amikor valamilyen API-hoz szeretne hozzáférni. Ugyanis az adott API vagy elérhetõ az adott nyelvi implementációból vagy nem. Mert az API-k implementációja már erõsen kötõdik a környezethez, így nem hordozható. Amíg valaki meg nem írja, addig nincs.
A Sun ehhez képest fogta magát, és a Java megalkotása mellett bevezette a bytecode-ot, ami egy pontosan specifikált virtuális környezetben fut. És mivel az összes Java API is így tesz, ezért az alkalmazások remekül hordozhatók (természetesen a JVM-et Java Virtual Machine ettõl még hardware/software környezettõl függõen kell implementálni, de ez úgy tûnik, sikerülni szokott).
Így a Java a hordozhatóság/platformfüggetlenség követelményének már a kezdetek óta eleget tesz; igazából vetélytárs nélkül.
Hálózatok márpedig vannak
Nagyon igaz. Három jelentõsen különbözõ mód áll rendelkezésre hálózatok eléréséhez:
Socket-ek használata
Ez a legalacsonyabb szintû hálózatkezelési mód. Nem sok szolgáltatást nyújt; kapcsolatorientált (TCP) ill. datagram alapon (UDP) küldhetünk és fogadhatunk byte-sorozatokat. Támogatja az URL-ek használatát ill. a Java Web-barátságossága miatt a HTTP protokollt, de minden egyéb szolgáltatást magunknak kell megvalósítanunk. Cserébe bármivel együtt lehet mûködni, csak egyeztetni/ismerni kell a használt protokollt.
RMI
Ha egyszerû, de elosztott Java alkalmazást kívánunk készíteni, akkor a leghelyesebb, ha az RMI (Remote Method Invocation)-t használjuk. Ez ugyanis a Java saját ORPC (Object RPC) protokollja. Csak a legszükségesebb szolgáltatásokat nyújta: távoli metódushívásokat hajthatunk végre vele Remote-ból származó interfészt megvalósító objektumokon, valamint az ilyen objektumokra való referenciák megszerzése érdekében egy névszolgáltatást biztosít. A rendszer érdekessége az objektum típusú paraméterek kezelése: ha az adott objektum Remote interfészû, akkor távoli objektumreferencia közlekedik a hálózaton, ha az objektum szerializálható (a Serializable interfészt úgymond implementálja; igazából ez csak egy jelölõinterfész, a csomagolás automatikusan történik), akkor az objektumpéldány továbbítódik. Nem jelent problémát egyik esetben sem, ha a célállomáson nem áll rendelkezésre a távoli referencia kezeléséhez szükséges stub (csonk) ill. a szerializált osztály implementációja, mivel az RMI ilyenkor átküldi azt is. Ilyen lehetõséggel kevés más elosztott környezetben találkozhatunk, használatával hatékonyan kezelhetõk terheléselosztási, skálázási feladatok.
Hasonlóságok a Java alap és elosztott objektummodelje között:
távoli objektumreferencia lehet metóduhívás (akár helyi, akár távoli) argumentuma ill. visszatérési értéke
távoli objektum a szokásos módszert követve cast-olható az általa megvalósított interfészek bármelyikére
az instanceof operátor alkalmazható a távoli objektumok által támogatott interfészek azonosítására
Eltérések is vannak persze:
a távoli objektumokkal való kapcsolattartás mindig valamely Remote interfészen keresztül történik, sosem közvetlenül az implementációs osztállyal
a metódushívások argumentumai és visszatérési értéke szinte mindig másolatként kerül átvitelre (az egyetlen kivétel, ha az adott érték maga is egy távoli objektumreferencia)
távoli objektumok átadása viszont mindig referenciálisan történik
a java.lang.Object-ben definiált metódusok egy része a távoli objektumok távolisága miatt megváltozott jelentéssel bír
az eloszott környezetbõl származó hibajelenségekre a kliensnek fel kell készülnie (ez új kivételek kezelését teszi szükségessé)
CORBA/IIOP
A CORBA (Common Object Request Broker Architecture) egy megoldás a komponens alapú elosztott rendszerek fejlesztése által felvetett problémákra. Az OMG (Object Management Group, a CORBA-n kívül pl. õk gondozzák az UML Unified Modelling Language specifikációját is, meglehetõsen sok 800-nál is több cég szövetsége) bábáskodása alatt született meg a CORBA 1.0 még a 90-es évek elején.
Az alap CORBA meglehetõsen haszontalanra sikerült: csak az OOP-t nem támogató C nyelvhez tartalmazott leképezést (pedig akkoriban a C++ nyelv már elég elterjedt volt, ezért rengeteg inkompatibilis házi-leképezés készült hozzá), ráadásul még nem sok szó esett a különféle ORB implementációk (és az alattuk fejlesztett komponensek) együttmûködésérõl, így ilyen nem is létezett.
1994-re jutottak a cégek közös nevezõre, és elkészült a CORBA 2.0 specifikáció, amely már számos nyelvhez tartalmazott egységes leképezést, valamint az IIOP (Internet Inter-ORB Protocol) protokollt az együttmûködés érdekében. Ez jelentette a fordulópontot a CORBA elterjedése szempontjából, használható-vá vált.
Mivel a Java már a nyelv szintjén el tudja különíteni interfészt és implementációt, ezért különösen jól illeszkedik a CORBA szemléletbe. Tulajdonképpen ugyanazt nyújtja, mint az RMI, több szolgáltatással (CORBA service-k igénybevehetõk), viszont az implementáció küldözgetésének meglehetõsen speciális lehetõségérõl le kell mondanunk.
Ami mégis elõnyössé teszi egy Java programozó számára az az, hogy a CORBA egy nyelv- és az IIOP bevezetése óta implementációfüggetlen komponensrendszer; a tulajdonképpeni munka pedig ugyanannyi: nem Java, hanem IDL nyelven kell megírni a Remote interfészt, a fejlesztés hátralevõ része pedig megegyezõ (szisztematikusan végrehajtható eltérésekkel csupán). Érdekesség, hogy maga az RMI alrendszer is rávehetõ, hogy IIOP segítségével kommunikáljon.
JavaBeans
A Java nyelv komponensalapú programfejlesztést támogató API-ja. Egy komponens alkalmazások összeállításához használható. Különválik ugyanis a programozó (hagyományos értelemben vett fejlesztõ, developer, aki a komponenseket készíti), és az összeállító (assembler, ez egy felhasználó-közelibb tevékenység, kész komponensek segítségével szereli össze a nagy egészt) szerepköre. JavaBean ugyanis szinte bármi lehet, egy apró GUI-elemtõl kezdve (például egy gomb vagy egy gördítõsáv) egy akár önállóan is mûködõképes alkalmazásig (közkedvelt példa: egy táblázatkezelõ). Amivel egy Bean több: egyszerûbb esetben egy összetett dokumentum, tágabb értelemben egy összetett alkalmazás része lehet. Például az említett GUI elemek tipikusan egy alkalmazás kezelõfelületének létrehozásakor kerülnek felhasználásra, egy táblázat pedig megjelenhet egy dokumentumban (szöveg-file vagy prezentáció).
Egy JavaBean nem más, mint egy vizuálisan kezelhetõ újrafelhasználható szoftverkomponens.
A következõ tulajdonságokkal kell rendelkeznie:
betekintési (introspection) lehetõséget kell biztosítania a fejlesztõeszközök számára: a komponens alapvetõ mûködésérõl szóló információkat elérhetõvé kell tennie:
vagy bizonyos design patter-nek betartásával (ekkor a Java Reflection API-jának segítségével történik a komponens felderítése)
vagy egy explicit BeanInfo osztály biztosításával
testreszabhatóság: ha van értelme a komponens testreszabásának (megjelenés, viselkedés, stb. szempontból), akkor az erre szolgáló property-ket elérhetõvé és beállíthatóvá kell tenni (emiatt - tekintve, hogy vizuális szoftverösszeszerelésrõl van szó - a komponensnek alkalmasnak kell lennie a fejlesztõkörnyezetben való futásra)
a legfejlettebb megoldás, ha a Bean-hoz tartozik egy BeanCustomizer osztály, ami a Bean testreszabására való célalkalmazás
események kezelése: a JavaBean-ek egymással események segítségével kommunikálnak
perzisztencia támogatása: erre már csak azért is szükség van, mert a testreszabás eredményét valahogyan rögzíteni kell
ezenkívül egy JavaBean osztály is, így rendelkezik metódusokkal, és property-kkel, amik kívülrõl nézve tagváltozókra hasonlítanak (get és/vagy set metódusokkal érhetõk el), belül meg ki tudja
Érezhetõ, hogy nem érdemes válogatás nélkül mindent JavaBean-né alakítani, hiszen ezeknek a tulajdonságoknak csak az osztályhierarchia felsõ, már vizuális (egérrel böködök) szerkesztésre alkalmas részében vehetjük hasznát. A JavaBeans architektúra még csak komponensorientált; lokális komponensekben gondolkodik, így elosztott környezetben futó üzleti logika megalkotására önmagában kevéssé használható (természetesen a példaként felhozott GUI komponensek esetében semmi értelme az elosztott mûködésnek, de már az összetett dokumentumok esetében nem ilyen egyértelmû a helyzet).
Jini
A Jini egy nagyon érdekes kezdeményezés: saját megfogalmazása szerint felhasználói csoportok és a rendelkezésükre álló erõforrások szövetségén alapuló elosztott rendszer. Célja, hogy a hálózaton keresztül megosztott erõforrásokat egy könnyen adminisztrálható, dinamikus entitássá fogja össze, amely rugalmasan képes az õt használó munkacsoport igényeihez alkalmazkodni.
Az említett erõforrások szolgáltatások formájában kerülnek megosztásra, és igazából nem számít, hogy egy szolgáltatás mi implementál (nem fontos a nyelv, sõt: majdnem puszta hardware eszközök is integrálhatók a rendszerbe).
Egy Jini rendszer középpontjában a Lookup szolgáltatás (esetleg szolgáltatások, mert szükség esetén hierarchiába szervezhetõek) áll: természetesen ez egy névszolgáltatás.
discovery protokoll segítségével böngészhetõ
join protokoll segítségével helyezhetõ el rajta új szolgáltatás
Architekturális bõvítés még az elosztott biztonsági rendszer megjelenése (a lokális hozzáférésvezérlés hálózatos kiterjsztése).
A Jini programozási modellje üzenetalapú (ennyiben emlékeztet a JavaBeans-re, csak persze itt bonyolítja a dolgot, hogy elosztott környezetben való mûködésrõl van szó), a szolgáltatásokhoz való hozzáférés azok kibérlésével (lease) történik: a bérlet bizonyos idõtartamra szól (megbízhatatlannak tekintendõ hálózatos környezetben ez a legbiztonságosabb), lehet kizárólagos vagy osztott, esetleg meghosszabbítható illetve meghosszabbíthatatlan.
A Jini tartalmaz elosztott tranzakciókezelést biztosító szolgáltatást, de ennek használatához persze tranzakció-képesnek kell lennie a résztvevõ szolgáltatásoknak is.
És hogy mitõl olyan érdekes a Jini? Attól, hogy elegánsan használja az RMI ismertetésénél említett lehetõséget: a Lookup szolgáltatás ugyanis az igénylõ kliens számára egyszerûen átküld egy Server Object néven emlegetett apró proxy objektumot (szükség esetén implementációval együtt ugye), és a késõbbiekben ez kommunikál a szolgáltatóval. Természetesen akármilyen implementációjú szolgáltatóval, akármilyen protokollt használva.
EJB
Az Enterprise JavaBeans a JavaBeans technológia kiterjesztése elosztott rendszerek számára. Az Enterprise Bean-ek kiszolgálóoldali software-komponensek, egy többrétegû alkalmazás üzleti logikáját lehet belõlük összeállítani.
Bár az EJB rokona a JavaBeans-nek, más követelményeket (is) támaszt, és másra is való:
EJB komponens lévén, hogy valamilyen kiszolgálón fut nem tud önállóan futni
Bár továbbra is felfogható a rendszer egy nagy alkalmazásként, ami komponensekbõl lett összeállítva, megjelenik a kliens fogalma: a kliens kizárólagos feladata a felhasználóval való kapcsolattartás (elvileg persze az üzleti logikából megvalósíthatna részleteket, de ekkor a verziókövetési nehézségek mellett elvesztenénk a vékony kliensek használatának lehetõségét). Egyazon alkalmazás éppen ezért többféle felhasználói felülettel is rendelkezhet, amelyek mindaddig használhatóak maradnak, amíg az üzleti logika által biztosított interfészeket nem csökkentjük
Az EJB komponensek számára nem biztosít futtatási környezetet a belõlük összeállított alkalmazás. Az Enterprise Bean-ek és az õket futtató JVM közé az úgynevezett Bean Container lép, aminek semmi köze magához az összeállított alkalmazáshoz, helyette viszont egy sok szolgáltatást nyújtó szabványos környezetet biztosít
Elosztott rendszerrõl lévén szó az EJB architektúra biztosítja az alkalmazások skálázhatóságát
Az Enterprise Bean-ek háromfélék lehetnek:
Session Bean
Egy Session Bean arról kapta a nevét, hogy olyan, mint egy párbeszédes elérés: nem megosztható (egy Session Bean egyetlen egy klienshez tartozhat csak), nem perzisztens (állapota ugyan lehet, de miután a kliens bontja a kapcsolatot, az állapot többet nem értelmezhetõ). A Session Bean-ek tekinthetõk egy alkalmazás tulajdonképpeni kliens-részének, hiszen a felhasználónál csak egy terminál-szerû mûködést produkáló programrészlet található, ami igazából a Session Bean-eket mûködteti.
Amikor használható:
Ha egy Bean-hez egyidõben csak egy kliens fér hozzá
Ha egy Bean állapota (ha egyáltalán értelmezhetõ ilyen) nem fontos, hogy perzisztens legyen
Entity Bean
Egy többrétegû alkalmazásnak két vége van; a klienseket a Session Bean-ek képviselik. Az Entity Bean-ek az alkalmazás másik végén található objektumokat képviselik, amelyek valamilyen perzisztens tárolóból származnak (tipikus példa: egy relációs adatbázisból). Egy Entity Bean többnyire megfeleltethetõ egy táblának az adatbázisban, és minden egyes példánya a tábla egy sorát reprezentálja.
Az Entity Bean-ek jelentõsen eltérnek a Session Bean-ektõl:
Perzisztens állapottal rendelkeznek
Állapotuk mentését illetve visszaállítását saját maguk is végezhetik (Bean Managed Persistence)
De a már említett szabványos környezet (a Bean Container) is biztosít hozzá támogatást (Container Managed Persistence)
Több kliens megosztva használhatja õket
Emiatt szükséges, hogy Entity Bean-ek résztvételével tranzakciók is lebonyolíthatóak legyenek, a Bean Container ezt is támogatja
Az Entity Bean-ek rendelkeznek egyedi azonosítóval, egy elsõdleges kulccsal, ami alapján kereshetõ (az EJB QL segítségével)
Entity Bean-ek között értelmezhetõk relációk. Ez saját perzisztencia-kezelés esetén úgy mûködik, ahogy megírjuk, a környezet által kezelt esetben container-managed relationship-rõl szokás beszélni, és a relációs adatbázisok szerkesztésénél megszokothoz hasonló módon hozhatjuk létre õket
Amikor Entity Bean-t érdemes használni:
Ha egy Bean kevésbé eljárást, inkább valamely üzleti objektumot képvisel
Ha egy Bean állapotának meg kell õrzõdnie
Üzenetvezérelt (Message-Driven Bean)
Egy eseményvezérelt Enterprise Bean (egyelõre csak) JMS üzenetekre reagál. Ezek az üzenetek viszont bárhonnan származhatnak (a JMS nem követeli meg a J2EE technológia használatát).
Tulajdonságaiban egy állapot nélküli Session Bean-re emlékeztet:
Nem õriz semmilyen információt semelyik kliensrõl
Ettõl még lehetnek tagváltozói (például adatbázis-kapcsolat fenntartására)
Minden példánya egyenértékû, egy beérkezõ üzenet bármelyikhez továbbítható
Egyazon üzenetvezérelt Bean számos kliens üzeneteit fogadhatja
Üzenetvezérelt Bean-ek használata akkor indokolt, amikor üzenetek aszinkron fogadására van szükség (ekkor a Bean Container egyszerûen meghívja a Bean onMessage metódusát, Session és Entity Bean viszont csak explicit várakozással képes üzenetet fogadni). Egy üzenetvezérelt Bean ugyanis másra nem is alkalmas (a BeanContainer-rel való kapcsolattartáshoz szükséges Bean interfészen kívül más interfésszel nem rendelkezik).
Enterprise Bean felülete
Egy Enterprise Bean (a meglehetõsen speciális üzenetvezérelt esetet leszámítva) az elérési módjától (távoli és/vagy lokális) függõen számos interfésszel rendelkezik:
Home interfész: életciklus-felügyeletet biztosító metódusok (létrehozás/eltávolítás, keresés), ezeket a Bean Container használja
Remote interfész: a Bean funkcionális részéhez való hozzáférést biztosító felület
A szóhasználat elosztott rendszert sejtet, de ugyanezt az interfész-párost lokális eléréshez is biztosíthatjuk: Local és LocalHome interfész néven. Tipikusan egymással a Bean Container által kezelt relációban álló entity bean-ek használatakor szokás implementálni.
Az EJB használata
Az EJB egy JAR csomagban szállítható, ami a következõket tartalmazza:
Deployment descriptor: egy XML-formába öntött részletes leírás az adott Bean-rõl
A Bean neve (konvenció: <név>EJB)
Az implementációs osztály neve (<név>Bean)
Home interfész(-ek) neve (<név>Home ill. Local<név>Home)
Funkcionális interfész(-ek) neve (<név> ill. Local<név>)
Típus (entity, stb)
Reentráns-ság (csak entity bean esetében értelmezett)
Session bean: állapottal rendelkezik vagy sem
Session bean: a bean vagy a Container választja-e szét a tranzakciókat
Entity bean perzisztenciakezelése
Entity bean elsõdleges kulcs-osztálya
Entity bean, Container-alapú perzisztencia: a Bean mely mezõit kell menteni
Szükséges környezeti bejegyzések
Használni kívánt egyéb Bean-ek Home-interfészei
Biztonsági szerepkör
Maga az implementációs osztály
Interfészek
Szükséges egyéb osztályok (kivételek, stb)
Látható, hogy egy Enterprise Bean létrehozása nem sokban tér el bármilyen Java osztály elõállításától, igazából csak egy szabványos formátumú leírást kell csupán nyújtani róla, és máris elõáll egy elosztott környezetben (is) használható, számos szolgáltatást akár automatikusan használó (pl. ha a Container-re bízzuk a tranzakciók és a perzisztencia kezelését) szoftverkomponens.
Irodalomjegyzék:
JavaTM 2 SDK, Standard Edition Documentation
Java Remote MethodInvocation Specification (rmi-spec-1.3.pdf)
JavaBeans (beans.101.pdf)
Enterprise JavaBeans Specification, Version 2.0 (ejb-2_0-fr2-spec.pdf)
JavaTM 2 Enterprise Edition Developer's Guide (devguide1_2_1.pdf)
The J2EE Tutorial (j2ee-1_3-doc-tutorial-draft3.pdf)