Java na web VII. - MVC I.

V dnešním díle seriálu si předvedeme architekturu MVC v praxi, vytvoříme controller a budeme pomocí něj zpracovávat příchozí GET a POST dotazy.

1.7.2013 00:00 | Petr Horáček | přečteno 12304×

Aplikace z minulého dílu byla postavena na samotném servletu, který zpracovával dotazy přicházející z jediné URL. Rozšiřování takové aplikace by mohlo probíhat přidáváním dalších servletů, s větším rozsahem by ale přišli i problémy s přehledností, údržbou a dalším rozšiřováním. Tento problém můžeme ale snadno vyřešit jedním controllerem jakožto centrálním prvkem zajišťujícím základní logiku, servírování dat a předávání viewů.

Příprava projektu

Dejme se tedy do práce. Vytvořte nový projekt webové aplikace s názvem JNW7 (jako Java Na Web 7). V kartě Source Packages vytvořte nový balíček s názvem servlety. Do vytvořeného balíčku přidejte nový servlet Controller, v průvodci vytvořením však neodškrtávejte možnost „Add information to deployment descriptor (web.xml)“, do vstupu URL Pattern(s) pak přidejte všechny URL, které bude naše aplikace využívat, tedy: „/zapisky, /upravit, /pridat, /smazat, /ulozitupravy“. Po vygenerování servletu si všimněte anotace určující adresy jež mají být přesměrovány k servletu.>

Vygenerovaný obsah třídy servletu smažte a vložte toto:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
       					 throws ServletException, IOException {
    String adresa = request.getServletPath();
    if(adresa.equals("/zapisky")) {
        // TODO: kód pro výpis zápisků
    }
    else if(adresa.equals("/upravit")){
        // TODO: kód pro vygenerování stránka určené k úpravě zápisku
    }
    else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) 
        					throws ServletException, IOException {
    String adresa = request.getServletPath();
    request.setCharacterEncoding("UTF-8");
    if(adresa.equals("/pridat")) {
        // TODO: kód pro zapsání nového zápisku do seznamu
    }
    else if(adresa.equals("/ulozitupravy")){
        // TODO: kód pro uložení úprav provedených na zápisku
    }
    else if(adresa.equals("/smazat")){
        // TODO: kód pro smazání zápisku ze seznamu
    }
    else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

Jak vidíte, nachází se zde dvě metody, první z nich zpracovává příchozí dotazy pomocí metody GET, v našem případě obstarává a) výpis stránky se seznamem zápisků na adrese /zapisky a b) výpis stránky s formulářem pro úpravu stávajících zápisků na adrese /upravit. Prohlédněme si obsah této metody. Hned na začátku uložíme požadovanou relativní URL do proměnné adresa. Následovně projdeme několik podmínek, pomocí kterých se pokusíme adresu přiřadit k určité akci, Pokud nebude k adrese žádná akce přiřazena, klientovi se odešle HTTP hláška 404 „soubor nenalezen“.

Tip: Pro zlepšení přehlednosti vyvíjeného projektu můžeme využít zvláštní komentáře začínajících slovem „TODO:“ a pokračujících textem popisujícím chybějící kód. Pokud si teď necháme zobrazit panel Action Items (Window → Action Items), objeví se před námi seznam všech těchto komentářů (umístěných v souboru, v projektu, či ve všech projektech).

Druhá metoda je té první velice podobná. Zpracovává dotazy s metodou POST pro a) přidání nového zápisku na adrese /pridat, b) uložení úprav stávajícího zápisku na adrese /ulozit upravy a c) smazání zápisku na adrese /smazat. Oproti předchozí metodě je zde navíc řádek nastavující kódování dotazu v UTF-8.

Tip: Vytvořenou kostru Controlleru si můžete pro další použití uložit jako šablonu, stačí v kartě Source Packages kliknout pravým tlačítkem na třídu Controller a zadat „Save As Template“. Nyní můžete přidat tento Controller do projektu jako kterýkoliv jiný soubor.

Tvorba nového souboru

Model

Stejně jako v minulém díle, budou i nyní základní jednotkou aplikace jednotlivé zápisky, tentokrát se ale budou skládat ze dvou textových polí – nadpisu a obsahu. Pojďme tedy vytvořit jejich model. V kartě Source Packages vytvořte nový balíček s názvem modely. Do tohoto balíčku přidejte novou třídu Javy pod názvem Zapisek a vložte do ní kód modelu:

package modely;

public class Zapisek {

    private String nadpis;
    private String obsah;

    public Zapisek(String nadpis, String obsah){
        this.nadpis = nadpis;
        this.obsah = obsah;
    }  

    public void setNadpis(String nadpis){
        this.nadpis = nadpis;
    }

    public void setObsah(String obsah){
        this.obsah = obsah;
    }

    public String getNadpis(){
        return nadpis;
    }

    public String getObsah(){
        return obsah;
    }  
}

Tato třída obsahuje dvě soukromé proměnné nadpis a obsah, ty je možné upravovat a číst pomocí metod getNadpis(), getObsah(), setNadpis() a setObsah(). Názvy těchto metod musí vždy začínat get/set, následovat po nich musí název proměnné s velkým prvním písmenem. Při tvorbě nového zápisku je nutné předat nadpis i obsah.

Tip: Pro zobrazení nápovědy příkazů stiskněte Ctrl+Mezerník, seznam se přizpůsobuje již napsanému textu a okolnímu kódu.

Controller podruhé

Vraťme se zpět ke controllerum do jeho třídy mimo metody vložíme nový seznam zápisků, který bude sloužit jako provizorní databáze:

public List<Zapisek> zapisky = new ArrayList(); 

Nyní už k funkcím na určených adresách.

/zapisky

Začněme s výpisem seznamu zápisků. Tento kód bude vypadat stejně jako v minulém díle:

if(adresa.equals("/zapisky")){
    request.setAttribute("zapisky", zapisky);
    request.getRequestDispatcher("/WEB-INF/view/zapisky.jsp").forward(request, response);
}

Na prvním řádku zapíšeme do atributu requestu seznam zápisků, na druhém pak pomocí RequestDispatcheru předáme objekty request a response viewu zapisky.jsp.

Kořenová adresa /

V minulém díle se zápisky vypisovaly na kořenové adrese aplikace, stejně bychom to chtěli i dnes, v servletu Controller je však zadání kořenového URL / poněkud problematické, proto se nachází výpis zápisků na adrese /zapisky. Nastavení domovské adresy aplikace je však jednoduché.

Ve složce WEB-INF vytvoříme nový Standard Deployment Descriptor (web.xml), možná jej bude nutné najít v nabídce other. V novém DD se přesuňme do záložky Pages a přidejme novou Welcome page (uvítací stránku) „zapisky“, adresy je sem nutné zadávat bez prvního lomítka. V XML to vypadá asi takto:

<welcome-file-list>
   <welcome-file>zapisky</welcome-file>
</welcome-file-list>

/upravit

Vraťme se zpět ke controlleru, na adrese /upravit se bude nacházet kód sloužící pro vypsání stránky s formulářem úpravy. Konkrétní zápisek identifikujeme pomocí jeho indexu, který předáme jako parametr v adrese. Nápříklad úprava zápisku č. 2 se tedy bude nacházet na adrese /upravit?index=2. Kód tedy bude vypadat nějak takto:

else if(adresa.equals("/upravit")){
    int index = Integer.parseInt(request.getParameter("index"));
    if(zapisky.get(index) != null) {  
        request.setAttribute("zapisek", zapisky.get(index));
        request.getRequestDispatcher("/WEB-INF/view/upravit.jsp").forward(request, response);    
    }
    else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

Nejprve zde přijmeme parametr index a poté ověříme zda se zápisek o onom indexu v seznamu nachází. Pokud vše vyjde, do requestu se zapíše vyžádaný zápisek a zobrazí se daný view. Pokud nebude zápisek existovat odešle se chybová hláška 404.

Přesměrování

Metoda POST je často využívána ke zpracování formulářů, u kterých si nepřejeme nebo není možné předávat atributy v adresním řádku. My tuto metodu v naší aplikaci užijeme pro smazání, vytvoření a úpravu zápisků. Po každé z těchto operací je třeba uživatele opět přesměrovat na původní stránku. Pro toto přesměrování na metodu GET se užívá HTTP stav 303, tedy přesměrování (více o HTTP stavech na: http://cs.wikipedia.org/wiki/Stavov%C3%A9_k%C3%B3dy_HTTP).

Abychom se nemuseli opakovat v zadávání této často užívané operace vytvoříme si vlastní funkci:

private void presmeruj(HttpServletRequest request, HttpServletResponse response, String url) {
    response.setStatus(HttpServletResponse.SC_SEE_OTHER);
    response.setHeader("Location", request.getContextPath() + url);
}

Jak vidíte, funkci je třeba předat adresu pro přesměrování a také objekty request a response. Najprve nastavíme HTTP stav na SC_SEE_OTHER (tedy právě 303) a poté nastavíme hodnotu HTTP hlavičky Location na relativní adresu pro přesměrování. Na začátek této hlavičky potřebujeme zadat kontextovou kořenovou URL aplikace (tu jsme zadali při tvorbě projektu a je mi možné měnit v souboru context.xml). Díky tomuto nastavení základní URL bude naše aplikace snadno přenostitelná například na produkční server.

/pridat

Na adrese /pridat probíhá ukládání nového zápisku (podobně jako v minulém díle).

if(adresa.equals("/pridat")) {
    String nadpis = request.getParameter("nadpis");
    String obsah = request.getParameter("obsah");
    if(!nadpis.isEmpty() && !obsah.isEmpty()){
        zapisky.add(new Zapisek(nadpis, obsah));
        presmeruj(request, response, "/");
    }
    else {
        presmeruj(request, response, "/?upozorneni=True");
    }          
}

Nejprve z requestu převezmeme nadpis a obsah zápisku. Pokud jsou obě pole vyplněná, zapíšeme nový zápisek do seznamu a uživatele přesměrujeme na úvodní stránku. Pokud nejsou vstupy vyplňeny, přesměrujeme uživatele na úvodní stránku a navíc přidáme do adresy parametr ?upozorneni=True, který vybídne view k vypsání upozornění.

/ulozitupravy

Na této adrese budeme ukládat změny provedené ze stránky /upravit. Postup je podobný jako u přidávání nového příspěvku, nyní ale navíc přijímáme index upravovaného příspěvku. Pokud jsou všechna pole vyplněna, nová data se uloží na místo již existujícího zápisku a proběhne přesměrování na úvodní stránku. Pokud zůstalo některé pole nevyplněno, bude uživatel přesměrován na stránku úpravy zápisku a do adresy přidáme parametr vybízející k vypsaní upozornění.

else if(adresa.equals("/ulozitupravy")){
    int index = Integer.parseInt(request.getParameter("index"));
    String nadpis = request.getParameter("nadpis");
    String obsah = request.getParameter("obsah");
    if(!nadpis.isEmpty() && !obsah.isEmpty()){
        zapisky.set(index, new Zapisek(nadpis, obsah));
        presmeruj(request, response, "/");
    }
    else {
        presmeruj(request, response, "/upravit?index=" + index + "&upozorneni=True");
    }
}

/smazat

Na adrese /smazat se převezme index zápisku, provede se jeho smazání ze seznamu a proběhne přesměrování na úvodní stránku.

else if(adresa.equals("/smazat")){
    int index = Integer.parseInt(request.getParameter("index"));
    zapisky.remove(index);
    presmeruj(request, response, "/");
}

Závěr

To je ke controlleru vše. V dalším navazujícím díle si vytvoříme příslušné view-y, základní šablonu stránek a předvedeme další funkce z knihovny JSTL.

Zdrojové kódy aplikace naleznete na GitHubu: https://github.com/PetrHoracek/JavaNaWeb

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1983