10.1 Trelagsmodellen 192
10.2 Model 1 og model 2-arkitekturer 192
10.2.1 Model 1-arkitektur: Logik sammen med HTML 192
10.2.2 Model 2-arkitektur: Logik adskilt fra HTML 192
10.3 Model-View-Controller-arkitekturen 193
10.3.1 Modellen 193
10.3.2 Præsentationen 194
10.3.3 Kontrolløren 194
10.3.4 Informationsstrøm gennem MVC 194
10.4 Eksempel på MVC: Bankkonti 196
10.4.1 Model (Bankmodel.java og Kontomodel.java) 196
10.4.2 Javabønnen (Brugervalg.java) 197
10.4.3 Præsentationen (JSP-siderne) 199
10.4.4 Kontrolløren (kontrol.jsp) 202
10.4.5 Fejlhåndtering (fejl.jsp) 204
10.4.6 Hvordan præsentation henviser til kontrollør 205
10.5 Designmønster: Frontkontrol 205
10.5.1 Variationer (til større applikationer) 205
10.5.2 Implementering af en Frontkontrol 206
10.5.3 Eksempel på en Frontkontrol 206
10.6 Test dig selv 208
10.7 Resumé 208
10.8 Rammer for webarkitekturer 209
10.8.1 Jakarta Struts 209
10.8.2 JSF - Java Server Faces 209
10.9 Mere læsning 210
Dette kapitel er frivillig læsning; det forudsættes ikke i resten af bogen. Det forudsætter kapitel 5, Brug af databaser, og kapitel 9, Javabønner i JSP-sider.
Efterhånden som en webapplikation vokser sig større og større, bliver den sværere at overskue. Det bliver svært at finde rundt i de mange linjers kode i de mange filer og fejlfinding og videreudvikling bliver mere og mere tidskrævende.
Der opstår et behov for et system, der opdeler applikationens dele i nogle bidder, der hver i sær har et klart ansvar og som er nogenlunde afgrænset fra de andre dele af applikationen.
Et hyppigt brugt system til inddeling er at lagdele applikationen, oftest i tre lag:
Øverste lag er JSP-siderne med HTML-koden og præsentationslogik.
Mellemste lag er klasser, der tager sig af forretningslogikken i applikationen.
Nederste lag er databasen.
Denne - nærmest klassiske - opdeling er meget populær og kaldes trelagsmodellen. JSP-siderne har ikke nogen databasekode selv, men kalder metoder i forretningslogik-klasser, som så fremfinder og ændrer på de relevante data i databasen. Brugerinteraktions-kode (behandle parametre fra request-objektet og beslutning om, hvilken side brugeren skal se næste gang) ligger som regel i øverste lag.
Trelagsmodellen er en god løsning i mange sammenhænge, men i en webapplikation kan øverste lag (JSP-siderne) vokse sig endog meget stort og en yderligere opdeling derfor blive nødvendig.
Model-View-Controller-arkitekturen er et meget populært bud på hvordan trelagsmodellen yderligere kan inddeles. En stor del af kapitlet handler derfor om denne arkitektur.
Inden for webprogrammering taler man ikke om trelagsmodellen men om model 1 og model 2-arkitekturer.
I webprogrammeringens spæde barndom lavede de fleste mindre applikationer, hvor programlogikken lå sammen med HTML-koden. Et eksempel på denne måde at programmere kan findes i afsnit 5.5 (Eksempel - gæstebog).
Har man en webapplikation hvor programlogikken ligger sammen med HTML-koden kaldes det en model 1-arkitektur. Denne arkitektur har følgende konsekvenser:
Simpel struktur
Nem at starte med
Velegnet til små projekter
Svært at adskille programlogik og HTML
Samme person er programmør og HTML-designer
Decentral - hver side behandler data fra sin egen formular
Potentiel redundans (samme programlogik flere steder)
Efterhånden som webprogrammerne blev større og større og krævede mere og mere vedligeholdelse, blev følgende anbefaling mere og mere relevant:
Adskil tekstligt indhold og programkode fra hinanden, sådan at f.eks. en HTML-designer kan koncentrere sig om HTML-layout og indhold, mens en programmør kan koncentrere sig om funktionalitet og den bagvedliggende kode, som behandler data og afgør, hvilken side der skal vises
Dette kaldes en model 2-arkitektur. Denne arkitektur har følgende konsekvenser:
Mere omfattende struktur
Sværere at starte med
Lettere at vedligeholde ved større projekter
Programlogik og HTML relativt adskilt
Forskellige personer kan tage sig af programmering og HTML-design
Centraliseret: Programlogik og beslutning om, hvilken side, der skal vises sker ét sted
En udbygning af tankegangen om at adskille tekstligt indhold og programkode er også, at adskille sprogligt indhold fra HTML-design, f.eks. for at kunne lade webapplikationen kunne køre på flere sprog (internationalisering). Dette behandles i kapitel 13, Internationale sider.
Model 2-arkitekturen kaldes også Model-View-Controller-arkitekturen. I næste afsnit vil vi gå i dybden med delene i teorien og i det efterfølgende afsnit give et konkret eksempel.
Model-View-Controller-arkitekturen (forkortet MVC)
er et designmønster beregnet til programmer med en
brugergrænseflade. Den anbefaler, at man opdeler ens
applikation (i hvert fald mentalt) i tre dele: En model, en
præsentation og en kontrollør:
Model-View-Controller-arkitekturen og det meste af ovenstående diagram er ikke specielt rettet mod webapplikationer, men gælder for alle slags programmer. På ovenstående diagram er forklaringer, der er specielt rettet mod webapplikationer, skrevet i kursiv.
Lad os nu kigge nærmere på de tre dele af Model-View-Controller-arkitekturen.
Modellen indeholder data og registrerer, hvilken tilstand den pågældende del af programmet er i
Oftest er data indkapslet i nogle klasser, sådan at konsistens sikres. I så fald er der kun adgang til at spørge og ændre på data gennem metodekald.
Modellen bør være uafhængig af, hvordan data præsenteres over for brugeren og er der flere programmer, der arbejder med de samme slags data, kan de i princippet have den samme datamodel, selvom de i øvrigt er helt forskellige.
Eksempel: En bankkonto har navn på ejer, kontonummer, kort-ID, saldo, bevægelser, renteoplysninger e.t.c.. Saldoen kan ikke ændres direkte, men med handlingerne overførsel, udbetaling og indbetaling kan saldoen påvirkes (se eksempelvis klassen Kontomodel i afsnit 10.4.1).
Bemærk hvordan modellen for en bankkonto er universel. Modellen kunne anvendes f.eks. både i et program til en Dankort-automat, et hjemmebank-system og i programmet som kassedamen anvender ved skranken.
Den samme model kunne anvendes både af en webapplikation og et almindeligt program.
Præsentationen (eng.: View) henter relevante data fra modellen og viser dem for brugeren i en passende form
Selvom to præsentationer deler model (viser data fra samme model) kan de være meget forskellige, da de er beregnet på en bestemt brugergrænseflade.
Eksempel: Bankkontoen præsenteres meget forskelligt. I en Dankort-automat vises ingen personlige oplysninger overhovedet. I et hjemmebank-system kan saldo og bevægelser ses. Ved skranken kan medarbejderen se endnu mere, f.eks. filial og kontaktperson i banken (det kunne være implementeret som en grafisk applikation, der kører hos brugeren).
I en webapplikation producerer præsentationen HTML-koden, som brugeren ser. Oftest er præsentationen implementeret som nogle JSP-sider (nogle kan dog foretrække servletter).
Kontrolløren (eng.: Controller) definerer hvad programmet kan. Den omsætter brugerens indtastninger til handlinger, der skal udføres på modellen
Eksempel: I Dankort-automaten kan man kun hæve penge. I et hjemmebank-system kan brugeren måske lave visse former for overførsel fra sin egen konto. Ved skranken kan medarbejderen derudover foretage ind- og udbetalinger.
I en webapplikation er kontrolløren den, der modtager alle parametre fra formularer fra klienten og derefter beslutter, hvad der skal ske. Oftest er præsentationen implementeret som en eller flere servletter (nogle kan dog foretrække at bruge JSP-sider).
Figuren herunder illustrerer hvordan strømmen af information går fra modellen, via præsentationen til brugeren (symboliseret ved et øje).
Brugeren foretager nogle handlinger (symboliseret ved musen), som via kontrolløren fortolkes som nogle ændringer, der foretages på modellen, hvorefter de nye data vises for brugeren.
I praksis foregår det i en webapplikation ved, at brugeren udfylder en formular, som sendes til serveren, hvor de modtages af kontrolløren. Kontrolløren fortolker parametrene og beslutter, hvad det er brugeren vil gøre og kalder de tilsvarende metoder på modellen. Til sidst omdirigerer kontrolløren til en præsentations-side med HTML-koden brugeren skal se.
Det følgende er et eksempel på hvordan en MVC-arkitektur kunne implementeres.
Det består af følgende JSP-sider og klasser:
Pilene viser hvilke sider/klasser, der kender til andre sider/klasser.
Kontrolløren er her lavet som JSP-siden kontrol.jsp. Den modtager alle forespørgsler, ændrer i objekterne og omdirigerer til en af de andre sider. De andre JSP-sider præsenterer data, som de aflæser fra objekterne og kender ikke til kontrolløren.
Modellen består af forretningslogikobjekterne Bankmodel og Kontomodel. Hertil kommer Javabønnerne Login og Brugervalg, som husker og behandler data nærmere præsentationslaget1. Der findes ét Brugervalg- og Login-objekt per bruger (knyttet til brugerens session).
I det følgende gennemgås koden i rækkefølgen Model, Præsentation (d.v.s. de JSP-sider der producerer HTML) og til sidst kontrolløren.
Vi har brug for en liste over samtlige konti i banken. Denne klasse kaldes Bankmodel:
Bankmodel.java
package bank; import java.util.*; public class Bankmodel { public List konti = new ArrayList(); public Bankmodel() { konti.add(new Kontomodel("Jacob", 1001, 113.75)); konti.add(new Kontomodel("Jacob", 1002, 555.00)); konti.add(new Kontomodel("Preben", 1101, 8.50)); konti.add(new Kontomodel("Søren", 1201, 78.25)); } }
Bankmodellen burde selvfølgelig, i et mere realistisk eksempel, have sine data i en database, men for letheds skyld opretter den her blot en fast liste over konti.
Hver konto er repræsenteret af et Kontomodel-objekt, med ejer, id (kontonummer), saldo og en liste over de bevægelser, der har været på kontoen.
Kontomodel.java
package bank; import java.util.*; public class Kontomodel { private String ejer; private int id; private double saldo; private List bevægelser = new ArrayList(); public Kontomodel(String ejer1, int id1) { ejer = ejer1; id = id1; } public Kontomodel(String ejer1, int id1, double saldo1) { ejer = ejer1; id = id1; saldo = saldo1; } public String getEjer() { return ejer; } public int getId() { return id; } public double getSaldo() { return saldo; } public List getBevægelser() { return bevægelser; } public String toString() { return ejer + ": "+saldo+" kr"; } public void overfør(Kontomodel til, double beløb) { if (beløb<=0) throw new IllegalArgumentException("Beløb skal være positivt"); if (beløb>saldo) throw new IllegalArgumentException("Der er ikke penge nok"); saldo = saldo - beløb; til.saldo = til.saldo + beløb;// privat variabel kan ændres i samme klasse String ændring = "Overført "+beløb+" fra "+ejer+" til "+til.ejer; bevægelser.add(ændring); til.bevægelser.add(ændring); } public void hæv(double beløb) { if (beløb<=0) throw new IllegalArgumentException("Beløb skal være positivt"); if (beløb>saldo) throw new IllegalArgumentException("Der er ikke penge nok"); saldo = saldo - beløb; bevægelser.add("Hævet "+beløb); } public void indsæt(double beløb) { if (beløb<=0) throw new IllegalArgumentException("Beløb skal være positivt"); saldo = saldo + beløb; bevægelser.add("Indsat "+beløb); } }
Bemærk hvordan klassen kun koncentrerer sig om forretningslogik.
Opstår der en fejlsituation i en metode, kastes en undtagelse, der beskriver fejlen, sådan at kalderen kan håndtere den (se hvordan senere, i afsnit 10.4.5, Fejlhåndtering).
Her er Brugervalg-klassen. Metoderne, der svarer til egenskaber, er fremhævet med fed.
Brugervalg.java
package bank; import java.util.*; public class Brugervalg { /** Egenskab 'bankmodel' sættes fra JSP-siden kontrol.jsp når bønnen oprettes */ Bankmodel bank; public void setBankmodel(Bankmodel b) { bank = b; } /** Liste over denne brugers konti */ public ArrayList konti; /** Den konto, brugeren har valgt at arbejde med lige nu */ public Kontomodel konto; /** Egenskab 'kontovalg' sættes fra JSP-side vaelg_konto.jsp*/ public void setKontovalg(int nr) { konto = null; for (int i=0; i<konti.size(); i++) { Kontomodel k = (Kontomodel) konti.get(i); if (k.getId() == nr) konto = k; } if (konto==null) throw new IllegalArgumentException("Ukendt Konto-ID: "+nr); } /** Streng der beskriver en handling brugeren ønsker at udføre */ public String handling; public void setHandling(String h) { handling = h; } /** Beløbet handlingen (hæv/sæt ind/overfør) drejer sig om */ double handlBeløb; public void setBeloeb(double b) { handlBeløb = b; } /** Hvis der skal foretages en overførsel, hvilken konto er det til */ Kontomodel ovfTil; /** Egenskab 'tilKontoId' sættes fra JSP-siden vis_konto.jsp */ public void setTilKontoId(int nr) { ovfTil = null; for (int i=0; i<bank.konti.size(); i++) { Kontomodel k = (Kontomodel) bank.konti.get(i); if (k.getId() == nr) ovfTil = k; } if (ovfTil==null) throw new IllegalArgumentException("Ukendt Konto-ID: "+nr); } public void udførHandling() { try { if (handling.equals("haev")) konto.hæv(handlBeløb); else if (handling.equals("saet_ind")) konto.indsæt(handlBeløb); else if (handling.equals("overfoer")) konto.overfør(ovfTil, handlBeløb); else System.out.println("Ukendt handling: "+handling); } finally { handling = null; // selv hvis noget gik helt galt skal data nulstilles handlBeløb = 0; ovfTil = null; } } }
En udskrift af Login-bønnen kan ses i afsnit 9.5.2.
Først log_ind.jsp, hvor brugeren kan logge ind. Den genbruger Login-bønnen fra afsnit 9.5:
log_ind.jsp
<jsp:useBean id="login" class="javabog.Login" scope="session"/> <jsp:setProperty name="login" property="*"/> <html> <head><title>Log ind</title></head> <body> <h1>Log ind</h1> <form> <table> <tr> <td>Brugernavn:</td> <td><input name="brugernavn" type="text" value='<jsp:getProperty name="login" property="brugernavn"/>'></td> </tr> <tr> <td>Adgangskode:</td> <td><input type="password" name="adgangskode"></td> </tr> <tr> <td></td> <td><input type="submit" name="handling" value="log ind"></td> </tr> </table> </form> <p><font color="red"><%= login.getMeddelelse() %></font><br> Vink: Brugere 'Jacob', 'Preben' og 'Søren' har adgangskoden 'hemli'.</p> </body> </html>
Når brugeren først er logget ind skal han, hvis han har flere konti, vælge hvilken konto han vil arbejde med. Det sker i vaelg_konto.jsp:
vaelg_konto.jsp
<jsp:useBean id="valg" class="bank.Brugervalg" scope="session" /> <html> <head><title>Vælg konto</title></head> <body> <h1>Vælg hvilken konto du vil bruge</h1> <form> <% for (int i=0; i<valg.konti.size(); i++) { bank.Kontomodel k = (bank.Kontomodel) valg.konti.get(i); %> <input type="radio" name="kontovalg" value="<%= k.getId() %>"> Konto <%= k.getId() %> med <%= k.getSaldo() %> kr.<br> <% } %> <input type="submit" value="Vælg konto"> </form> <p><a href="?handling=log_ud">Log ud</a> </body> </html>
Når man har valgt en konto, kommer man videre til vis_konto.jsp:
vis_konto.jsp
<%@ page import="java.util.*" %> <jsp:useBean id="valg" class="bank.Brugervalg" scope="session" /> <html> <head><title>Vis konto</title></head> <body> <h1>Konto <%= valg.konto.getId() %> med ejer <%= valg.konto.getEjer() %></h1> Saldo: <%= valg.konto.getSaldo() %><p> Bevægelser:<br> <% List bev = valg.konto.getBevægelser(); for (int i=0; i<bev.size(); i++) { %> <%= bev.get(i) %><br> <% } %> <hr> Hvad vil du gøre:<p> <form> <input type="hidden" name="handling" value="haev"> Hæv <input type="text" name="beloeb" size="4"> kr. fra kontoen. <input type="submit" value="Hæv"> </form> <p> <form> <input type="hidden" name="handling" value="saet_ind"> Sæt <input type="text" name="beloeb" size="4"> kr. ind på kontoen. <input type="submit" value="Sæt ind"> </form> <p> <form> <input type="hidden" name="handling" value="overfoer"> Overfør <input type="text" name="beloeb" size="4"> kr. til konto nr. <input type="text" name="tilKontoId" size="6">. <input type="submit" value="Overfør"> </form> <p><a href="?handling=log_ud">Log ud</a> </body> </html>
Hvis brugeren ikke har nogen konti tilknyttet, kommer han til ingen_konto.jsp i stedet for vaelg_konto.jsp:
ingen_konto.jsp
<jsp:useBean id="login" class="javabog.Login" scope="session"/> <html> <head><title>Ingen konto</title></head> <body> <h1>Ingen konti fundet.</h1> Brugeren <%= login.getBrugernavn() %> har ingen konti tilknyttet.<p> Kontakt venligst banken for at få oprettet en konto. <p><a href="?handling=log_ud">Log ud</a> </body> </html>
Her kommer kontrol.jsp, der sørger for kontrollør-delen af applikationen: At manipulere med modellen (bønnernes egenskaber oftest) ud fra brugerens valg (den indkomne formular) samt at beslutte, hvilken side der derefter skal vises.
Den er lavet som en JSP-fil, men producerer ikke selv noget output (den omdirigerer i stedet til præsentations-JSP-filerne vist tidligere).
kontrol.jsp
<%-- Hvis der opstår en undtagelse omdirigeres automatisk til fejl.jsp --%> <%@ page contentType="text/html; charset=iso-8859-1" errorPage="fejl.jsp" import="java.util.*, bank.*" %> <jsp:useBean id="login" class="javabog.Login" scope="session"> <% login.init(application); %> <%-- køres første gang bønnen bruges --%> </jsp:useBean> <jsp:setProperty name="login" property="*" /> <% String handling = request.getParameter("handling"); String brugernavn = login.getBrugernavn(); //<title>kontrol</title> if ("log ind".equals(handling)) login.tjekLogin(); if ("log_ud".equals(handling)) login.setAdgangskode(""); if (!login.isLoggetInd()) { // er brugeren logget korrekt ind? application.log("Bruger "+brugernavn+" skal logge ind."); session.removeAttribute("valg"); // eller evt: session.invalidate() request.getRequestDispatcher("log_ind.jsp").forward(request,response); return; // afslut behandlingen af denne side } %> <%-- Nedenstående objekt har globalt virkefelt (svarer til en singleton) --%> <jsp:useBean id="bank" class="bank.Bankmodel" scope="application" /> <jsp:useBean id="valg" class="bank.Brugervalg" scope="session"> <% valg.setBankmodel(bank); %> <%-- sæt bankmodel første gang --%> </jsp:useBean> <jsp:setProperty name="valg" property="*" /> <% if (valg.konto == null) { // har han valgt en af sine konti? // nej - find alle brugerens konti og læg dem i valg.konti if (valg.konti == null) { valg.konti = new ArrayList(); for (int i=0; i<bank.konti.size(); i++) { Kontomodel k = (Kontomodel) bank.konti.get(i); if (k.getEjer().equalsIgnoreCase(brugernavn)) valg.konti.add(k); } } if (valg.konti.size() == 0) { application.log(brugernavn+" skal oprette konto."); request.getRequestDispatcher("ingen_konto.jsp").forward(request,response); } else if (valg.konti.size() == 1) { application.log(brugernavn+" har kun en konto, den vælger vi!"); valg.konto = (Kontomodel) valg.konti.get(0); request.getRequestDispatcher("vis_konto.jsp").forward(request,response); } else { application.log(brugernavn+" vælger ml. "+valg.konti.size()+" konti"); request.getRequestDispatcher("vaelg_konto.jsp").forward(request,response); } return; // afslut behandlingen af denne side } if (valg.handling != null) { // konto er valgt - nogen handlinger? application.log(brugernavn+" udfører handling: "+valg.handling); valg.udførHandling(); } request.getRequestDispatcher("vis_konto.jsp").forward(request,response); %>
Vi kunne også have lavet kontrolløren som en servlet (se afsnit 7.1), men da kunne vi ikke udnytte <jsp:setProperty ... />-mekanismen til at sætte bønnernes egenskaber.
Derudover skal servletter ligge i en anden mappe, adskilt fra JSP-siderne, hvilket ville gøre det mere besværligt at prøve eksemplet.
Når brugervalg-bønnen oprettes, skal den initialiseres, ved at få kaldt setBankmodel() med bank-objektet. Det gør vi ved at lægge kode ind mellem <jsp:useBean> og </jsp:useBean>, som beskrevet i afsnit 9.2.7, Initialisering af javabønner:
<jsp:useBean id="valg" class="bank.Brugervalg" scope="session"> <% valg.setBankmodel(bank); %> </jsp:useBean>
Indtaster brugeren noget forkert (f.eks. hæve et negativt beløb), opstår der en undtagelse (eng.: Exception) og siden fejl.jsp vises:
Dette sker fordi sidedirektivet errorPage="fejl.jsp" var sat i kontrol.jsp hvor fejl (undtagelser) kunne opstå:
<%-- Hvis der opstår en undtagelse omdirigeres automatisk til fejl.jsp --%>
<%@ page errorPage="fejl.jsp" %>
Hvis en undtagelse opstår, vil anmodningen derfor automatisk blive omdirigeret til siden fejl.jsp. Denne side skal have sat sidedirektivet isErrorPage="true" og har det implicitte objekt exception defineret:
fejl.jsp
<%@ page contentType="text/html; charset=iso-8859-1" isErrorPage="true" %> <jsp:useBean id="login" class="javabog.Login" scope="session"/> <html> <head><title>Fejl</title></head> <body> Der skete en fejl: <%= exception.getLocalizedMessage() %>. <p> Gå tilbage og prøv igen. <% application.log( (Exception)exception, "Fejl for "+login.getBrugernavn()); %> <p><a href="?handling=log_ud">Log ud</a> </body> </html>
Se også afsnit 4.4 og afsnit 4.5.8 for information om fejlhåndtering.
I præsentationssider har vi ikke angivet, hvor formulardata skal sendes hen. Der står blot:
<form>
og
<a href="?handling=log_ud">Log ud</a>
Vi kunne godt have skrevet kontrol.jsp ind i alle formularer og henvisninger, da det jo er den, der skal modtage data, f.eks.:
<form action="kontrol.jsp">
og
<a href="kontrol.jsp?handling=log_ud">
men det er strengt taget overflødigt, da vi bruger serverside-omdirigering (beskrevet i afsnit 4.3.2), sådan at netlæseren slet ikke ved, at der er omdirigeret til en anden side. Netlæseren indsender således formulardata til kontrol.jsp som den skal, selvom det i virkeligheden er en anden side på serveren, der har skabt formularen.
En Frontkontrol (eng.: Front Controller) sørger for at dirigere brugeren hen til den rigtige side. Hvis brugeren må navigere frit, vil frontkontrollen blot sørge for, at brugeren får de anspurgte sider. Hvis brugeren skal følge en bestemt vej gennem siderne, vil frontkontrollen tjekke brugerens indtastninger og omdirigere brugeren, sådan at vejen gennem siderne følges. Det kunne f.eks. være, at brugeren skulle indtaste brugernavn og adgangskode, for at kunne se nogle bestemte sider. Frontkontrollen ville da omdirigere brugere, der ikke er logget ind, til login-siden.
En Frontkontrol er altså en central del af webapplikationen, som alle anmodninger går igennem. Frontkontrollen beslutter, hvilken side der rent faktisk skal vises og omdirigerer til den.
JSP-siden kontrol.jsp fra afsnit 10.4.4 er næsten en frontkontrol. Den er det ikke helt, fordi man kan kan rette i URLen og på den få fat i de andre sider uden om kontrol.jsp. Senere, i afsnit 10.5.3, vil vi se på, hvad der skulle gøres, for at brugeren ikke kan gå uden om den.
I en større webapplikation bliver det besværligt, hvis én Frontkontrol skal stå for al omdirigering. Da kan man vælge, at kæde flere Frontkontroller, sådan at forskellige Frontkontroller kan stå for forskellige dele af applikationen.
Man kan også vælge, at Frontkontrollen kalder en eller flere eksterne klasser, der 'ekspederer' brugerens anmodninger og beslutter, hvad der skal ske i forskellige situationer (en ekspeditør - eng.: Dispatcher).
Skal applikationen kunne anvendes på flere forskellige sprog eller fra flere forskellige slags klienter, f.eks. både fra en almindelig netlæser (såsom Netscape/Mozilla) og fra PDA'er og mobiltelefoner, kunne man vælge at lade Frontkontrollen kalde en ekstern klasse (en 'View Mapper'), der beslutter, om klienten skal have svaret tilsendt som HTML eller som f.eks. WML.
En Frontkontrol er ofte implementeret som en servlet, men der er intet, der forhindrer en i at bruge en JSP-side som Frontkontrol i stedet. Det kan gøres på flere måder:
Manuel inklusion af JSP-side, der agerer Frontkontrol: En simpel implementering af en Frontkontrol som en JSP-side kunne være, at alle JSP-sider inkluderede en bestemt anden JSP-side, der derfor blev udført som det første på hver side. Denne side kunne f.eks. tjekke om brugeren var logget ind og lave en omdirigering af klienten (se afsnit 4.3.1, Klient-omdirigering) til login-siden, hvis det ikke var tilfældet.
Man redigerer web.xml sådan, at alle forespørgsler dirigeres til én servlet eller JSP-side, der agerer Frontkontrol. Den vælger derpå hvilken JSP-side, forespørgslen skal dirigeres videre til. Siderne ligger skjult for klienten i WEB-INF/-mappen og omdirigering skal derfor ske internt i serveren (se afsnit 4.3.2, Server-omdirigering).
Lad os se på den anden mulighed.
Ved at rette i web.xml kan man tvinge al trafikken til én side (kontrol.jsp), der derpå vælger hvilken JSP-side, der skal vises. Her er den relevante del af web.xml:
udsnit af WEB-INF/web.xml
<!-- Frontkontrol: Alt der starter med /bank sendes til kontrol.jsp --> <servlet> <servlet-name>Frontkontrol</servlet-name> <jsp-file>/WEB-INF/bank/kontrol.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>Frontkontrol</servlet-name> <url-pattern>/bank/*</url-pattern> <!-- Bemærk: * i URL-mønster --> </servlet-mapping>
Alle JSP-siderne flyttes nu ind i mappen WEB-INF/bank/, hvilket gør det umuligt for klienten at få fat i dem direkte.
Til gengæld angives, at alle forespørgsler, der passer med URL-mønstret /bank/*, skal dirigeres til frontkontrollen /WEB-INF/bank/kontrol.jsp, der dermed bliver den eneste side, brugerne får adgang til.
Lige meget hvilken URL brugeren skriver får han altså kontrol.jsp (her har en bruger forsøgt sig med bank/xyz som URL):
I omdirigeringen i kontrol.jsp skal den absolutte sti (/WEB-INF/bank/) nu angives, så:
request.getRequestDispatcher("log_ind.jsp").forward(request,response);
skal rettes til:
request.getRequestDispatcher("/WEB-INF/bank/log_ind.jsp").forward(request,response);
Afprøv bankkonto-eksemplet. Tjek om du
kan omgå kontrol.jsp og se nogle af de andre sider ved at
rette i URLen.
Ret f.eks. kode/kapitel_10/ til
kode/kapitel_10/vaelg_konto.jsp
Udfør de
ovenstående ændringer og tjek om du stadig kan omgå
kontrol.jsp.
Ellers ret URLen fra kode/kapitel_10/ til bank/
(her er rettelserne allerede foretaget).
Du kan også prøve
eksemplet fra: http://javabog.dk:8080/JSP/bank/
Der er rigtig mange rammer (eng.: frameworks) og standarder for hvordan en webapplikation med model 2-arkitektur skal bygges op. I det følgende vil de to vigtigste blive berørt.
Struts er et Open Source-projekt baseret på JSP og servletter. Det understøtter Model-View-Controller-arkitekturen beskrevet i dette kapitel, på den måde at Struts udgør Frontkontrol (d.v.s. kontrolløren) og dele af præsentationen.
Der er under rammerne for Struts bl.a. mekanismer til implementering af indsamlingen af brugerinput fra formularer, validering af brugerinput, visning af meddelelser og fejltekster med understøttelse for forskellige sprog, opbygning af websider ud fra skabeloner m.v..
Struts er indenfor de seneste par år blevet meget populært og er nu en de facto standard inden for større webapplikationer. Alle udviklingsværktøjerne nævnt i denne bog understøtter således Struts.
Java Server Faces (JSF) er et system til at designe web-baserede brugergrænseflader i Java. Det er samme idé som bruges i WebObjects fra Apple og ASP.NET Web Forms fra Microsoft: Ligesom med almindelige grafiske brugergrænseflader i AWT eller Swing har man et sæt komponenter (knapper, indtastningsfelter, afkrydsningsfelter e.t.c. og det er også muligt at definere sine egne), som man bruger til at opbygge sin webside med.
Opbygningen kan ske i en visuel designer, som man kender det fra når man designer i AWT/Swing: Hver komponent er en javabønne, hvis egenskaber kan ændres (d.v.s. som kan aflæses/sættes med get- og set-metoder) og når brugeren foretager en handling, afsender komponenterne hændelser (eng.: events), som kan aktivere netop de stumper kode, der er brug for.
JSF giver en helt anden måde at gå til webprogrammering på: I stedet for at programmere JSP-sider, der undersøger request-objektet og producerer HTML, definerer man altså en JSF-side ved hjælp af komponenter og kan så manipulere med komponenterne og definere kode, der bliver udført når brugeren gør bestemte med komponenterne, f.eks. udfylder et indtastningsfelt. JSF sørger for det mellemliggende lag, med at undersøge request-objektet og producere den rigtige HTML.
JSFs arkitektur understøtter at komponenterne producerer forskellig HTML-kode afhængig af klientens formåen, sådan at samme JSF-applikation kan bruges til PDA'er, mobiltelefoner, almindelige netlæsere etc..
Afsnit '4.3 Web-Tier
Application Structure' og ' 11.1 J2EE Architecture Approaches' i
http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/
Den officielle hjemmeside om Jakarta
Struts:
på http://struts.apache.org.
Suns officielle side om
Java Server Faces:
http://java.sun.com/j2ee/javaserverfaces/
Om JSF i 'The J2EE
Tutorial':
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/JSFIntro.html
Portal om JSF:
http://www.jsfcentral.com
Artikel: 'Developing Web Applications with
JavaServer Faces':
http://java.sun.com/developer/technicalArticles/GUI/JavaServerFaces/
Se
under 'Servlets' og under 'Libraries_and_Frameworks'
på
http://directory.google.com/Top/Computers/Programming/Languages/Java/Server-Side/
1De kunne også opfattes som en del af kontrolløren.