Pod kapotou Enterprise JavaBean (EJB)

18. 3. 2010
Doba čtení: 8 minut

Sdílet

V poslední podčásti seriálu o JBossu věnované EJB komponentám si ukážeme, jakým způsobem je možné použít dynamický proxy objekt, seznámíme se se způsobem předávání volání na server a ukážeme si, jak je možné upravit existující konfiguraci EJB kontejneru. Napíšeme si také vlastní zachytávače volání.

Úvod

Minule jsme si nastínili, jak se používá dynamický proxy objekt, který deleguje volání EJB z klienta na server. Dnes si o této úmorné cestě našeho volání povíme něco víc a ukážeme si ji na příkladu. Také prozkoumáme možnosti nastavení EJB 3 a v dalším příkladu si naprogramujeme vlastní zachytávače volání EJB komponent.

Nastavení EJB 3 můžeme nalézt ve své serverové konfiguraci v adresáři deploy. Jsou to soubory začínající ejb3. Nejzajímavějším z těchto souborů je ejb3-interceptors-aop.xml. Tento soubor obsahuje většinu konfigurace až na výjimky jako clusterová cache, remoting a deployer. To je rozdíl oproti EJB verze 2, která se konfiguruje pomocí souboru standardjboss.xml v adresáři conf. Nemá však nic společného s verzí 3.

Každé volání EJB probíhá přes řadu tzv. interceptorů (což lze přeložit jako zachytávač). Jedná se o použití návrhového vzoru Chain of responsibility. Ten můžete znát například v podobě servlet filtrů. Každý interceptor nebo továrna na interceptory se musí definovat pomocí tagu <interceptor …/>, který také definuje rámec působnosti – tedy kolik instancí interceptoru se má kdy vytvořit (např. PER_VM). Rozdílný je ovšem způsob zápisu nastavení pro stranu klienta a serveru. Podívejme se teď na obě strany zvlášť.

Volání na straně klienta

Jako handler (z terminologie návrhového vzoru, neplést s InvocationHan­dler) zde slouží dynamický proxy objekt (viz také java.lang.reflec­t.Proxy), který jsme zmínili v minulém dílu.

konfiguraci je pak nastaven řetěz interceptorů, které mohou s vaším voláním manipulovat. Ten je uveden v elementu <stack …/> a ve výchozí konfiguraci je nastaven pro různé typy EJB.

Posledním interceptorem v řadě obvykle bývá ten, který vaše volání deleguje na server. Tedy má na starost zařídit komunikaci se serverem, předat mu jméno metody, všechny použité parametry a převzít zpět návratovou hodnotu. Podívejme se nyní na další typické interceptory na straně klienta.

IsLocalInterceptor

Tento interceptor zjišťuje, zda se nejedná o lokální volání (kontejner běží v aktuálním JVM). V takovém případě je totiž možné volání předat přímo a není nutné spouštět další interceptory na straně klienta. Pokud tedy chceme nějaký interceptor vyvolat za všech okolností, musí být definován před tímto.

SecurityClien­tInterceptor

Pro potřeby autentizace klienta je potřeba do metadat volání vložit potřebné údaje, kterými jsou identita uživatele (Principal) a na implementaci závislé přihlašovací údaje (Credential). Objekt Credential může obsahovat nezašifrované heslo. Proto je potřeba další zabezpečení na úrovni protokolu. Úkolem bezpečnostního interceptoru je také obnovení původního bezpečnostního kontextu po návratu z volání.

ClientTxPropa­gationIntercep­tor

Dalším důležitým aspektem volání je zachování transakčního kontextu. Musí se proto zjistit transakční propagační kontext a ten nastavit do metadat volání.

RemotingProxy

Snad nejdůležitějším interceptorem je ten, který předává volání na server. Ten sám musí pod sebou mít vrstvu, která umí jako klient k serveru přistoupit. Volání je totiž možné předávat různými způsoby (RMI, HTTP…). Jako příklad přivedený do extrému by se dalo uvést zaslání volání emailem.

Další interceptory

Jak je vidět z předchozích odstavců, nejsou interceptory samy o sobě nic zvláště složitého. Většina důležité aplikační logiky je skryta jinde a interceptory většinou pouze obohacují volání o nějaká ta metadata.

Existují ještě další interceptory pro práci v prostředí clusteru, pro logování volání atd. Je dokonce možné si napsat svůj vlastní interceptor.

Volání na straně serveru

Nastavení na straně serveru je zapsáno pomocí jazyka AOP Pointcut (více např. v dokumentaci). Je potřeba zdůraznit, že se zde využívá JBoss AOP pouze k načítání konfigurace interceptorů. Rozhodně nedochází k jejich reálnému nasazování pomocí jakékoliv manipulace s bytekódem. Přesvědčit se o tom můžete ve zdrojovém kódu. Může se nabízet otázka, jaký to má smysl. Původně totiž byl EJB 3 kontejner vyvíjen se záměrem nasazení obecnějších komponent (např. POJO), kde by se aspektově orientovaného programování muselo opravdu použít.

Nastavení na straně serveru se provádí pomocí elementu <domain …/>. Ve výchozím stavu je definována doména pro každý typ EJB. V příkladu níže se podíváme na možnost změny konfigurace.

Interceptory na straně serveru jsou většinou funkčně komplementární těm na straně klienta.

Stateless/Stateful instance interceptor

Jedním z hlavních interceptorů pro session EJB je ten, který vybere patřičnou instanci EJB, na které se má volání provést. V případě stateless je možné vybrat téměř libovolnou. Naopak v případě stateful se musí věnovat pozornost tomu, aby instance měla příslušný stav.

Transakční interceptor

Zcela analogicky tomu na straně klienta, musí transakční interceptor na straně serveru zajistit běh volání ve správném transakčním kontextu. Dalším interceptorem souvisejícím s transakcemi je ten, který zajišťuje kontrolu splnění podmínek nastavených transakčních atributů u jednotlivých metod.

Bezpečnost

Na straně serveru probíhá autentizace a autorizace příchozích volání. Je také obslouženo použití anotace @RunAs.

Tento popis interceptorů na straně serveru není rozhodně vyčerpávající, ale poskytuje základní odrazový můstek pro ty, kteří budou chtít proniknout hlouběji do architektury EJB 3 v JBossu a snad se i stát aktivním vývojářem.

Příklad použití proxy objektu

Nejdříve se podívejme na jednoduchý příklad, který nám ukáže, jak můžeme využít proxy objekt. Předpokládejme, že mám nějaké jednoduché rozhraní, a chci vytvořit objekt, kterým jej implementuje. Rozhraní může vypadat třeba takto:

public interface MyInterface {
   public void helloWorld(String toWhom);
   public String silentHelloWorld(String toWhom);
   public void multiHelloWorld(String person, String dog, String car);
}

Musím si nachystat InvocationHan­dler, který bude zachytávat všechna volání mého objektu. Nezapomeňte, že opravdu všechna volání. Nejenom volání metod uvedeného rozhraní, ale i těch, které každý objekt dědí ze třídy Object (např. toString()). Velice jednoduchý handler je ukázán níže.

public class MyInvocationHandler implements InvocationHandler {
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("\n==== Byl zavolán proxy objekt ====");
      System.out.println("Proxy objekt: " + proxy.getClass().getName());
      System.out.println("Metoda:       " + method.getName());
      System.out.println("Argumenty:    " + Arrays.toString(args));

      if (method.getName().equals("toString")) {
         return proxy.getClass().getName();
      } else {
         return "kuk";
      }
   }
}

Nyní můžeme v podstatě přímo podle dokumentace vytvořit proxy objekt a zkusit jej použít.

   ...
   InvocationHandler handler = new MyInvocationHandler();
   MyInterface iface = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
           new Class[] { MyInterface.class }, handler);
   System.out.println("Proxy objekt: " + iface);
   iface.helloWorld("Pepa");
   iface.multiHelloWorld("Pepa", "Karl von Bahnhoff", "Fanouš");
   System.out.println("Návratová hodnota: " + iface.silentHelloWorld("Zdeněk"));
   ...

Na výstupu můžeme sledovat, co se ve skutečnosti děje, a jak je naše volání zachytáváno. Na tento příklad není potřeba aplikační server a spustíme ho pomocí ant run. Jeho síla tkví v tom, že tímto způsobem můžeme pro program zcela transparentně měnit chování a volání delegovat na libovolný objekt podle dané situace (jak toho využívají právě EJB).

Příklad použití vlastních interceptorů

Další příklad je založen na jiném, který poskytují přímo vývojáři EJB 3 a ukazuje uživatelské nastavení interceptorů. Osobně jsem jej zkoušel na JBoss AS 6.0.0.M2 a tomu je také přizpůsoben build.xml a soubory s nastavením interceptorů.

Ještě dříve než nastartujeme server, je potřeba nastavit build.properties a zavolat ant, který na server nainstaluje modifikovanou verzi nastavení (viz výchozí cíl ejbjar). V této modifikované verzi se nainstaluje TeacherAopInter­ceptor na stranu klienta do elementu <stack name=„Stateles­sSessionClien­tInterceptors“ …/>. Na stranu serveru se do dómeny Stateless Bean nainstaluje BadMathAopInter­ceptor. Můžete si všimnout, že je navázán pouze na volání metody subtract(). Toto uživatelské nastavení se projeví při použití CustomizedCal­culatorBean.

Další možností je vytvoření uživatelského seznamu interceptorů jak na straně klienta, tak na straně serveru. To je možné vidět v soubor custom-ejb3-interceptors.a­op.xml. Použití těchto uživatelských nastavení můžete vidět ve třídě CustomDomainCal­culatorBean za pomoci anotací @AspectDomain a @RemoteBinding. V uživatelské konfiguraci je odstraněno několik interceptorů (bezpečnost, transakce, clustering), BadMathAopInter­ceptor je navázán na metodu add() a velikost zásobníku stateless session EJB (tzv. pool) je nastavena na 1 (viz @org.jboss.ej­b3.annotation­.Pool).

Podívejme se nyní na klientskou část. Program po nastartování serveru spustíme pomocí ant run a můžeme sledovat jeho výstup (varovné zprávy o nastavení log4j byly vypuštěny).

     [java] Testing CustomizedCalculatorBean ...
     [java] 1 + 1 = 2
     [java] Wrong Answer!!
     [java] 1 - 1 != 1
     [java] Saving 123 ...
     [java] Recalling 0
     [java] Recalling 123
     [java] Recalling 0
     [java] Recalling 0
     [java] Recalling 0

Nejdříve se testuje CustomizedCal­culatorBean, který nám ukáže změnu nastavení v globálním souboru ejb3-interceptors-aop.xml. Vidíme, že při sčítání funguje vše normálně. Při odečítání se projeví BadMathAopInter­ceptor na serveru a změní výsledek. Vypíše o tom také hlášení na konzoli aplikačního serveru.

14:49:11,990 INFO  [org.jboss.tutorial.configuration.BadMathAopInterceptor] ***
Intercepting in BadMathAopInterceptor in public int org.jboss.tutorial.configuration.bean.CalculatorBean.subtract(int,int)

Následně v TeacherAopIn­terceptor dojde k zachycení návratové hodnoty (volání metody invokeNext() se nachází před výkoným kódem interceptoru) a výpisu varování Wrong Answer!!. Pak se do bezestavové session bean uloží hodnota a několikrát se přečte. Je vidět, že v zásobníku je skutečně více instancí komponenty, protože jsme získali zpět i jinou hodnotu než uloženou. Toto je pochopitelně normální chování pro bezestavové komponenty.

Dál se testuje CustomDomainCal­culatorBean, který využívá nastavení ze souboru custom-ejb3-interceptors-aop.xml.

     [java] Testing CustomDomainCalculatorBean ...
     [java] Wrong Answer!!
     [java] 1 + 1 != 3
     [java] 1 - 1 = 0
     [java] Saving 123 ...
     [java] Recalling 123
     [java] Recalling 123
     [java] Recalling 123
     [java] Recalling 123
     [java] Recalling 123

Vidíme, že sčítání funguje špatně, protože zafungoval BadMathAopInter­ceptor a opět vypsal zprávu na konzoli serveru.

bitcoin školení listopad 24

14:49:17,063 INFO  [org.jboss.tutorial.configuration.BadMathAopInterceptor] ***
Intercepting in BadMathAopInterceptor in public int org.jboss.tutorial.configuration.bean.CalculatorBean.add(int,int)

Odečítání funguje tak jak má. Zvláštní situace ale nastává v případě uložení hodnoty. Při jejím čtení stále dokola získáváme tu samou uloženou hodnotu. Je to tím, že jsme velikost zásobníku instancí nastavili na 1. Dalo by se s nadsázkou říct, že jsme z bezestavové komponenty učinili stavovou. To platí samozřejmě jen za velmi omezených podmínek a rozhodně to nelze doporučit jako správné řešení.

Závěr

Tímto dílem můžeme uzavřít kapitolu o EJB a podívat se na další JBoss projekt. Možná byly EJB komponenty představeny pro někoho zbytečně podrobně, ale je to základ na kterém celý JBoss vyrostl. Vždyť původní název serveru byl EJBoss a byl primárně EJB kontejnerem. Na co se podívámě příště? S upozorněním právo na změnu vyhrazeno to budou JBoss Transactions.

Autor článku