DWR - AJAX knihovna pro remotování Java objektů

7. Září 2007

S knihovnou DWR (Direct Web Remoting) jsem se setkal před několika měsíci při brouzdání internetem. Tehdy jsem si vyzkoušel příklad ze stránky Getting Started with DWR a nepracoval jsem s ní až do doby před dvěma týdny, kdy jsme začali pracovat na zakázce, která si o její použití přímo říkala.

K čemu knihovna DWR slouží

Pomocí DWR můžete volat Javovský kód přímo z Javascriptu z prostředí internetového prohlížeče na klientském počítači. DWR se samo postará o vykonání asynchronního požadavku na server a převod objektů z javovského světa do javascriptového.

Kromě toho má DWR sadu Utilit, které pomáhají v manipulaci s odesíláním a zpracováním příjímaných dat – plnění selektu daty, přidávání řádků do tabulky atd. V této části knihovny ovšem nespatřuji její těžiště, a tak se o ní více zmiňovat nebudu. Osobně k práci se stránkou používáním knihovnu jQuery.

Nastavení na straně serveru

DWR je nyní nachází ve verzi 2.0 a kromě kromě samostatného použití nabízí integraci do některých populárních frameworků – například Spring Framework, JSF, Struts. Základem je definování DWR servletu v deployment deskriptoru:

<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <display-name>DWR Servlet</display-name>
  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
  <init-param>
     <param-name>debug</param-name>
     <param-value>true</param-value>
  </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

Pro integraci se Spring Frameworkem toto není potřeba. Stačí do aplikačního kontextu přidat nové mapování a kontroler:

<bean id="dwrUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="alwaysUseFullPath" value="true" />
        <property name="mappings">
        <props>
                <prop key="/dwr/**/*">dwrController</prop>
        </props>
        </property>
</bean>
<bean id="dwrController" class="org.springframework.web.servlet.mvc.ServletWrappingController">
        <property name="servletClass">
                <value>uk.ltd.getahead.dwr.DWRServlet</value>
        </property>
</bean>

Tím máme zajištěno, že požadavku začínající na /dwr/ obslouží DWR engine. Dále musíme specifikovat remotnuté objekty. Jsou to business metody našich servisních tříd a dále samotné doménové objekty. Možnosti konfigurace máme tři:

  • pomocí souboru dwr.xml
  • přímo ve aplikačním kontextu springu pomocí přidání dwr namespacu
  • programově (návod)

O možnoststech nastavení remotnutých objektů přímo v aplikačním kontextu springu se lze více dočíst v článku Spring & DWR – Ajax made Easy. Já používám Spring Framework 1.2.8, takže jsem byl nucen použít soubor dwr.xml. Ten jednododuše umístíte do stejného adresáře jako web.xml a DWR si jej při startu aplikace samo najde.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">
<dwr>
        <allow>
                <create creator="spring" javascript="BusinessService">
                        <param name="beanName" value="businessService" />
                        <include method="method1" />
                        <include method="method2" />
                </create>
                <convert converter="bean" match="cz.vavru.samples.dto.*" />

                <convert converter="hibernate2" match="cz.vavru.samples.business.vo.Article">
                        <param name="include" value="id,title,content" />
                </convert>
        </allow>
</dwr>

Tag create

Pomocí tagu create se definují remotnuté třídy a jejich metody. Pokud tyto metody vrací nějaké nestandardní objekty (například naše vlastní třídy nebo jejich kolekce) je nutné mít k nim nadefinovaný Conventor – jak název napovídá dělá se to pomocí tagu convert.

Pomocí atributu creator defunujeme kdo je zodpovědný za získání (vytvoření) instance volané třídy. Možnosti jsou následující:

new
Je vytvořen vždy nový objekt. Objekt musí mít bezpearametrický konstruktor.
none
Nevytváří se žádný nový objekt. Toto se používá buď při volání statických metod nebo při volání nestatických metod při jiném kontextu než page.
scripted
Nový objekt je vytvořen pomocí BSF skriptu.
spring
Získá instanci objektu z aplikačního kontextu Springu.
jsf
Získá instanci objektu z kontextu JSF.
struts
Získá instanci objektu z kontextu Struts.
pageflow
Zístká instanci objektu z PageFlow kontextu na serverech Weblogic nebo Beeehive.
ejb3
Dle dokumentace experimentální creator, která dokáže zpřístupnit EJB.

Pomocí atributu javascript definujeme pod jakým názvem budeme k vystavenému objektu přistupovat z javascriptu.

Atribut scope nastavuje kontext objektu – možnosti jsou „application“, „session“, „request“ a „page“. Defaultní hodnota je page. V dokumentaci se sice dočtete, že scope session funguje pouze při fungujících cookies, ale na jiném místě dokumentace naopak zjistíte, že funguje i bez nich a je nahrazen URL rewritingem.

Tag create může obsahovat další tagy:

param
Slouží pro nastavení prametrů pro vytvářený objekt. Pokud je atribut creator nastaven na hodnotu spring tak se následujícím zápisem nastavím k danému creatoru beana businessService – <param name=„beanName“ value=„busines­sService“ />
include
Slouží pro explicitní povolení metody specifikované v atributu method.
exclude
Opak tagu include – slouží k explicitnímu zakázání metody specifikované v atributu method. DWR umožňuje použití pouze tagů typu include (neuvedené metody jsou zakázané) nebo exclude(neuvedené metody jsou povoleny).
auth
Slouží pro definování rolí, které musí mít autentifikovaný uživatel pro invoknutí dané metody.
filter
Umožňuje definovat filtry – např. pro účely logování, autentizace atd. Více na stránce AjaxFilter.

Tag convert

Konventory jsou druhou důležitou skupinou v souboru dwr.xml. Pomocí nich se definuje jakým způsobem jsou převedený objekty z javovského do javascriptového světa. Konventory pro základní typy objektů jsou zaregistrovány automaticky:

  • primitivní typy a jejich objektové wrappery (včetně java.lang.String, java.util.Date)
  • pole a kolekce výše uvedených typů
  • DOM objekty

Pro ostatní typy objektů je nutné explicitně definovat converter (možno používat i wildcardy). Například zápisem <convert converter=„bean“ match=„cz.vavru­.samples.dto.*“ /> nadefinujete, že se všechny třídy z uvedeného balíčku mají převádět pomocí bean convertoru. Ten pracuje tak, že projde všechny gettery a settery objektu a převede jej do javascriptu ve formě asociativního pole. Object convertor pracuje tak, že prochází přímo třídní proměnné.

Zmíním bych ještě konventory pro hibernate 2 a 3. Pokud jej používáme se Springem nesmíme zapomenou zkontrolovat zda máme nastaveno mapování OpenSessionInView filtru i pro DWR. Samozřejmě pouze v případě, že je to potřeba – například pokud jste mezi vlastnostmi, které se mají převést, uvedli lazy kolekci.

<filter>
        <filter-name>Hibernate OpenSessionInView</filter-name>
        <filter-class>org.springframework.orm.hibernate.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>Hibernate OpenSessionInView</filter-name>
        <url-pattern>*.htm</url-pattern>
</filter-mapping>
<filter-mapping>
        <filter-name>Hibernate OpenSessionInView</filter-name>
        <url-pattern>/dwr/*</url-pattern>
</filter-mapping>

V konventoru lze pomocí tagu param (podobně jako u tagu create) explicitně nadefinovat členské proměnné (vlastnosti) objektu, které mají (nemají) být převedeny.

Další tagy

Kromě tagu allow může soubor obsahovat část init (používá se pro definování vlastních Creatorů a Conventorů) a signatures (používají se pro deklaraci typových konverzí z javasriptu do Javy – více o signatures).

Ještě nesmím napomenout upozornit na anotace, které lze používat i souběžně se souborem dwr.xml

Nyní jsme si probrali vše potřebné na straně serveru. Pojďme se podívat na klienta.

Práce s DWR na straně klienta

Pro používání DWR je na klientské straně nutné vložit sript se DWR enginem a spriptem s remotnutnutými metodami. Těch může samozřejmě být více. Pokud se rozhodnete využívat funkni z knihovny utilit musíte vložit i tu.

<script src='/my-application/dwr/interface/BusinessService.js'></script>
<script src='/my-application/dwr/engine.js'></script>
<script src='/my-application/dwr/util.js'></script>

Skript na adrese /my-application/dwr/in­terface/Busines­sService.js vygeneruje za běhu DWR servlet. Samotné volání vzdálených metod může vypadat takto:

<script type="text/javascript">
myImportantUserInput = "Hello World";
BusinessService.method1(myImportantUserInput, callback);

function callback(result) {
        // práce s výsledkem požadavku
}
</script>

Řešení vyjímečných stavů

Vyjímečné stavy lze zachytávat pomocí error a exception handlerů.

<script type="text/javascript">
dwr.engine.setErrorHandler(errorHandler);
dwr.engine.setWarningHandler(warningHandler);

function errorHandler(obj) {}
function warningHandler(obj) {}
</script>

Error a Warning handler se dají nastavit výše uvedeným způsobem na globální úrovni. Exception handlery se nastavují pouze v kontextu dané vzdáleného volání nebo batche.

Batchování

Pokud chceme několik asynchronních volání seskupit do jednoho (což může být mnohem rychlejší) použijeme k tomu právě batch. Malá ukázka:

<script type="text/javascript">
dwr.engine.beginBatch();
BusinessService.method1(variable1, callback1);
BusinessService.method2(variable2, callback2);
dwr.engine.endBatch();
</script>

Reverse AJAX

Pod pojmem Reverse AJAX si lze představit technologie pro posílání dat ze serveru do klienta. DWR podporuje tyto metody:

pollování
Spočívá v poolování (periodického dotazování) serveru (například každých několik sekund).
comet
Ponechá se otevřené spojení server-klient
piggyback
Server shromažďuje data, která chce poslat klientovi. Ty mu doručí při příštím dotazu klienta (přibalí je do odpovědi bez ohledu na volanou funkci).

Více se lze dočíst na stránkách DWR – Reverse Ajax a Configuring Reverse Ajax.

Další zajímavé funkce

Pomocí metody dwr.engine.se­tOrdered(boole­an) lze nastavit, aby se další dotaz na server poslal až ve chvíli kdy se vrátí odpověď dotazu předcházejícího – tedy zaručení jakési synchronnosti pořadí odpovědí.

Pomocí metody dwr.enginesetA­sync(boolean) můžeme vykonávat dotazy na server synchronním resp. asynchroním způsobem.

Dalšími příjemnými funkcemi jsou dwr.engine.set­PreHook(functi­on) a dwr.engine.set­PostHook(functi­on). Jak název napovídá – jedná se o fukce, které jsou invoknuty před vykonáním požadavku resp. po příjetí odpovědi ze serveru – ideální na zobrazování loadovacích hlášek. Pro tento případ má DWR funkci useLoadingMes­sage.

Dostal jsem se do situace, že jsem potřeboval v remotnutých servisních metodách přistupovat k session – řešení je nakonec velmi triviální (dík Honzovi Novotnému za pomoc) – pokud máte v signatuře vaší servisní metody objekt typu HttpServletRequest, HttpServletRes­ponse, HttpSession, ServletContext, ServletConfig tak jej při volání z javaskriptu jednoduše vynecháte a DWR je při volání samo nastaví. Druhou možnosti je zavolání použití třídy WebContextFac­tory.

Pěkné je, že v debug režimu převede DWR chybu na string, který zobrazí v alertu. Podpora browserů se zdá také velmi dobrá – my jsme testovali v Opeře, Firefoxu, IE6 a IE7 a vše bez problémů.

Knihovna DWR se v praxi ukázala jako velmi rychlá. Za předpokladu, že se počet objektů (velikost dat) v jedné odpovědi bude pohybovat v rozumných mezích (řekněme do 300) pohybuje se odezva serveru v řádi stovek milisekund (nedostal jsem se nad 500 ms), což je odpověď de facto okamžitá. Určitě stojí za další prozkoumání reverse ajax funkce – např. pro chat určitě lahůdka.

Závěr

Doufám, že jsem vás článkem dostatečně navnadil a celý nedočkavý chvátáte si DWR vyzkoušet na vlastní kůži.

Odkazy

Článek patří do kategorie: Java, Ajax

12 Komentářů Přidat komentář

  • 1. Honza Novotný  |  8. Září 2007 v 11.58

    Skvělý článek, vyčerpávající. Jen drobnost – jmenuju se Honza Novotný (ne Jirka).

  • 2. Vlasta  |  8. Září 2007 v 12.09

    [1]Co se dá dělat – stalo se. Omlouvám se.:)

  • 3. Pet  |  10. Září 2007 v 9.44

    Ahoj, pekny clanek. S dwr pracuju uz nejaky cas. Jenom jedna vec ktera me minuly tyden dostala. Zkuste si pouzit pro to dwr misto firefoxu operu. 9.23 nebo zkuste i tu nejnovejsi alfu 9.5. To je naprosto neskutecny rozdil v rychlosti zpracovani celeho scriptu.

  • 4. Vlasta  |  10. Září 2007 v 9.51

    [3] Teď nevím jestli myslíte zrychlení nebo zpomalení. Já používám jako primární prohlížeč Operu 9.22 a vše je bleskurychlé. Otestování i na prohlížečích IE6, IE7, Mozilla 2 a taktéž velmi rychlé.

  • 5. benzin  |  11. Září 2007 v 21.05

    No mohou se zeptat jak je to s linym nacitanim? Pokud neco chci line nacitat z databaze, pak to v 99% pripadu budu chtit stejne tak line nacitat i do Ajaxu. Problem je v tom, ze DWR si zazada o kazdou polozku i ta ktera by mela byt ctena line.

    Samozrejme metody publikovaneho objektu jsou predany az na pozadani, ale vsekere objekty ktere jsout takto vraceny jsou nacteny jiz kompletne se vsim vsudy. Mam-li napriklad objekt ktery mi vraci kraje, ve kterych je seznam okresu a v nich seznam obci a v nich seznam zastupitelu, provede se pri volani getKraj, nacteni komplet veskerych techto dat. To je dost nezadouci.

    Jak tomu zabranit?

    P.S.: Ten CAPS obrazek je znacne necitelny, si asi na neho budu muset poridit nejaky poradny OCR nastroj :(. Uz to zadavam popate a nemuzu se trefit.

  • 6. Vlasta  |  11. Září 2007 v 21.48

    [5]V konfiguracnim souboru je potreba explicitne uvest ktere vlasnosti javabeany se maji prevest do javascriptu. Pak muzete napr u vyse jmenovaneho objektu Kraj zadat pouze id a jmeno.

    V javascriptu zadne line natahovani neni – a pokud je presto potreba dalsich dat tak se jednoduse vykona dalsi dotaz a k danemu Kraji se dotahou Okresy.

  • 7. benzin  |  12. Září 2007 v 7.18

    No jenze ja budu chtit vsechny informace prevest do javascriptu, ale az na pozadani.

    Protoze jeden uzivatel bude v jednu chvili pracovat s jednim krajem, takze netreba nacitat okresy a obce z jinych kraju. A v tom je ten hacek :(

    Navic to nastaveni co sem zatim nasel je funkcni primo pro beany, ale ne jiz pro objekty ktere obsahuji.

    To DWR totiz umoznuje nastavit vsechno primo u exportovane beany, ale pak kdyz z ni zavolate funkci, ktera vraci objekt, je vracen ten objekt, se vsim vsudy co obsahuje. Jsou totiz okamzite zavolany vsechny get metody tohoto vraceneho objektu a objektu vraceneho z tohoto objektu atd. atd. atd. hloubeji a hloubeji.

  • 8. Vlasta  |  12. Září 2007 v 8.02

    [7]Uplne Vam asi nerozumim. Tak za prve – kdyz pracuji s Hibernatem tak nepouzivam beanconverter, ale hibernate (2,3) converter.

    A co se tyce Vami popisovane situace – musite vedet jak jsou data velka. Pak muzeme definici v dwr.xml do javascriptu prevest i lazy kolekce.

    A pokud vite, ze by vysledna data byla prilis rozsahla, tak si ne jednoduse sahnete dalsim requestem.

    Nevidim nikde problem.

  • 9. benzin  |  12. Září 2007 v 13.17

    View vrstva o zpusobu ulozeni, nebo o mistu kde se data obevili nepotrebuji nic vedet.

    Predpokladejme mame beznou stromovou strukturu. Kde jednotlive lavaly jsou pod sebou naskladany. Seznam podrizenych objektu potrebujete znat, az kdyz si o to explicitne pozadate. Ale zcela logicky mate metodu setSubLevel a getSubLevel, tyto metody nejakym zpusobem na pozadani ziskaji informace o podrizenych polozkach.

    V beznem programu (bez DWR) nactete seznam objektu prvni urovne, z nich si uzivatel vybere jeden a u neho nacte seznam podrizenych objektu a z nich si zase vybere jeden a nacte seznam podrizenych atd. atd. a tak to jde dolu. (napriklad se jedna o adresarovou strukturu).

    DWR, ale pracuje, tak ze ve chvili kdyz pozadate o prvni uroven objektu, je postupne u vsech vyvolana metoda getSubLevel a u takto nabytych objektu zase zavolano getSubLevel atak dale, dokud nema kompletne sestaveny cely strom a ten pak prenese na klienta.

    Tak je to alespon v defaultnim nastavenim.

    Ja bych ale potreboval donutit DWR, aby nacetl jenom prvni uroven a treba klidne u kazdeho parametru, ktery pak budu volat vytvoril nove spojeni a stahl uz jenom to co skutecne potrebuje.

    Naprosto idealni reseni by pak byla nejak anotace (nebo filtr), ktery by urcoval co teda dolu poslat okamzite, co az na explicitni pozadani a co treba nevratit vubec, protoze uzivatel nema opravneni.

  • 10. Vlasta  |  12. Září 2007 v 13.31

    [9]Stale si asi nerozumime

    Naprosto idealni reseni by pak byla nejak
    anotace (nebo filtr), ktery by urcoval co teda dolu
    poslat okamzite, co az na explicitni pozadani a co
    treba nevratit vubec, protoze uzivatel nema opravneni.

    Vsak prave takto DWR funguje. Explicitne povolite (include) nebo zakazete (exclude) vlastnosti beany.

    Ten problem se stromovou strukturou bych resil tak, ze tak, ze bych vlastnost childNodes (kolekce podrizenych polozek) nedaval do vyctu vlasnosti, ktere chci remotovat. Na severu bych pak mel vystavenou metodu, ktera mi pro pole idecek nodu totahne childNody:
    public Map<Integer, List<MyNode>> findChildNodes(In­teger[] nodeIds);

  • 11. benzin  |  13. Září 2007 v 9.00

    Vlasta: Jenze to neni ono. Exclude vyradi celou metodu u cele tridy nikoli u obektu.

    No prave na to sem nakonec taky prisel. Pokud chci neco nacitat line, musim odriznout metodu a vytvorit tidu, ktera najde (napriklad podle ID) objekt a z neho vrati jeho ChildNode.

    No mozna udelam jinou vec. Forknu si ten controller a udelam si anotaci, ktera by umoznovala nacitat line. :( Jenze pak by bylo nutne tohle zpetne volani naimplementovat i v javascriptu, ale to by se mohlo zvladnout.

  • 12. Dalena&hellip  |  15. Červenec 2008 v 4.03

    software…

    software…

Přidat komentář

Povinné

Povinné, skryté

Security Image Povinné
Opište text z obrázku

Povolené HTML značky:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Odkazovat na tento článek  |  Přihlásit se k odběru těchto komentářů přes RSS Feed


Kalendář

Květen 2016
P Ú S Č P S N
« Led    
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Poslední články

Locations of visitors to this page