DWR - AJAX knihovna pro remotování Java objektů
7. September 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=„businessService“ /> - 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/interface/BusinessService.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.setOrdered(boolean)
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.enginesetAsync(boolean)
můžeme
vykonávat dotazy na server synchronním resp. asynchroním způsobem.
Dalšími příjemnými funkcemi jsou
dwr.engine.setPreHook(function)
a
dwr.engine.setPostHook(function)
. 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
useLoadingMessage.
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, HttpServletResponse, 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 WebContextFactory.
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.
13 Komentářů Přidat komentář
1. Honza Novotný | 8. September 2007 v 11.58
Skvělý článek, vyčerpávající. Jen drobnost – jmenuju se Honza Novotný (ne Jirka).
2. Vlasta | 8. September 2007 v 12.09
[1]Co se dá dělat – stalo se. Omlouvám se.:)
3. Pet | 10. September 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. September 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. September 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. September 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. September 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. September 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. September 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. September 2007 v 13.31
[9]Stale si asi nerozumime
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(Integer[] nodeIds);
11. benzin | 13. September 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. July 2008 v 4.03
software…
software…
13. 2picture&hellip | 13. January 2022 v 1.06
1interwoven…
…
Přidat komentář
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