Archiv pro měsíc July, 2008

Reverse Ajax s DWR, Spring, iBatis

Před nějakým časem se v Avedyi rozjížděl nový projekt. Jednalo se o klasický webový chat. Vytvořil jsem tehdy jednoduchý prototyp. Použil jsem DWR a technologii reverse ajax.

Pro tento článek jsem původní prototyp trochu rozšířil a zapojil jsem Spring Framework a jako DAO vrstvu jsem vybral iBatis.

Krátce o iBatisu

I když iBatis není plnotučné ORM s klidem bych ho nasadil na většinu projektů, na které jsem v minulosti použil Hibernate. Stručný přehled vlastností a featur iBatisu:

  • Konfigurace v XML podobně jako Hibernate.
  • Veškeré SQL příkazy (insert, update, select) jsou nadefinovány přímo v XML konfigurácích. V nich definujete placeholdery, které jsou při vykonávání nahrazeny skutečnými hodnotami.
  • Umí relace (1:1, 1:n, m:n)
  • má vlastní kešování (kongurace opět v xml)

iBatis mě nadchnul a původně jsem o něm chtěl napsat krátký článek. Ale když jsem viděl kolik zdrojů na (i českém) internetu je rozmyslel jsem si to. Seznam zdrojů o iBatisu uvádím na konci článku.

Krátce o DWR

O DWR jsem z na blogu již psal. V tomto článku bych se blížeji podíval pouze na typy reverse ajaxu, které nabízí.

Full Streaming Mode

Nejrychlejší způsob reverse ajaxu. Spojení zůstává otevřeno po dobu 60 sekund. Poté je vytvořeno nové (což se nemusí podařit pokud browser již není otevřený např). Má dvě nevýhody:

  • velká zátěž serveru (při velkém počtu klientů)
  • proxy, antivirové program a různé moduly webserveru mohou držet data do doby než se spojení ukončí (k zobrazování dat by pak docházelo pouze jednou za 60 sekund)
Early Closing Mode (Long Polling)

Funguje tak, že server zavře spojení s klientem a vyžádá si spojení nové v případě, že klientovi posíláme nějaká data. Jestliže data neposíláme je chování stejné jako u Full Streaming modu. Spojení s klientem není uzavřeno ihned, ale lze jej konfigurovat pomocí parametru maxWaitAfterWri­te. Typicky napstaveno na 500 ms. Server v tomto případě pošle klientovi zprávu, počtu 500 ms a poté ukončí a znovu otevře spojení s klientem.

Výhodou je odolnost vůči držení dat na straně proxy atd. Nevýhodou je opět velká zátěž serveru.

Polling Mode

Polling Mode bych pravým reverse ajaxem ani nenazýval. Spočívá v periodickém dotazování serveru klientem. Směr volání je klient → server, což je opakem předchozích dvou možností.

Full Streaming Mode je představitelem technologie Comet – tzv. spojení mezi serverem a klientem s dlouhou délkou života.

Příklad

Příkladem je jednochuchý chat. Základní usecasy jsou:

  • nalogování uživatele
  • zjištění aktuálního uživatele ze session
  • odlogování
  • poslání nového vzkazu (buď všem v chatu nebo jednomu adresátovi)
Klient

Klientem je statická html stránka (index.html), která dělá dotazy na server pomocí js souboru. V něm je volán DWR servlet. Pro komfort při práci se stránou je použita i knihovna jQuery.

Server

Serverovou částí je javovská webové aplikace běžící v libovolném servlet kontajneru. Jediným endpointem je DWR servlet. Jako IOC kontajner je použit Spring Framework. Obsahuje jednu remotnutou servisní beanu – chatService. Jsou zde dva doménové objekty – User a Message.

Soubor dwr.xml obsahující konfiguraci remotnuté servisy a bean convertor pro doménové objekty:

<dwr>
  <allow>
    <!-- configure service -->
    <create creator="spring" javascript="ChatService" scope="application">
      <param name="beanName" value="chatService"/>
    </create>

    <!-- configuje bean converter -->
    <convert converter="bean" match="cz.vavru.test.dwr.domain.*"/>
  </allow>
</dwr>

Soubor Message.xml s iBatis mapováním:

<sqlMap namespace="Message">
  <typeAlias alias="message" type="cz.vavru.test.dwr.domain.Message"/>

  <resultMap id="result" class="message">
    <result property="messageId" column="messageId"/>
    <result property="date" column="date" />
    <result property="message" column="message"/>
    <result property="sender.userId" column="senderId"/>
    <result property="sender.nick" column="senderNick"/>
    <result property="recipient.userId" column="recipientId"/>
    <result property="recipient.nick" column="recipientNick"/>
  </resultMap>

  <select id="getMessages" resultMap="result">
    select * from Message where recipientId is null or senderId = #userId# or recipientId = #userId#
  </select>

  <insert id="insertMessage">
    insert into Message set date = #date#, senderId = #sender.userId#, senderNick = #sender.nick#, recipientId = #recipient.userId#, recipientNick = #recipient.nick#, message = #message#
        <selectKey resultClass="int" keyProperty="messageId">
      select last_insert_id() as messageId
    </selectKey>
  </insert>
</sqlMap>

Metoda getScriptSessi­onsToNotifyAbou­tNewMessage z třídy cz.vavru.test­.dwr.util.DwrWeb­ContextUtil. Úkolem metody je vrátit kolekci objektů ScriptSession, které mají být notifikovány o nově příchozí zprávě. Jak je z kódu vidět v případě, že je vybraný adresář je notifikace poslána jenom odesílateli nebo adresátovi. Proměnná webCtx je interface org.directwebre­moting.WebCon­text obsahují např odkaz na aktuální ScriptSession (obdoba HttpSession). Na rozdíl od HttpSession vzniká nová ScriptSession s každým obnovením stránky (F5 v prohlížeči). Ohromnou výhodou tohoto objektu je možnost uložit si do něj libovolný atribut. Já do něj při zalogování ukládám informace o uživateli. A tak později přesně vím, které scriptsešný mám zavolat:

@SuppressWarnings("unchecked")
public Collection<ScriptSession> getScriptSessionsToNotifyAboutNewMessage(Message message) {
        Collection<ScriptSession> pageScriptSessions = (Collection<ScriptSession>) webCtx
                        .getScriptSessionsByPage(currentPage);

        Collection<ScriptSession> scriptSessions = new ArrayList<ScriptSession>();

        if (message.getRecipient() != null) {
                for (Iterator<ScriptSession> it = pageScriptSessions.iterator(); it.hasNext();) {
                        ScriptSession scriptSession = it.next();
                        User user = (User) scriptSession.getAttribute(ChatServiceImpl.SCRIPT_CHAT_USER_SES_ATTR);
                        if(user == null) continue;

                        if (message.getSender().getUserId() == user.getUserId()
                                        || message.getRecipient().getUserId() == user.getUserId()) {
                                scriptSessions.add(scriptSession);
                        }
                }
        } else {
                scriptSessions = pageScriptSessions;
        }

        return scriptSessions;
}

Posledním fragmentem kódu je metoda addMessage z třídy cz.vavru.test­.dwr.service.Chat­ServiceImpl.

@Transactional
public void addMessage(Message message) throws ChatServiceException {
        WebContext webCtx = WebContextFactory.get();
        // zjisteni aktualniho usera ze session
        User currentUser = (User) webCtx.getHttpServletRequest().getSession(true).getAttribute(
                        CHAT_USER_SES_ATTR);

        // nastaveni odesilatele a data
        message.setSender(currentUser);
        message.setDate(new Date());

        // vlozeni message do databaze
        messageDao.insertMessage(message);

        DwrWebContextUtil dwrUtil = new DwrWebContextUtil(WebContextFactory.get());
        // zjisteni vsech scriptsession, ktere mame notifikovat o nove zprave
        Collection<ScriptSession> scriptSessions = dwrUtil
                        .getScriptSessionsToNotifyAboutNewMessage(message);

        logger.info("Sending info about new message to " + scriptSessions.size() + " users");

        Util util = new Util(scriptSessions);
        // timto zpusobem se vola primo kod na strane klienta
        // nasledujici radek tedy zavola javascriptovou funkci addMessage (viz soubor js/chat.js) s parametrem message
        // objekt message, ze samozrejme preveden do javascriptoveho sveta
        util.addFunctionCall("addMessage", message);
}

Celý příklad lze stáhnout zde – dwr-ajax.zip (zip, 9.9 MB). Archiv obsahuje:

  • zdrojové soubory (celý Eclipse projekt)
  • dwr-ajax.sql s DDL
  • war archiv

Závěr

Věřím, že se mi povedlo ukázat jak je spojení všech použitých knihoven naprosto bezbolestné a jednoduché. Při testech na mém notebooku, kdy jsem si v každém prohlížeči (IE, Opera, Mozilla) otevřel chat a vstoupil pod jiným uživatelem, byl celý chat pekelně rychlý. Zpráva byla zobrazena takřka okamžitě.

Odkazy

Celý článek Přidat komentář 6. July 2008


Kalendář

July 2008
M T W T F S S
« Jun   Sep »
 123456
78910111213
14151617181920
21222324252627
28293031  

Články podle měsíců

Kategorie

Locations of visitors to this page