Hlavní navigace

Komponenta Session beans v JBoss

22. 4. 2008
Doba čtení: 12 minut

Sdílet

V tomto dílu seriálu se podíváme na jeden ze tří typů business komponent, na session beans. Tento typ komponent jsme v minulých dílech již několikrát používali, aniž bychom je nějak podrobněji zkoumali. Ukážeme si možnosti session beans ve verzi 3.0 a jak je s jejich pomocí možné stavět enterprise aplikace.

Mám session bean, ale co s ní?

Session bean je komponenta, která umožňuje vykonat určitou práci pro klienta, který si to vyžádá. Jedná se o objekt, který obsahuje aplikační logiku nějakého business procesu. Instanci této komponenty je možné použít vícekrát pro více klientů. Vždy však maximálně pro jednoho klienta současně. Session bean může provádět akce jako určování ceny, zadávání objednávky, komprese dat, bankovní transakce, obchodování na burze, databázové operace, složité kalkulace atd.

Komponenty typu session bean mají relativně krátkou životnost. Na žádost od klienta musí aplikační server vytvořit instanci této komponenty, zavolat na ní požadovanou operaci a zase ji může v klidu zahodit. Z pohledu klienta začíná session bean existovat poté, co na ni klient získá odkaz, a končí po uzavření sezení. Nikde není řečeno, co se session bean děje před a po ukončení klientského sezení.

Komponenty typu session bean nejsou persistentní. To znamená, že nejsou ukládány (na rozdíl od entity beans) do datového úložiště. Session beans samy mohou pochopitelně provádět databázové operace, ale samotná bean ukládána pro dlouhodobé uchovávání není.

Všechny business komponenty (Enterprise Beans) vedou konverzaci s klienty. Konverzace je interakce mezi klientem a komponentou a sestává z několika volání metod komponenty. Konverzace zaštiťuje provedení určitého business procesu nad komponentou, jako nákup zboží po internetu nebo zadání údajů o novém zákazníkovi. Někdy může nastat situace, kdy je žádoucí během několika volání různých metod udržovat určitý stav konverzace na straně komponenty (např. již zadané jméno a příjmení nového zákazníka z předchozí stránky formuláře). Tento požadavek rozděluje session beans na dva základní typy

  • bezestavové (stateless) session beans
  • stavové (stateful) session beans

Bezestavové session beans

Bezestavové (stateless) session beans slouží pro obsloužení konverzace, která sestává z jednoho volání a není tedy potřeba uchovávat nějaký konverzační stav mezi voláními více metod. Klient tedy nemůže ze svého pohledu vidět uchovávání nějakého stavu mezi jednotlivými voláními metod této komponenty.

Aplikační server, konkrétně jeho část – EJB (Enterprise Java Beans) kontejner, může odstranit a znovu vytvořit bezestavovou session bean okamžitě po každém volání metody. Tím dojde ke ztrátě jakékoliv informace uložené v takové komponentě. Klient tedy musí při každém požadavku (volání) na bezestavovou session bean předávat všechny potřebné informace.

Bezestavovost znamená skutečně žádný konverzační stav mezi jednotlivými voláními metod. Bezestavová session bean může uchovávat stav, který není závislý na klientovi. Takový stav může být například odkaz na nějakou továrnu spojení (ConnectionFactory).

Příkladem bezestavové session bean může být kompresní mechanismus, který počítá složité rovnice pro kompresi videa. Klient může takové komponentě předat blok dat, session bean provede kompresi a vrátí zpět klientovi data ve zkomprimované metodě. Business proces zde vyžadoval právě jedno volání a session bean si neuchovala na základě tohoto volání žádný stav.

Protože bezestavové session beans neuchovávají žádný konverzační stav, jsou si všechny rovné. Všechny instance jsou od sebe k nerozeznání a je proto jedno, kterou instanci poskytneme kterému klientovi. S výhodou tak můžeme mít sdílenou zásobu (angl. pool) session bean a podle požadavků je přidělovat klientům, jak ukazuje následující obrázek.

JBoss pool

Sdílená zásoba bezestavových session bean

Stavové session beans

Stavové (stateful) session beans si uchovávají svůj konverzační stav po celou dobu sezení s jedním klientem. Příkladem využití takových komponent může být internetový obchod. Zákazník prochází internetovým obchodem a přidává si různé zboží do nákupního košíku. Na konci chce na základě obsahu nákupního košíku vytvořit objednávku. Obě tyto operace (přidání zboží do košíku a vytvoření objednávky) může obsluhovat stejná stavová session bean. Podmínkou je, že si mezi jednotlivými voláními metod můsí pamatovat obsah nákupního košíku – musí tedy udržovat určitý konverzační stav.

Pokud tedy během provádění metody session bean dojde ke změně jejího stavu, musí být tento stav pro daného klienta zachován. Specifikace ovšem neříká, kam se tento stav má ukládat, a neklade ani podmínku na to, že tento stav musí přeží pád či restart aplikačního serveru. V případě JBossAS jsou všechny uložené stavy session beans během startu serveru vymazány, protože není možné určit, za jakých okolností byly pořízeny, zda ještě je k dispozi příslušná implementace atd.

Protože je nutné udržovat konverzační stav, není možné tak jednoduše používat sdílenou zásobu instancí stavových session beans a libovolně je přidělovat klientům. Je potřeba nějakým způsobem uchovávat stav komponent tak, aby byl příslušnému klientovi vždy k dispozici. Limitem jsou také zdroje dostupné pro stavovou paměť.

Řešení je jednoduché. Stav každé stavové session bean se dočasně ukládá do nějakého persistentního datového úložiště. Před každým voláním pak dojde k výběru libovolné session bean ze sdíleného zásobníku, načtení stavu pro daného klienta a zavolání příslušné metody. Pokud je potřeba v paměti uvolnit místo pro konverzační stav jiného klienta, dojde opět k uložení stavu do úložiště. Těmto procesům se říká aktivace a pasivace. Ve stavové session bean můžeme na tyto události reagovat v metodách, které jsou označeny anotacemi @PrePassivate a @PostActivate. Vhodné je v těchto metodách ošetřit například uzavírání a otevírání síťových nebo databázových spojení.

JBoss passive

Pasivace SFSB

Aktivace SFSB

Nabízí se ale otázka, jak se stav stavové session bean může ukládat. EJB kontejner musí podporovat serializaci nebo kompatibilní způsob ukládání vlastností session bean. Vaše session bean proto musí umožňovat serializaci těch vlastností, které chce uchovat v rámci konverzačního stavu. Konkrétně se jedná o

  • primitivní datové typy
  • Java objekt (potomek třídy java.lang.Object), který je označen jako serializovatelný

Navíc umožňuje AS uložit stav vlastností typu

  • odkaz na jiný lokální nebo vzdálená rozhraní business komponent
  • odkaz na objekty SessionContext, UserTransaction, EntityManager, EntityManagerFactory
  • JNDI jmenný kontext

Verba movent, exempla trahunt

Slova dojímají, příklady táhnou. Podívejme se tedy na funkční příklady obou typů session beans a jak k nim přistoupit ze strany klienta…

Bezestavová session bean

S bezestavovými session beans jsme se již setkali v předchozích dvou dílech seriálu. Na konkrétní příklady bych si proto dovolil odkázat tam. Jenom si připomeňme strukturu session bean. Jedná se o klasickou třídu, která je označená anotací @Stateless. Kromě toho musí implementovat lokální, vzdálené nebo obě tato rozhraní. Lokální a vzdálené rozhraní se pozná podle anotace @Local, příp. @Remote, před definicí rozhraní. Je také možné použít neoznačené rozhraní a jeho typ specifikovat anotací přímo před session bean. Například @Remote(cz.root.jboss.ejb.MyBeanInterface). Takto je možné použít totožné rozhraní jako lokální i vzdálené. Tato vlastnost je však specifická pro JBossAS a ve specifikaci se o ní nemluví.

Proč je nutné implementovat nějaké rozhraní? Klient na svojí straně nevidí přímo třídu implementující business kód, ale pouze zástupný proxy objekt, který implementuje dané rozhraní. Rozhraní tedy slouží jako deskriptor funkcionality, kterou business komponenta nabízí. Rozdíl mezi lokálním a vzdáleným rozhraním je ve způsobu volání.

Přes lokální volání je možné v rámci jedné instance virtuálního stroje (JVM) pracovat přímo s business komponentou. Použije de facto klasické volání metody, jako pro každý jiný objekt. Podle specifikace není možné takto pracovat s komponentami mimo EAR archív s aktuální třídou. JBossAS však standardně umožňuje v rámci optimalize použít lokální volání přednostně, pokud se daná komponenta vyskytuje ve stejném JVM. V takovém případě nemusí být lokální rozhraní vůbec definované (stačí pouze vzdálené).

Vzdálené volání umožňuje volat business komponentu i v rámci jiného JVM. Při volání přes zástupný proxy objekt dochází k vytvoření kontextu volání ( InvocationContext), který se serializuje a posílá po síti na server. Celý koncept je poněkud složitější a nad rámec tohoto dílu.

Při použití JBossAS nemá ve většině případů smysl implementovat jiné rozhraní než @Remote, protože se při standarním nastavení automaticky použije lokální volání, jakmile je to možné (klient i server běží v rámci jednoho JVM).

Stavová session bean

Musíme mít na paměti, že o konverzační stav přijdeme při pádu nebo restartu AS. Není proto dobré ukládat si takto důležité informace. Vhodným kandidátem pro takto „odkládané“ informace může být již zmíněný nákupní košík, rozpracovaný formulář, mezivýsledek výpočtu atd. Pokud dojde k pádu serveru, tak stejně nemůžeme pokračovat ve zpracování požadavků, takže ztráta konverzačního stavu není až tak bolestivá (s ohledem na typ uložených dat).

Jako příklad stavové session bean si ukážeme jednoduchou kalkulačku s pamětí (calculator.tgz).

public interface Calculator {
    public double add(double x, double y);
    public double subtract(double x, double y);
    public double multiply(double x, double y);
    public double divide(double x, double y);
    public void memoryAdd(double x);
    public void memorySubtract(double x);
    public double memoryRead();
    public void memoryClear();
    public void close();
}
import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateful;

@Stateful
@Remote(Calculator.class)
public class CalculatorBean implements Calculator {
    private double memoryCell = 0.0;

    public double add(double x, double y) { return x + y; }

    ...
    @Remove
    public void close() {
        // uvolnit zdroje získané během života této bean
    }
}

Existují ještě další anotace, kterými můžeme označit metody v session bean. Ty se pak zavolají podle příslušné situace.

@PostConstruct
Metoda se zavolá po provedení dependency injection a před zavoláním jakékoliv business metody.
@PreDestroy
Metoda se zavolá po vykonání všech metod označených  @Remove.

Můžeme použít přiložený build skript pro Ant a příklad si zkompilovat ( ant build, nebo pouze ant). Pro bezchybnou kompilaci věnujte pozornost nastavení v souboru build.properties. Výsledný soubor calculator.jar jednoduše okopírujeme (nebo použijeme ant deploy) do adresáře deploy naší oblíbené konfigurace. Nyní se podíváme na vystavenou session bean v JMX konzoli.

Krátké nahlédnutí do jmx-console

Na adrese http://localhost:8080/jmx-console naleznete běžící JMX konzoli. Po jejím otevření můžete vyhledat objekt jboss.j2ee:jar=calculator­.jar,name=CalculatorBean,ser­vice=EJB3. Po kliknutí na něj uvidíte například, kolik instancí má server právě vytvořených, kolik konverzačních stavů bylo odloženo (pasivováno) aj.

Klient na stejném virtuálním stroji

Klientem na stejném JVM může být například servlet, který jsme si ukázali v minulém dílu seriálu. Podle specifikace by měl JEE 5 kompatibilní server podporovat tzv. dependency injection čili snadné vložení odkazu na session bean jen pomocí anotace. Jak bylo zmíněno již v minulém dílu, není tato vlastnost v současné Beta verzi AS funkční. Ukázku použití však naleznete v minulém dílu a můžete ji s plnou verzí AS vyzkoušet.

Klient v jiném virtuální stroji

Naprogramujeme si samostatného tlustého klienta, který bude využívat klasické JNDI vyhledávání (lookup) komponent a bude pracovat s naším kalkulátorem. Klient se bude spouštět v samostatném JVM. Můžete spustit několik instancí klienta naráz, abyste viděli ukládání konverzačního stavu.

public class Client {
    Calculator calc;

    private void getCalculator() {
        try {
            InitialContext ic = new InitialContext();
            calc = (Calculator) ic.lookup("CalculatorBean/remote");
        } catch (NamingException ne) {
            System.err.println("Nelze inicializovat session bean. Zkontrolujte jndi.properties. " + ne);
        }
    }

    private void doTest() throws IOException {
        calc.memoryAdd(Math.random() * 100);
        System.out.println("MR = " + calc.memoryRead());
        System.out.println("Spusťte další instanci klienta a uvidíte jinou hodnotu. Pro pokračování stiskněte ENTER!");
        System.in.read();
        System.out.println("5 + M = " + calc.add(5, calc.memoryRead()));
        System.out.println("MC");
        calc.memoryClear();
        System.out.println("MR = " + calc.memoryRead());
    }

    public static void main(String[] args) throws IOException {
        Client c = new Client();
        c.getCalculator();
        c.doTest();
    }
}

Klientskou aplikaci můžete spustit pomocí ant run. Klient bude čekat na váš vstup, abyste mohli spsutit několik instancí a pozorovat jejich vzájemně rozdílné konverzační stavy.

Před spuštěním ještě nahlédněte do souboru jndi.properties a ověřte, že je správně nastavena adresa vašeho AS.

Veškeré knihovny, které by měl náš klient potřebovat, jsou umístěny v adresáři client v instalaci AS. Nemusíme tedy ke klientům nahrávat nějaké součásti serveru (serverové verze knihoven).

Služby poskytované pro session beans aplikačním serverem

Timer

Služba časovače umožňuje v rámci session bean nastavit časový alarm a k němu obslužnou metodu (označenou anotací @Timeout). Nejjednoduší bude si ukázat rovnou příklad.

@Stateless
@Remote(ExampleTimer.class)
public class ExampleTimerBean implements ExampleTimer {
    private @Resource SessionContext ctx;

    public void scheduleTimer(long milliseconds) {
        ctx.getTimerService().createTimer(new Date(new Date().getTime() + milliseconds), "Hello World");
    }

    @Timeout
    public void timeoutHandler(Timer timer) {
        System.out.println("Nastal čas: " + timer.getInfo());
        timer.cancel();
    }
}

Metodou createTimer() vytvoříme časovač. Můžeme předat libovolný serializovatelný objekt, který pak můžeme z časovače získat. Můžeme tak jednou @Timeout metodou obsloužit všchny časovače a rozlišit je od sebe. Je možné vytvořit jednorázovy časovač, nebo časovač, který se neustále v určitém intervalu opakuje (metoda createTimer() má několik variant). Slušná session bean časovač také zastaví v případě, že už ho nepotřebuje. Zde je to poněkud zbytečné, protože časovač je jednorázový a vyprší sám. V případě intervalového časovače je ale volání metody cancel() pro zastavení potřeba.

V příkladu vidíme ještě jednu zajímavou techniku, a tou je dependency injection (vložení závislosti). Anotací @Resource je označena proměnná ctx typu SessionContext. Aplikační server se postará o to, abychom v ní měli odkaz na očekávaný objekt. Nemusíme sami provádět JNDI vyhledávání.

Další informace můžete najít v JBoss EJB3 tutorial. Některé informace jsou bohužel už mírně zastaralé, ale jako odrazový můstek poslouží jistě dobře.

Invocation Interceptors

V doslovném překladu se jedná o zachytávač volání, odchytávač volání nebo dokonce přepadový stíhač. Zde se budeme držet zažitého anglického termínu interceptor. V podstatě jde o to, že je možné zachytit volání libovolné business metody dané session bean. Toto zachycení můžu provést společně v jedné metodě dané session bean.

@Stateless
public class BusinessServiceBean implements BusinessService {

    public void doService() {...}
    ...

    @PostActivate
    initializeDB() {...}

    @AroundInvoke
    doChecks(InvocationContext ctx) {
        checkDBAccess();
        checkSecurity();
    }
}

V uvedeném příkladu dojde k vyvolání metody označené anotací @AroundInvoke před voláním každé business metody (v tomto případě metody doService()).

Druhou možností je použití specializovaných zachytávačů (interceptorů) pro jednotlivé metody. V takovém případě je možné pro jednu metodu definovat libovolný počet interceptorů, a ty budou vyvolány v pořadí, v jakém jsou uvedeny v anotacích před metodou. Dokonce je možné zařadit několik interceptorů na úroveň třídy (anotace @Interceptors). Jejich chování je pak stejné jako v předchozím případě, pouze kód můžeme mít v jiné třídě.

@Stateless
@Interceptors({MyDummyInterceptor.class}) // interceptor na úrovni třídy pro všechny metody
public class BusinessServiceBean implements BusinessService {

    @Interceptors({AnotherInterceptor.class}) // na úrovni metody
    public void doService1() {...}

    @Interceptors({AnotherInterceptor.class, YetAnotherInterceptor.class})
    public void doService2() {...}
    ...
}

Samotná třída s implementací interceptoru pak musí pouze obsahovat metodu označenou anotací @AroundInvoke.

root_podpora

public class MyDummyInterceptor {
    @AroundInvoke
    intercept(InvocationContext ctx) {
        ...
    }
}

Více informací o interceptorech v JBossAS můžete najít opět v JBoss EJB3 tutorial.

Závěr

Některá tajemství session beans jsme dnes odhalili a některá nám stále zůstávají skryta. Není divu, protože do jednoho dílu seriálu není možné vměstnat naráz tolik informací. Pro úvodní zasvěcení do problému by měly tyto informace postačovat a na pokročilejší techniky se podíváme ještě v některém z dalších dílů.

Byl pro vás článek přínosný?