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 12299×
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ů.
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.
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.
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.
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.
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>
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.
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.
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í.
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"); } }
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, "/"); }
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