Néhány érdekesség a nagyvilágból:
http://ajaxian.com/archives/gwt-quake
http://www.boingboing.net/2011/05/18/javascript-based-pc.html
Blog by Pál "voji" Vojácsek
Néhány érdekesség a nagyvilágból:
http://ajaxian.com/archives/gwt-quake
http://www.boingboing.net/2011/05/18/javascript-based-pc.html
Igen igen, hiába mondjuk azt a Microsoft böngészőjének azt css-ben:
border: 0 none;
Ez nem elég egyértelmű. ha egy IFrame-ről van szó. Ahhoz, hogy IE alatt is működjön a dolog ezt kell mondani HTML-ben:
...iframe frameBorder="0" ...
GWT alól pedig valami ilyesmit:
iFrame.getElement().setAttribute("frameBorder", "0");
Gondoltam áldozok a maréknyi szabadidőmből, és feltelepítem az otthoni eclipsemre az új gwt-t.
Valami hiba történt, mert azt mondta a letöltés, hogy sajnos az alábbi fájl letöltése során connection error van:
com.google.gdt.eclipse.designer.hosted.2_0_2.2.0.r36x201102111446.jar
Sajnos ugyan ez van új eclipse-el is, szóval a hiba nem az én számítógépemben van (legalábbis a neten még többen panaszkodtak erre).
A megoldás:
Le kell tölteni az offline telepítő csomagot, és telepíteni az itt leírtak szerint:
Nem hittem volna, hogy valaha eljutok oda, hogy html alkalmazások esetén is render enginet, és renderereket fogok írni. A webes világ most ért el oda, ahol a pc-s már egy ideje tart, és várhatóan az emberek többsége most szembesül majd azokkal a problémákkal (lehet picit később) amikkel a „pc”-s fejlesztők már évek óta.
Annyi a különbség, hogy itt nem D3d-nek, vagy opengl-nek hívják a dolgokat, de ez az idő sincs már messze.
Tehát ha valaki webes alkalmazást fejleszt, és számít neki a sebesség, esetleg nem akarja kéthavonta újraírni az alkalmazását, az alábbi desgin patternt kövesse (hacsak nem akar pórul járni):
Renderer’s (software -> html dom, hardware -> html5 canvas, bármi más)
Render Engine
Game Logic
Game Data
Game Assets
Így talán van esélye, hogy ha 180 fokos fordulatot vesz a világ akkor se kelljen kidobni többéves munkáját.
Egy ötletes kis demóval zárom a mai napot: http://gwtcanvasdemo.appspot.com/
A gwt alkalmazások (egyik) legnagyobb előnye a perfect cache kezelés. Ahhoz, hogy ez hatékony legyen érdemes minden statikus tartalmat egy apache-ra rakni, és csak az RPC hívásokat átfűzni egy proxy-n az alkalmazás szerver felé. Ennek hatására viszont előállhat az a furcsa helyzet, hogy az RPC szolgáltatásokat kezelő szervlet context-root-ja eltér a statikus fájlok context-root-jától.
Ezt a GWT rpc kezelő servlete nem szereti valami nagyon és mindenkit megjutalmaz az alábbi hibaüzenettel:
[10/28/10 12:50:44:383 CEST] 00000041 webapp I com.ibm.ws.webcontainer.webapp.WebApp log SRVE0296E: [app_war#app.war][/app/services][Servlet.LOG]:.PartnerServiceServlet: ERROR: The module path requested, /app/, is not in the same web application as this servlet, /app/services. Your module may not be properly configured or your client and server code maybe out of date.:.null
Ez önmagában nem baj, csak csúnya, de amikor komplex osztályokat küldünk fel a szerverre, ennél komolyabb hibák is jelentkeznek (cannot load serialization policy).
Ennek oka, hogy az RPC üzenetek hordozzák magukban, hol keletkeztek, és a szerver oldal ellenőrzi, hogy az egyezik e a context rootjával.
Ha megfigyeljük az RPC hívások tartalmát látszik, hogy az url a verziók után utazik, pl:
verziók|hívó|ser policy fájl|hívás névtér|szolgáltatás, stb…
A valóságban valami ilyesmi:
5|0|9|http://server/app/|D0C8380096848CC179C5488360839537|hu.aftershock.client.services.gwtrpc.modules.common.ICommonService|getCommonCodes
A probléma a 4 résznél van, amit cserélni kell (ha az alkalmazás context rootja nem /app hanem /app/services). Ezt oly módon tudjuk megtenni, hogy leszármazunk a RemoteServiceServlet osztályból, és felülírjuk a processcall funkciót (ezt egyébként is érdemes megtenni, ide lehet mindenféle hasznos dolgot mint profiling, audit, security implementálni).
@Override
public String processCall(String paydload) throws SerializationException {
paydload=mainUrlFix(paydload);
…
A mainUrlFix implementáció az alábbi módon néz ki:
private String mainUrlFix(String payload) {
int tokenNum=0;
String prefixString=””, mainUrl=””, postfixString=””;
for (int i=0, il=payload.length(); i<il; i++ ) {
if (payload.charAt(i)== AbstractSerializationStream.RPC_SEPARATOR_CHAR) {
tokenNum++;
if (tokenNum==3) {
prefixString=payload.substring(0,i);
} else if (tokenNum==4) {
mainUrl=payload.substring(prefixString.length(),i);
postfixString=payload.substring(i);
break;
}
}
}
if (!mainUrl.endsWith(“services/”)) {
mainUrl+=”services/”;
}
return prefixString+mainUrl+postfixString;
}
Lassan 1 éve foglalkozok GWT-vel. Gondoltam eljött az idő, hogy röviden számotvessek magammal, milyen is volt ez az 1 év…
A maga a koncepció, ahogy a megvalósították, hogy java-ban hozhatunk létre böngészőfüggetlen javascriptet, a szabad forráskódú fejlesztés, hogy a rendszer minden részének működését forráskód szinten látjuk, hogy módosíthatók (vagy utánozhatóak) az alap implementációk, és még sorolhatnám. Ez kétségtelenül megérdemel egy + jelet 🙂
Persze az ismerkedés nem volt egyszerű, néhány fejlesztési alapvetést, és nézetet adaptálni kellett, tehát aki gwt-re adja a fejét (szerintem ez ajax-ra, sőt minden asszinkron működésű programra vonatkozik) kénytelen picit „másképp gondolkodni”.
A tapasztalataim részletekbe menő ismertetését most sajnos nem írom le, de néhány gondolatot azért igen.
Nem, nem bántam meg, hogy gwt-be fejlesztek, és ha újra kellene választanom, én ismét ezt választanám. (bár ilyen téren mindegy miben kell fejleszteni, ha valaki elég eltökélt, bármivel tud nagyszerű dolgokat készíteni, max sok idő, és áldozat árán 😉 )
A GWT alkalmazásokban gondolkozik, és ezért „nagy alkalmazás” fejlesztése során felmerülnek problémák (pl. a memória használat, kódok mérete). Ha viszont egy picit tovább gondoljuk a dolgot, minden nagy alkalmazás nem más mint kis alkalmazások összessége, amik viszont játszi könnyedséggel integrálhatóak egy nagy alkalmazásba (ez a megközelítés egyébként is rengeteg előnnyel jár, mint pl. az egymástól független verziókezelés, stb).
Kliens és szerver oldali kódok az RPC hívások miatti duplikálása (ami sajnos mostanában elég bevett szokás), egy hibás koncepció. Rengeteg fejlesztő duplikálja a DAO kódokat (pl. egy tábla leíró osztály), hogy a gwt számára a böngészőbe át tudja adni a benne lévő adatot. Erre nincs szükség. Amikor a gwt fordít, akkor a gwt fordító fordít, amiből javascript lesz. Amikor a java fordít abból pedig szerver oldali java class fájlok. A két folyamat eltéréséből adódik, hogy megtehető az, hogy a gwt fordítás alatt az ember picit más class-okat használ fel, mint a java fordítás alatt.
Egy életből vett példa, hogy érthető legyen:
Tegyük fel, hibernate-t használunk, és csinálunk egy partner osztályt:
@Entity
@Table(name = "Partner")
public class Partner {
…
@Id
@GeneratedValue
@Column(name = "PARTNER_ID")
public long getId() {
return this.id;
}
…
Ha ezt gwt oldalon el szeretnénk érni két általános dolgot kell tennünk:
– levinni a kódot a kliens oldalra
– implementálni az IsSerializable típust
A második dolog viszonylag egyszerű, ellenben amikor a kliens oldalra mozgatjuk az osztályt, akkor a gwt fordító kedvesen figyelmeztet, hogy kliens oldalon nem lehet szerver oldali komponenseket használni (mint a hibernate notáció).
Persze azért mondja ezt, mert nem tudja, hogy mi ezeket a kódokat kliens oldalon nem is akarjuk használni. A megoldás kézenfekvő, és egyszerű. A hibernate forrásából ki kell szedni azokat a fájlokat amiket kliens oldalon is használunk. A forrásban használt függvényeket meghagyuk, de az implementációs részt töröljük. Fordítunk belőlle egy olyan jar-t amiben benne van a forrás is, és a fordított funkció nélküli class fájlok, megmondjuk a gwt fordítónak, hogy tessék ezt használni.
A kód lefordul, a javascript a hibernate notációval ellátott, de funkcióval nem rendelkező osztályokat használja, ellenben amikor ez az adat felkerül szerver oldalra, ott már a hibernate funkcionalitás is elérhetővé vállik, nincs szükség az osztályok duplikálására.
Ugyanez az elv használható a validációk során is, a jól megírt validációt végző keretrendszer minden esetben használható mind kliens mind szerver oldalon (optimális esetben notációkkal, pl. a hibernate által használt dao osztályban… ).
Részemről a geek hajlamok ezzel letudva a hétvégére 🙂
Ezer éve nem volt módom (és időm) írni, de most sikerült, megtaláltam, és megengedek magamnak ekkora pihenőt. Jó ideje egy GWT alapú alkalmazás készülget, és eddig szinte minden „simán” ment a fejlesztés során, volt néhány kisebb trükk, pár gwt-s osztályt át kellett írni, de összességében szerintem a GWT keretrendszer jól teljesített, sőt felül is múlta az elvárásaimat.
Egészen tegnapig, amikor is azt jelezték, hogy az alkalmazás sok memóriát fogyaszt. Ilyenkor az ember hajlamos azt mondani, hogy persze, hiszen nagy alkalmazás, de a C++-hoz szokott énem rögtön kétségbe esett, ez bizony memory leak. Gyors mérések után a tünetek egyértelműek voltak. Az alkalmazás leak-el, menüpontonként pár megát. Az látszott, hogy nem a javascript memóriahasználata nő (google chrome kiírja), hanem a böngésző memória használata. Ekkor kezdtem gyanakodni, hogy ezt pedig mi rontottuk el, és a végén igazolódott is ez az állítás. A hibát csak a tanúsága miatt megosztom mindenkivel.
Az eredeti kód:
private void setLoadingVisible(boolean pVisible) {
if (pVisible) {
waitingPopup=new APopupWaiting(msgs.main_waitingmessage(), Window.getClientWidth(),Window.getClientHeight());
waitingPopup.center();
}
if (!pVisible && waitingPopup.isVisible()) {
waitingPopup.hide();
}
Ez a szolgáltatás arra szolgál, hogy ha tölteni kell, akkor kirak egy töltés dialógust. Első látásra minden rendben van, létrehoz egy újat, megjeleníti, vagy elrejti a megadott paraméter alapján. Az implementáció hibás, az már érdekesebb kérdés miért. Normál esetben maximum egy nem optimális implementáció lenne, hiszen miért hozunk mindig létre valamit, ami már egyszer létre lett hozva, ráadásul sohasem változik. Sokkal nagyobb probléma, hogy ez javascriptben fut, és a GWT a változót (és a hozzá javascript által generált HTML dom-ot) csak akkor szabadítja fel, ha már nincs rá referencia. Ellenben a PopupDialogra igen is lesz referencia, és nem a DOM-on keresztül, hanem azon eseménykezelők miatt, amikre működéséből fakadóan feliratkozott. Tehát az objektum lóg a levegőben, és fogyasztja a memóriát.
Egy lehetséges megoldás:
private void setLoadingVisible(boolean pVisible) {
if (pVisible) {
if (waitingPopup==null) {
waitingPopup=new APopupWaiting(msgs.main_waitingmessage(),
Window.getClientWidth(),Window.getClientHeight());
waitingPopup.center();
} else {
waitingPopup.center();
}
}
if (!pVisible && waitingPopup!=null) {
waitingPopup.hide();
}
}
Végezetül egy kis olvasmány (de az ellen nem véd ;):
http://code.google.com/p/google-web-toolkit/wiki/DomEventsAndMemoryLeaks