V predchádzajúcich dvoch článkoch o servletoch sme preberali spracovanie formulárov. Údaje zadané používateľom sa vždy nejakým spôsobom spracovávajú a väčšinou aj ukladajú. Zatiaľ sme si ukázali len spracovanie, ale nie ukladanie dát. Dáta môžete uložiť do databázy ale aj do súborov. V dnešnom článku si ukážeme práve ukladanie dát (objektov) do súborov.

Hneď na úvod musím vysvetliť jednu vec. Serializácia (proces ukladania objektov do súborov) nie je vhodná na ukladanie väčšieho objemu formulárových alebo akýchkoľvek iných dát. V takom prípade je oveľa jednoduchšie a efektívnejšie použiť databázu a SQL. Myslím, že nemusím vysvetľovať dôvody. Serializácia je skôr vhodná na uchovanie stavu jednoduchších objektov, ktoré potrebujeme z nejakého dôvodu neskôr znova obnoviť. Mimochodom, objekty je možné serializovať nielen do súboru, ale aj do databázy. Ale to je už niečo iného.

Aký je vlastne zmysel toho celého? Predstavte si, že potrebujete uchovať určité informácie o užívateľovi nielen po dobu životného cyklu servletu (čo je pomerne jednoduché), ale aj v prípade, že server ukončí inštanciu servletu, prípadne je zastavený (spadne) samotný server. Aby ste mohli požadovanú informáciu získať späť po vytvorení novej inštancie príslušného servletu, môžete si ju prečítať z externého súboru.

Pomocou serializácie je možné zapisovať a čítať len objekty, ktoré implementujú rozhranie java.io.Serializable. Väčšina tried z Java Core API tak aj robí. Ak si však vytvoríte vlastnú triedu a tú chcete serializovať, musí vaša trieda implementovať toto rozhranie. To by však nemal byť veľký problém, rozhranie totiž neobsahuje žiadne metódy. Ak tak nespravíte, výsledkom bude výnimka java.io.NotSerializableException.

Proces serializácie objektov je v Jave zabezpečený triedami ObjectOutputStream a ObjectInputStream. Tieto triedy vlastnia okrem iných aj metódy readObject() a writeObject(). Pomocou nich prečítate a zapíšete všetky dáta, ktoré objekt obsahuje.

Ukážme si na konkrétnom príklade, akým spôsobom môžete využiť serializáciu objektu pri vývoji servletov. Naším cieľom bude vytvoriť servlet tak, aby si zachoval hodnotu premennej, predstavujúcu počet prístupov k nemu. Doplňujúcou požiadavkou neskôr bude, aby bola táto hodnota zachovaná a načítaná aj medzi jednotlivými životnými cyklami servletu. Príklad si môžete vyskúšať on-line a k dispozícii je aj zvýraznený kód SimpleCounter.java.

package interval;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SimpleCounter extends HttpServlet {
   private int calledCount;
   public void init(ServletConfig config) throws ServletException {
      super.init(config);
      calledCount = 0;
   }
   protected void service(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException {
      res.setContentType(„text/html“);
      PrintWriter out = res.getWriter();
      out.println(„<HTML><BODY>“);
      out.println(„<H2>Servlet API Example – SimpleCounter</H2><HR>“);
      ++calledCount;
      out.println(„<H4>This servlet has been called: “ + calledCount +
         “ times.</H4>“);
      out.println(„</BODY><HTML>“);
      out.close();
   }
}

SimpleCounter je jednoduchý servlet obsahujúci inštančnú premennú calledCount, ktorá je inicializovaná v metóde init(). Pri každej požiadavke na servlet túto premennú inkrementujem o 1. Spomeňte si na článok o životnom cyklu servletu. V tomto článku je uvedené, že metóda init() sa vykonáva len raz, pri vytváraní inštancie servletu. A práve v tomto okamihu nastavím počet prístupov na nulu.

Cieľom uvedeného príkladu bolo ukázať, že inštančné premenné servletu zostávajú aktívne počas celej doby životného cyklu servletu. A zároveň, že jednotlivé vlákna vytvorené pri požiadavkách na servlet (metóda service()), majú prístup k týmto premenným. Môžeme povedať, že servlet je perzistentný medzi jednotlivými požiadavkami. Podmienka číslo jedna je teda splnená.

A máme tu podmienku číslo dve. Chceme, aby servlet bol perzistentný aj medzi jednotlivými životnými cyklami. Vieme, že životný cyklus servletu je ukončený buď automaticky alebo „násilne“. Tak alebo tak, po znovuvytvorení servletu v pamäti začne pre inštanciu servletu nový životný cyklus. Pre našu premennú calledCount to znamená, že bude nastavená na nulu. Naším cieľom však je sledovať skutočný počet prístupov k servletu. Logicky nám vychádza, že prístupy musíme zapisovať niekam, kde sa tento údaj nestratí ani pri ukončení inštancie servletu. Napríklad do súboru na disk. A sme pri serializácii.

Obohatíme teda náš vytvorený príklad tak, aby sme splnili aj druhú podmienku. Povedal som, že serializovateľné triedy musia implementovať interface Serializable. Vytvoríme si preto novú triedu SaveServletStats, ktorá bude v sebe uchovávať počítadlo prístupov a bude serializovateľná.

class SaveServletStats implements Serializable {
   public int calledCount = 0;
}

Táto trieda bude teda uchovávať počet prístupov k servletu. Aby sme zabezpečili perzistenciu servletu medzi životnými cyklami, musíme ukladať premennú calledCount do súboru. Následne musíme tento súbor načítať pri ďalšej inicializácii servletu. Vytvoríme vlastný objekt SaveServletStats s premennou calledCount. A pomocou ObjectOutputStream budeme tento objekt serializovať. V metóde init() získavam z objektu ServletConfig názov súboru, a následne obnovím objekt SaveServletStats zo serializovaného súboru použitím ObjectInputStream. PersistentCounter servlet demonštruje ako to spraviť. Môžete si ho vyskúšať a tiež si prezrieť jeho zvýraznený zápis.

package interval;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PersistentCounter extends HttpServlet {
   private SaveServletStats stats;
   private String filename;
   class SaveServletStats implements Serializable {
      public int calledCount = 0;
   }
   public void init(ServletConfig config) throws ServletException {
      super.init(config);
      filename = config.getInitParameter(„filename“);
      stats = new SaveServletStats();
      if (filename != null) {
         try { ObjectInputStream in = new ObjectInputStream(
            new FileInputStream(filename + „.ser“));
            stats = (SaveServletStats) in.readObject();
            in.close(); }
         catch (Exception e) { e.printStackTrace(); }
      }
   }
   public void doGet(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException {
      res.setContentType(„TEXT/HTML“);
      PrintWriter out = res.getWriter();
      out.println(„<HTML><TITLE>PersistentCounter</TITLE><BODY>“);
      stats.calledCount++;
      out.println(„<H4>This servlet has been called: </H4><BR>“);
      out.println(„<B>“ + stats.calledCount + „</B> times since the servlet
         was loaded ALL servlet life-cycle sessions<BR>“);
      synchronized (this) {
         if (filename != null) {
         ObjectOutputStream outstats = new ObjectOutputStream(
            new FileOutputStream(filename + „.ser“));
         outstats.writeObject(stats);
         out.println(„Saving stats file: “ + stats.calledCount + „times <BR>“);
         outstats.close();
         }
      }
      out.println(„</BODY></HTML>“);
      out.close();
   }
}

Aby bol servlet kompletný, treba samozrejme aj aktualizovať súbor web.xml. Zaregistrovať servlet a vložiť inicializačné parametre. Ak tak nevykonáte, servlet nebude mať prístup k parametru predstavujúcemu serializovaný súbor. Na platforme Windows sa súbor automaticky vytvorí v zložke \WinDIR\System32\. Na platforme Linux hľadajte serializovaný súbor v adresári %CATALINA_HOME/bin/.

<servlet>
   <servlet-name>Persistent</servlet-name>
   <servlet-class>interval.PersistentCounter</servlet-class>
   <init-param>
      <param-name>filename</param-name>
      <param-value>statsfile</param-value>
   </init-param>
</servlet>

V metóde doGet() pri každej požiadavke ukladáme objekt stats do súboru. Takýto postup môže znížiť výkon servera, hlavne pri väčšom počte súčasných prístupov. Určitým riešením tohto problému by bolo vložiť blok, v ktorom ukladáme súbor, do metódy destroy(). Ak si spomínate, túto metódu vyvolá servlet container pred ukončením inštancie servletu, teda pred ukončením jeho životného cyklu. V tom ale spočíva riziko, v prípade náhleho pádu (vypnutia) servera sa tak nestane. A objekt nebude serializovaný.

Výkon znižuje aj synchronizácia prístupu k inštančným premenným. Na druhej strane to zaručuje, že v jednom okamihu len jedno vlákno môže aktualizovať počítadlo prístupov. Zabezpečí sa tak určitá dátová integrita.

Dúfam, že vám dnešný článok pomohol lepšie pochopiť, k čomu je dobrá perzistencia a serializácia. Ukážkové príklady si môžete stiahnuť a vylepšiť.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

1 Příspěvěk v diskuzi

  1. Ahoj. Stojím před problémem naučit se celou řadu technologií. Abych získal nějaký celkový přehled, potřeboval bych na každou z nich seriál jako je tento. Stručné, výstižné, obsažné. Děkuji za všechny.

Odpovědět