MVC en Servlets

Leerdoelen

Na het bestuderen van dit hoofdstuk wordt van je verwacht dat je de volgende begrippen kent en kunt gebruiken:

  • het MVC ontwerppatroon
  • java servlets
  • JavaBeans
  • Expression Language (EL)

In het veelgebruikte Model View Controller ontwerppatroon wordt onderscheid gemaakt tussen verschillende lagen in je applicatie.

mvc patroon

Onderstaande afbeelding toont de structuur van het project uit deel 4 opgezet volgens het mvc patroon. De broncode van het project met zowel een Datastore als een MySQL databaseklasse kun je hier vinden.

Data laag (package sportIO)

Bevat klasse(n) die connectie maakt/maken met de database en alle transacties met database afhandelt. Indien een andere database moet worden geïmplementeerd, hoeft alleen deze klasse te worden aangepast. Dat maakt een applicatie een stuk gemakkelijker onderhoudbaar.

Model laag (package sport)

Bevat de "business" of "domein" klassen. De kern van de applicatie. De baas van de model laag in dit voorbeeld is de klasse Administratie. Deze heeft toegang tot de andere klassen in de package en krijgt een instantie van SportIO die de connectie met de data-laag tot stand brengt.

import sportIO.SportIO;

public class Administratie {
	private SportIO io;
	private ArrayList<Lid> leden;
	private ArrayList<Team> teams;
	private boolean ledenIsLeeg;
	private boolean teamsIsLeeg;
	
	public Administratie() {
		io = new SportIO();
		leden = io.getLedenLijst();
		if (leden.isEmpty())
		    ledenIsLeeg = true;
		teams = io.getTeamLijst();
		if (teams.isEmpty())
		    teamsIsLeeg = true;
	}
	....

Control laag (package control)

De control-laag bestaat uit Servlets die het verkeer tussen gebruiker, de model-laag en de views regelt. Er is geen directe connectie tussen control-laag en data-laag. Dat verloopt via de model-laag.

De servlet in ons voorbeeld krijgt een object van het type Administratie die de connectie met de model-laag en de data-laag tot stand brengt.

public class SportServlet extends HttpServlet {
	private Administratie admin;
	....

Methodes van Servlets

Onderstaande twee methodes worden altijd geïmplementeerd en handelen de get en post requests af die door gebruiker zijn verstuurd. Vaak wordt één van de twee methodes gebruikt om alle code te schrijven en wordt de ander hiernaar doorverwezen (zie voorbeeld). Je hoeft je dan geen zorgen meer te maken over wanneer methode get en wanneer methode post wordt gebruikt.

		   
doGet(HttpServletRequest req, HttpServletResponse resp) {
    //alle benodigde code
}
doPost(HttpServletRequest req, HttpServletResponse resp) {
    //stuur door naar doGet
    doGet(req, resp)
}

Binnen doGet of doPost kan met de methode getParameter van het Request object de parameters met een bepaalde naam worden opgehaald.

		   
req.getParameter("naam van parameter");

voorbeeld

		   
if (req.getParameter("knopNieuwLid") != null) {
    // doe iets
}

Als de servlet zijn taken heeft uitgevoerd kan hij de gebruiker terug- of doorsturen naar een html/jsp pagina of servlet met sendRedirect("url").

		   
if (req.getParameter("knopNieuwLid") != null) {
    // roept hulpmethode aan die een nieuw lid toevoegt...
    this.voegNieuwLidToe(req, resp);
    //... en stuurt gebruiker (terug) naar doelpagina
    resp.sendRedirect("/index.jsp");
}

Ook kan de servlet met de methode setAttribute() een attribuut aan het request meegeven. Deze wordt vervolgens doorgestuurd naar een doelpagina (jsp of servlet) die dit attribuut kan gebruiken.

		   
req.setAttribute("<naam attribuut>", object);

In een dergelijk geval moet het request worden " gedispatched". Hiervoor heb je een Dispatcher object nodig. Voorbeeld:

Lid lid = admin.getLid(req.getParameter("spelerscode"))		   
req.setAttribute("lid", lid);
RequestDispatcher disp = getServletContext()
    .getRequestDispatcher("/wijziglid.jsp");
try {
	disp.forward(req, resp);
} catch (ServletException e) {
	e.printStackTrace();
}

In de doelpagina (jsp of servlet) kan het attribuut vervolgens worden opgevraagd met de methode request.getAttribute(naam attribuut). Het object "lid" dat in het vorige code fragment als attribuut is meegegeven kan in de jsp pagina (wijziglid.jsp) met de volgende "scriptlet" worden opgevraagd:

<%
Lid lid = request.getAttribute("lid");
%>   

Een servlet heeft een url nodig. Je kunt de url van een servlet "mappen" in het web.xml bestand. Dit bevindt zich in de map WEB-INF.

<servlet>
	<servlet-name>Sport</servlet-name>
	<servlet-class>control.SportServlet</servlet-class>
</servlet>
	
<servlet-mapping>
	<servlet-name>Sport</servlet-name>
	<url-pattern>/sport</url-pattern>
</servlet-mapping>		   

Een simpel voorbeeld

We gaan de bekende rekenmachine uit deel 3 en 4 nu maken met behulp van een MVC ontwerppatroon. Tot nu toe hebben we met scriptlets in onze jsp pagina's gewerkt, maar eigenlijk is het gebruik hiervan sinds de invoering van jsp-2 in 2002 verouderd. In dit voorbeeld werken we met een jsp pagina die geen scriptlets bevat. Om dit mogelijk te maken gebruiken we een zogenaamde JavaBean en "Expression Language" (EL).

Uitwerking stap voor stap

Maak een project met een package rekenmachine. Maak in de package een java klasse Rekenmachine. Uiteindelijk moet de rekenmachine twee strings kunnen tonen: de uitkomst van de berekening en een eventuele waarschuwing als gebruiker bijvoorbeeld per ongeluk een letter in plaats van een cijfer invoert.

JavaBean (model)

Als je van een klasse een JavaBean maakt, kun je straks in je jsp pagina attributen van de klasse aanroepen met een speciale tag. Om als bean te fungeren moet de klasse aan de volgende voorwaarden voldoen:

import java.io.Serializable;

@SuppressWarnings("serial")
public class Rekenmachine implements Serializable {
    private String uitkomst; 
    private String waarschuwing;
	
    //constructor met parameters
    public Rekenmachine(String getal1, String getal2, String functie) {
        uitkomst = "";
        waarschuwing = "";
        bereken(getal1, getal2, functie);
    }
	
    //parameterloze constructor voor bean
    public Rekenmachine() {
        uitkomst = "";
        waarschuwing = "";
    }

    //hulpmethode
    private void bereken(String getal1, String getal2, String functie) {
		
        try {
            double arg1 = Double.parseDouble(getal1);
            double arg2 = Double.parseDouble(getal2);
			
            switch (functie) {
			
                case "+": uitkomst +=  (arg1 + arg2);
                break;
			
                case "-": uitkomst +=  (arg1 - arg2);
                break;
			
                case "x": uitkomst +=  (arg1 * arg2);
                break;
			
                case "/": uitkomst +=  (arg1 / arg2);
                break;
            }
        } catch(NumberFormatException e) {
            waarschuwing = "Voer twee (decimale) getallen in";
        }
    }
	
    //benodigde getters en setters
    public String getUitkomst() {
        return uitkomst;
    }

    public void setUitkomst(String uitkomst) {
        this.uitkomst = uitkomst;
    }

    public String getWaarschuwing() {
        return waarschuwing;
    }

    public void setWaarschuwing(String waarschuwing) {
        this.waarschuwing = waarschuwing;
    }

}

Servlet (control)

Maak in dezelfde package een Servlet. De servlet controleert of de gebruiker iets heeft ingevoerd en maakt - afhankelijk van de invoer - een object van het type Rekenmachine aan en geeft die als attribuut mee aan het request, dat vervolgens naar de jsp pagina wordt geforward.

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class RekenmachineServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        Rekenmachine rm = null;
        
        //maak object rekenmachine afhankelijk van wel of niet invoer gebruiker
        
        //gebruiker heeft iets ingevoerd
        if (req.getParameter("reken_functie") != null) {
            String getal1 = req.getParameter("reken_arg1");
            String getal2 = req.getParameter("reken_arg2");
            String functie = req.getParameter("reken_functie");
            rm = new Rekenmachine(getal1, getal2, functie);
            
        //gebruiker heeft nog niets ingevoerd
        } else {
            rm = new Rekenmachine();
        }
        req.setAttribute("rekenmachine", rm);
        RequestDispatcher disp = getServletContext()
                    .getRequestDispatcher("/rekenmachine.jsp");
        try {
            disp.forward(req, resp);
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }
}    

Als je de servlet hebt gemaakt, kun je er een url aan toekennen in je web.xml bestand.

<servlet>
    <servlet-name>Rekenmachine</servlet-name>
    <servlet-class>rekenmachine.RekenmachineServlet</servlet-class>
</servlet>
 <servlet-mapping>
    <servlet-name>Rekenmachine</servlet-name>
    <url-pattern>/rekenmachine</url-pattern>
</servlet-mapping>
<servlet>

jsp (view)

In je jsp pagina kun je het attribuut rekenmachine en haar attributen "uitkomst" en "foutboodschap" nu meteen aanroepen met "Expression Language" (EL). Je hoeft hiervoor geen object te maken en ook is het niet nodig om de klasse rekenmachine te importeren.

<form action="/rekenmachine" method="get">
    <table id="rekenmachine">
        <tr>
            <td colspan="2">
              <input type="text" name="reken_arg1" value="${rekenmachine.uitkomst }">
            </td>
            <td colspan="2">
              <input type="text" name="reken_arg2" value="">
            </td>
        </tr>
        <tr>
            <td>
                <input type="submit" name="reken_functie" value="+">
            </td>
            <td>
                <input type="submit" name="reken_functie" value="-">
            </td>
            <td>
                <input type="submit" name="reken_functie" value="x">
            </td>
            <td>
                <input type="submit" name="reken_functie" value="/">
            </td>
        </tr>
        <tr>
            <td colspan="4">${rekenmachine.waarschuwing }</td>
        </tr>
    </table>
</form>

Praktijkopdracht

Een luxe hotel wil een web-app waarmee een klant per kamer een beschikbaarheidskalender kan opvragen en - als de kamer beschikbaar is - een reservering maken. Maak bij de uitwerking gebruik van een servlet.

Kies een kamer