Salta al contenuto
Note per Studenti JSP e pattern Model-View-Controller

JSP e pattern Model-View-Controller

In questa pagina 7

Nella servlet di Java servlet e Apache Tomcat - ciclo di vita, richieste e sessioniUna servlet è una classe Java che un container (Apache Tomcat: connettore HTTP Coyote, contenitore Catalina, compilatore JSP Jasper) istanzia una sola volta e chiama per ogni richiesta, ciascuna in un thread del pool. Ciclo di vita: caricamento e costruzione, init() una volta, service() per ogni richiesta (HttpServlet lo smista su doGet, doPost, doPut, doDelete), destroy() allo spegnimento. La richiesta (HttpServletRequest) dà metodo, parametri (query string e corpo del form), header, path (contextPath, servletPath, pathInfo) e corpo; la risposta (HttpServletResponse) si costruisce con stato, header e corpo (PrintWriter), con sendRedirect (302 + Location) o sendError. Il mapping si dichiara con @WebServlet o in WEB-INF/web.xml (che prevale). Poiché esiste una sola istanza e molti thread, i campi di istanza sono condivisi: lo stato della richiesta sta in variabili locali, quello condiviso in oggetti thread-safe (AtomicInteger, DataSource). HTTP è senza stato, quindi si usano le sessioni: la servlet crea un HttpSession, tiene i dati sul server e manda al browser solo l'identificatore nel cookie JSESSIONID (Set-Cookie con HttpOnly, Secure, SameSite, Path secondo RFC 6265); al login si cambia l'identificatore (changeSessionId) contro la session fixation. I filtri (Filter, FilterChain) elaborano richiesta e risposta prima e dopo le servlet: codifica, autenticazione, log. Il war ha la struttura WEB-INF/classes, WEB-INF/lib, WEB-INF/web.xml, META-INF/context.xml; l'accesso ai dati usa un DataSource con pool di connessioni dichiarato in context.xml e cercato con JNDI, e un DAO con PreparedStatement e try-with-resources.Java servlet e Apache Tomcat - ciclo di vita, richieste e sessioni → l'HTML era costruito con una serie di out.println. Funziona, ma è scomodo: per cambiare un'etichetta si ricompila Java, un grafico non può modificare la pagina, e la logica (controlli, accesso ai dati) si mescola con la presentazione. Questa nota separa i due mondi: le JSP per l'HTML e il pattern MVC per organizzare l'applicazione.

JSP: Jakarta Pages

Una JSP (Jakarta Server Pages, oggi Jakarta Pages, specifica 3.1 in Tomcat 10.1) è un file .jsp che si scrive come una pagina HTML e contiene parti dinamiche. Alla prima richiesta Jasper la traduce in una servlet Java, la compila (il risultato sta nella cartella work di Tomcat) e da quel momento è una normale servlet: il ciclo di vita è lo stesso, con jspInit() e _jspService() al posto di init() e service(). Le regole sulla concorrenza valgono tali e quali: una variabile dichiarata a livello di classe nella JSP (<%! ... %>) è un campo condiviso da tutti i thread.

I costrutti, dal più vecchio al più usato oggi:

Costrutto Sintassi Uso
direttiva <%@ page contentType="text/html;charset=UTF-8" %> configura la pagina (tipo e codifica); taglib carica una libreria di tag; include inserisce un file a tempo di traduzione
scriptlet <% ... %> codice Java dentro la pagina: da evitare, è la logica dentro la vista
espressione <%= espressione %> scrive il valore di un'espressione Java: sostituita dall'EL
EL espressione tra graffe dopo il simbolo del dollaro, vedi sotto legge i dati e li scrive nella pagina
tag JSTL <c:forEach ...> cicli, condizioni, formattazione in forma di tag XML

La regola moderna è zero scriptlet: nella JSP stanno solo HTML, EL e tag, e tutto il Java sta nelle classi.

Expression Language (EL)

L'EL (Expression Language) è un linguaggio piccolo per leggere dati. Una espressione si scrive come dollaro seguito da una espressione tra graffe; negli esempi sotto compare in blocchi di codice.

jsp
<p>Numero di aule: ${aule.size()}</p>              <%-- metodo di un oggetto (EL 3) --%>
<td>${a.nome}</td>                                  <%-- proprietà "nome": chiama a.getNome() --%>
<td>${p.aulaId}</td>                                <%-- chiama p.getAulaId() --%>
<p>Giorno richiesto: ${param.giorno}</p>            <%-- parametro della richiesta: req.getParameter("giorno") --%>
<p>Ciao ${sessionScope.studente}</p>                <%-- attributo di sessione --%>
<c:if test="${empty prenotazioni and not empty errore}">...</c:if>
<option ${a.id == bozza.aulaId ? 'selected' : ''}>  <%-- operatore condizionale --%>
  • Proprietà dei JavaBean. a.nome non accede al campo: chiama getNome(). Per questo il modello è fatto di JavaBean (classe pubblica, costruttore vuoto, campi privati, getX/setX; isX per i booleani). Un record Java, che ha nome() e non getNome(), non va bene con le versioni di EL che non lo conoscono.
  • Ricerca per nome. Un nome libero come aule viene cercato negli scope (ambiti) in ordine: pageScope (la sola pagina), requestScope (la richiesta, quello che le servlet riempiono con setAttribute), sessionScope (la sessione), applicationScope (l'applicazione, cioè il ServletContext). Se non c'è, il risultato è vuoto, non un errore: l'espressione su bozza.oraInizio, con bozza assente, scrive niente.
  • Oggetti impliciti: param (parametri), header, cookie, pageContext (da cui pageContext.request.contextPath per costruire URL), più i quattro scope.
  • Operatori: ==, !=, <, >, and, or, not, empty (vero per null, stringa vuota, collezione vuota), ? :.
  • L'EL non fa escape. Se p.studente contiene <script>, il browser lo esegue. I dati dell'utente si stampano con <c:out>, che sostituisce < > & " '.

JSTL

La JSTL (Jakarta Standard Tag Library) è la libreria standard di tag; in Maven si aggiunge come dipendenza (Tomcat non la include, vedi il pom in Strumenti di sviluppo - git, Maven e DockerTre strumenti rendono un progetto web riproducibile e condivisibile. Git tiene la storia del codice: tre aree (cartella di lavoro, area di staging, repository), il commit è un'istantanea con un hash, il branch è un puntatore mobile a un commit, HEAD indica dove si è; il merge fonde due rami (fast-forward se non c'è divergenza, altrimenti un commit di merge) e può produrre conflitti, che si risolvono a mano nei marcatori di conflitto, poi add e commit; con un remoto si usano clone, fetch, pull (fetch + merge) e push, e un push rifiutato (fetch first) richiede prima un pull; in gruppo si lavora su rami di funzionalità con commit piccoli e main sempre funzionante. Maven descrive il progetto nel pom.xml (coordinate groupId:artifactId:version, packaging war, dipendenze con scope compile, provided, runtime, test), impone la struttura src/main/java, src/main/webapp, src/test, e il ciclo di vita validate, compile, test, package, verify, install, deploy: ogni fase esegue prima tutte le precedenti, mvn clean package produce target/aule.war. Docker confeziona l'applicazione: un'immagine (strati di sola lettura, costruita da un Dockerfile) è il modello, un container è un'istanza in esecuzione; per l'esempio un Dockerfile a due fasi compila il war con Maven e lo copia in Tomcat, e docker compose avvia insieme Tomcat e PostgreSQL con un volume per i dati.Strumenti di sviluppo - git, Maven e Docker →). Si carica con la direttiva taglib, e il prefisso c è per la parte di base (core):

jsp
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
Tag Funzione
<c:out value="..."/> stampa il valore dell'espressione EL con escape
<c:if test="..."> condizione semplice (non esiste l'else)
<c:choose> <c:when test> <c:otherwise> condizione a più rami
<c:forEach var="a" items="..."> ciclo su una collezione (nell'esempio l'attributo aule), a è la variabile del ciclo (varStatus per l'indice)
<c:set var="x" value="..."/> imposta una variabile in uno scope
<c:url value="/prenotazioni"/> costruisce un URL con il context path

Esistono anche fmt (formattazione di numeri e date) e fn (funzioni su stringhe, per esempio fn:escapeXml). Per il resto, JSP e JSTL non servono per l'esame più di quanto qui riportato.

Il pattern Model-View-Controller

L'MVC (Model-View-Controller) divide un'applicazione interattiva in tre ruoli, perché ciascuno possa cambiare senza toccare gli altri:

Ruolo Che cosa è Nell'applicazione delle aule
Model i dati e le regole del dominio, indipendenti da HTML e da HTTP Aula, Prenotazione (JavaBean), AulaDAO, PrenotazioneDAO, PrenotazioneException
View come i dati si mostrano; nessuna logica aule.jsp, prenotazioni.jsp
Controller riceve la richiesta, decide che cosa fare, coordina model e view AuleServlet, PrenotazioniServlet

In una web app Java con servlet (a volte detta Model 2) il flusso è:

browser ──GET /prenotazioni?giorno=2026-10-19──> PrenotazioniServlet (controller)
                                                    1. legge e valida i parametri
                                                    2. chiede i dati al model (DAO) ──> database
                                                    3. req.setAttribute("prenotazioni", lista)
                                                    4. req.getRequestDispatcher(".../prenotazioni.jsp").forward(req, resp)
                                                 prenotazioni.jsp (view)
                                                    5. legge gli attributi con EL e scrive l'HTML
browser <──────────────── 200 + HTML ───────────────────┘

Vantaggi: il grafico cambia l'HTML senza toccare Java; la stessa logica può alimentare viste diverse (HTML, JSON: Servizi REST in Java - progettazione delle risorse e implementazione con servletUn servizio REST espone risorse, non pagine: il server risponde con dati (JSON) e codici di stato, e la presentazione passa al client. Per progettare l'API si individuano i sostantivi del dominio (aula, prenotazione), si dà a collezioni ed elementi un URI (/api/aule, /api/aule/3, /api/prenotazioni, /api/prenotazioni/7: nomi al plurale, mai verbi), si usano i metodi HTTP con la semantica di RFC 9110 (GET sicuro e idempotente, POST crea e non è idempotente, PUT e DELETE idempotenti) e si filtrano le collezioni con la query (?aula=1&giorno=2026-10-19). I codici fanno parte del contratto: 200 lettura, 201 creazione con Location, 204 cancellazione, 400 richiesta malformata, 404 risorsa inesistente, 405 metodo non consentito con Allow, 409 conflitto (orario già occupato), 415 tipo di contenuto, 422 richiesta ben formata ma non elaborabile (aula inesistente), 500 guasto. Il JSON usa i nomi delle colonne (aula_id, ora_inizio, ora_fine) e gli errori hanno un corpo {"errore": "..."}. In Java una servlet mappata su /api/prenotazioni/* fa il dispatch sul path: getPathInfo() nullo o "/" indica la collezione, "/12" l'elemento; doGet, doPost e doDelete implementano i metodi, Gson converte oggetti e JSON, i metodi non previsti rispondono 405 con l'header Allow. Il servizio è senza stato: niente sessione, ogni richiesta porta tutto ciò che serve.Servizi REST in Java - progettazione delle risorse e implementazione con servlet →); il model si prova senza server; i ruoli si dividono fra persone. Regole: niente SQL né Java nelle JSP, niente HTML nelle servlet, niente HttpServletRequest nel model (altrimenti non è riusabile). Le JSP delle viste si mettono in WEB-INF/jsp/: il browser non può chiederle direttamente, passano sempre dal controller.

Il controller

Il controller per le aule è breve: legge, passa i dati alla vista, forward.

java
package it.unipd.aule.mvc;

import it.unipd.aule.dao.AulaDAO;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.sql.SQLException;

/** Controller: legge i dati con il DAO (model) e li passa alla JSP (view). Non scrive HTML. */
@WebServlet("/aule")
public class AuleServlet extends HttpServlet {
    private AulaDAO aulaDAO;

    @Override
    public void init() {
        aulaDAO = (AulaDAO) getServletContext().getAttribute("aulaDAO");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            req.setAttribute("aule", aulaDAO.tutte());                                   // model -> view
            req.getRequestDispatcher("/WEB-INF/jsp/aule.jsp").forward(req, resp);        // forward interno al server
        } catch (SQLException e) {
            throw new ServletException(e);
        }
    }
}
jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <title>Aule</title>
</head>
<body>
  <h1>Aule disponibili</h1>
  <c:choose>
    <c:when test="${empty aule}">
      <p>Nessuna aula in archivio.</p>
    </c:when>
    <c:otherwise>
      <table>
        <thead><tr><th>Nome</th><th>Posti</th></tr></thead>
        <tbody>
          <c:forEach var="a" items="${aule}">
            <tr>
              <td><c:out value="${a.nome}"/></td>
              <td>${a.posti}</td>
            </tr>
          </c:forEach>
        </tbody>
      </table>
      <p>${aule.size()} aule in totale.
         <a href="${pageContext.request.contextPath}/prenotazioni">Vai alle prenotazioni</a></p>
    </c:otherwise>
  </c:choose>
</body>
</html>
  • L'attributo aule impostato nella servlet è visto dalla JSP con l'EL sul nome aule: stessa richiesta, attributi condivisi.
  • c:out sul nome dell'aula stampa con escape; l'EL su a.posti va bene senza c:out perché è un numero.
  • empty aule gestisce la lista vuota con un messaggio invece di una tabella senza righe.

Il controller delle prenotazioni gestisce anche l'invio del form, quindi mostra tutti gli esiti:

java
package it.unipd.aule.mvc;

import it.unipd.aule.dao.AulaDAO;
import it.unipd.aule.dao.PrenotazioneDAO;
import it.unipd.aule.dao.PrenotazioneException;
import it.unipd.aule.model.Prenotazione;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;

/** Controller MVC delle prenotazioni: GET mostra la pagina, POST registra e poi fa il redirect. */
@WebServlet("/prenotazioni")
public class PrenotazioniServlet extends HttpServlet {
    private static final String VISTA = "/WEB-INF/jsp/prenotazioni.jsp";

    private AulaDAO aulaDAO;
    private PrenotazioneDAO prenotazioneDAO;

    @Override
    public void init() {
        aulaDAO = (AulaDAO) getServletContext().getAttribute("aulaDAO");
        prenotazioneDAO = (PrenotazioneDAO) getServletContext().getAttribute("prenotazioneDAO");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String giorno = req.getParameter("giorno");
        if (giorno == null || giorno.isBlank()) {
            giorno = LocalDate.now().toString();
        }
        try {
            LocalDate.parse(giorno);
        } catch (DateTimeParseException e) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "giorno non valido");
            return;
        }
        HttpSession sessione = req.getSession();
        Object messaggio = sessione.getAttribute("messaggio");       // "flash": scritto prima del redirect
        if (messaggio != null) {
            req.setAttribute("messaggio", messaggio);
            sessione.removeAttribute("messaggio");                   // si mostra una volta sola
        }
        mostra(req, resp, giorno);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Prenotazione p = new Prenotazione();
        p.setGiorno(req.getParameter("giorno"));
        p.setOraInizio(req.getParameter("ora_inizio"));
        p.setOraFine(req.getParameter("ora_fine"));
        p.setStudente((String) req.getSession().getAttribute("studente"));   // messo dal login, controllato dal filtro
        try {
            p.setAulaId(Integer.parseInt(req.getParameter("aula_id")));
            p.valida();
            prenotazioneDAO.crea(p);
        } catch (IllegalArgumentException | PrenotazioneException e) {      // NumberFormatException è un IllegalArgumentException
            // errore dell'utente: stessa pagina, con il form già compilato e il messaggio; nessun redirect
            req.setAttribute("errore", e instanceof NumberFormatException ? "aula non valida" : e.getMessage());
            req.setAttribute("bozza", p);
            resp.setStatus(e instanceof PrenotazioneException ? HttpServletResponse.SC_CONFLICT : HttpServletResponse.SC_BAD_REQUEST);
            mostra(req, resp, p.getGiorno() != null ? p.getGiorno() : LocalDate.now().toString());
            return;
        } catch (SQLException e) {
            throw new ServletException(e);
        }
        req.getSession().setAttribute("messaggio", "Prenotazione registrata.");
        resp.sendRedirect(req.getContextPath() + "/prenotazioni?giorno=" + p.getGiorno());   // Post/Redirect/Get
    }

    private void mostra(HttpServletRequest req, HttpServletResponse resp, String giorno)
            throws ServletException, IOException {
        try {
            req.setAttribute("giorno", giorno);
            req.setAttribute("aule", aulaDAO.tutte());
            req.setAttribute("prenotazioni", prenotazioneDAO.lista(null, giorno));
        } catch (SQLException e) {
            throw new ServletException(e);
        }
        req.getRequestDispatcher(VISTA).forward(req, resp);
    }
}
jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <title>Prenotazioni del <c:out value="${giorno}"/></title>
</head>
<body>
  <h1>Prenotazioni del <c:out value="${giorno}"/></h1>

  <c:if test="${not empty messaggio}"><p class="ok"><c:out value="${messaggio}"/></p></c:if>
  <c:if test="${not empty errore}"><p class="errore"><c:out value="${errore}"/></p></c:if>

  <table>
    <thead><tr><th>Aula</th><th>Dalle</th><th>Alle</th><th>Studente</th></tr></thead>
    <tbody>
      <c:forEach var="p" items="${prenotazioni}">
        <tr>
          <td>${p.aulaId}</td><td>${p.oraInizio}</td><td>${p.oraFine}</td>
          <td><c:out value="${p.studente}"/></td>
        </tr>
      </c:forEach>
      <c:if test="${empty prenotazioni}"><tr><td colspan="4">Nessuna prenotazione.</td></tr></c:if>
    </tbody>
  </table>

  <form method="post" action="${pageContext.request.contextPath}/prenotazioni">
    <select name="aula_id">
      <c:forEach var="a" items="${aule}">
        <option value="${a.id}" ${a.id == bozza.aulaId ? 'selected' : ''}><c:out value="${a.nome}"/></option>
      </c:forEach>
    </select>
    <input type="date" name="giorno" value="<c:out value='${giorno}'/>" required>
    <input type="time" name="ora_inizio" value="<c:out value='${bozza.oraInizio}'/>" required>
    <input type="time" name="ora_fine" value="<c:out value='${bozza.oraFine}'/>" required>
    <button>Prenota</button>
  </form>
</body>
</html>

Questa versione sostituisce la servlet con l'HTML scritto a mano della nota precedente (sarebbero due servlet sullo stesso URL): il lavoro del model (crea, la validazione, la sovrapposizione) è identico, cambia solo come si mostra l'esito. I tre casi di doPost:

  1. successo: salva in sessione il messaggio, risponde con un redirect alla GET;
  2. errore dell'utente (formato, aula, sovrapposizione): non è un redirect; si rimette il form già compilato (bozza) e il messaggio (errore) negli attributi della richiesta, si imposta lo stato 400 o 409 e si fa il forward alla stessa pagina;
  3. guasto (SQLException): ServletException, 500.

Forward e redirect

Per passare il controllo ad un'altra risorsa si può fare in due modi molto diversi.

Forward Redirect
chi lo fa il server, internamente il browser, su ordine del server
codice req.getRequestDispatcher(path).forward(req, resp) resp.sendRedirect(url)
messaggi HTTP una richiesta, una risposta risposta 302 Found con Location, poi una nuova richiesta GET
URL nella barra non cambia cambia
attributi della richiesta conservati persi (nuova richiesta)
destinazione solo risorse della stessa applicazione, anche sotto WEB-INF qualunque URL, anche di un altro sito
uso tipico controller →\to vista dopo una POST che ha modificato dati

Dopo un forward la risposta è quella della risorsa di destinazione: il controller non deve scrivere nulla dopo.

Post/Redirect/Get

Se la risposta a una POST è direttamente la pagina HTML, l'URL nel browser resta quello della POST. Ricaricare la pagina (F5) rinvia la stessa POST: il browser chiede conferma, e se l'utente accetta si prenota due volte. Lo stesso accade con il pulsante "indietro" in certe sequenze.

Il pattern Post/Redirect/Get (PRG) lo evita: la POST non risponde con una pagina ma con un redirect a una GET.

http
POST /prenotazioni HTTP/1.1
Content-Type: application/x-www-form-urlencoded

aula_id=1&giorno=2026-10-19&ora_inizio=11%3A00&ora_fine=12%3A00
http
HTTP/1.1 302
Location: /prenotazioni?giorno=2026-10-19
http
GET /prenotazioni?giorno=2026-10-19 HTTP/1.1

La barra mostra l'URL della GET, e F5 ripete la GET, innocua (sicura e idempotente). Il prezzo è che la richiesta della GET non sa nulla della POST precedente: il messaggio "Prenotazione registrata" deve attraversare il redirect. Si usa un messaggio flash nella sessione: la POST lo scrive (session.setAttribute("messaggio", ...)), la GET lo legge, lo mette negli attributi della richiesta e lo toglie dalla sessione, così si vede una sola volta (si vede nel codice del controller sopra).

PRG si applica solo ai successi. In caso di errore non si fa il redirect: si mostra di nuovo il form con i valori inseriti e il messaggio (e lo stato 4xx), altrimenti l'utente perderebbe ciò che ha scritto. Un ricaricamento in quel caso è innocuo, perché la POST fallirebbe di nuovo.

Errori tipici

  • Scrivere <%= %> e scriptlet con SQL e cicli nella JSP: la vista diventa un controller.
  • Stampare con l'EL, senza <c:out>, dati dell'utente: XSS.
  • Mettere le viste fuori da WEB-INF (raggiungibili senza controller, con attributi mancanti).
  • Scrivere HTML nel controller, o importare HttpServletRequest nei DAO.
  • Aspettarsi gli attributi della richiesta dopo un redirect: sono perduti (usare la sessione, il parametro dell'URL o il flash).
  • Rispondere alla POST riuscita con una pagina invece che con un redirect.
  • Fare forward dopo aver già scritto sul writer o chiamato sendRedirect: IllegalStateException (risposta già committed).
  • Dimenticare la dipendenza JSTL nel pom: errore di taglib non trovato.

Domande d'esame

1. Disegnare il flusso di una GET /aule in un'applicazione MVC con servlet e JSP e dire chi fa che cosa. Traccia. Il browser invia la richiesta; la servlet (controller) la riceve, chiama AulaDAO.tutte() (model), mette la lista in un attributo della richiesta e fa il forward a /WEB-INF/jsp/aule.jsp; la JSP (view) legge l'attributo con EL/JSTL e produce l'HTML. Il DAO non conosce HTTP, la JSP non conosce l'SQL.

2. Differenza tra forward e redirect. Quale si usa dopo aver salvato una prenotazione e perché? Traccia. Il forward è interno al server: una sola richiesta, URL e attributi invariati. Il redirect è una risposta 302 che fa fare al browser una nuova GET. Dopo una POST riuscita si fa redirect (PRG): F5 ripete la GET e non rinvia il form; il messaggio passa nella sessione e si legge una volta sola.

3. Perché l'EL su p.studente non va usata così com'è per un dato inserito dall'utente? Come si corregge? Traccia. L'EL non fa escape, quindi un nome come <script>...</script> verrebbe eseguito dal browser degli altri utenti (XSS). Si usa <c:out> con quell'espressione come valore, che sostituisce <, >, & e gli apici con le entità HTML.

Versione ripasso

JSP (Jakarta Pages): pagina HTML con parti dinamiche, tradotta da Jasper in una servlet alla prima richiesta (stessa concorrenza: <%! %> crea campi condivisi). Costrutti: direttive <%@ page %>, <%@ taglib %>, <%@ include %>; scriptlet <% %> e <%= %> da evitare; EL e JSTL. Regola: zero scriptlet.

EL: nelle pagine si scrivono con dollaro e graffe.

jsp
<td>${a.nome}</td>                       <%-- chiama a.getNome() --%>
<p>Ciao ${sessionScope.studente}</p>
<p>${param.giorno}</p>                   <%-- req.getParameter("giorno") --%>
<c:if test="${empty prenotazioni and not empty errore}">...</c:if>
<option ${a.id == bozza.aulaId ? 'selected' : ''}>
  • Le proprietà sono i getter dei JavaBean (classe pubblica, costruttore vuoto, campi privati, getX/setX).
  • Un nome libero si cerca in pageScope, requestScope, sessionScope, applicationScope; se manca è vuoto, non è un errore.
  • Oggetti impliciti: param, header, cookie, pageContext (pageContext.request.contextPath). Operatori: ==, and, or, not, empty, ? :.
  • L'EL non fa escape: dati dell'utente con <c:out> (altrimenti XSS).

JSTL core: <%@ taglib prefix="c" uri="jakarta.tags.core" %> e dipendenza nel pom.

Tag Funzione
c:out stampa con escape
c:if condizione (niente else)
c:choose, c:when, c:otherwise più rami
c:forEach var items ciclo
c:set, c:url variabile, URL con context path

MVC. Model = JavaBean e DAO (dati e regole, senza HTTP); View = JSP (mostra, senza logica); Controller = servlet (legge, valida, chiama il model, imposta gli attributi, fa il forward).

GET /prenotazioni ──> servlet: legge i parametri ──> DAO ──> database
                      req.setAttribute("prenotazioni", lista)
                      getRequestDispatcher("/WEB-INF/jsp/prenotazioni.jsp").forward(req, resp)
                      JSP: legge con EL, scrive l'HTML ──> 200 + HTML
  • Niente SQL né Java nelle JSP, niente HTML nelle servlet, niente HttpServletRequest nel model. Le viste in WEB-INF/jsp/.
  • Controller delle aule: req.setAttribute("aule", aulaDAO.tutte()); poi forward a /WEB-INF/jsp/aule.jsp; la JSP usa c:choose con empty aule e c:forEach con var="a" sull'attributo aule.
jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<h1>Aule disponibili</h1>
<c:choose>
  <c:when test="${empty aule}"><p>Nessuna aula in archivio.</p></c:when>
  <c:otherwise>
    <table>
      <c:forEach var="a" items="${aule}">
        <tr><td><c:out value="${a.nome}"/></td><td>${a.posti}</td></tr>
      </c:forEach>
    </table>
  </c:otherwise>
</c:choose>
  • doPost delle prenotazioni: successo →\to messaggio in sessione e redirect; errore dell'utente →\to bozza e errore negli attributi, stato 400 o 409, forward alla stessa pagina; guasto →\to ServletException (500).
java
req.getSession().setAttribute("messaggio", "Prenotazione registrata.");
resp.sendRedirect(req.getContextPath() + "/prenotazioni?giorno=" + p.getGiorno());   // Post/Redirect/Get
Forward Redirect
chi server, internamente browser, su ordine del server
codice getRequestDispatcher(path).forward(req, resp) sendRedirect(url)
HTTP una richiesta 302 + Location, poi nuova GET
URL e attributi invariati cambia, attributi persi
destinazione solo la stessa applicazione (anche WEB-INF) qualunque URL

Post/Redirect/Get: la POST riuscita risponde 302 con Location verso una GET; F5 ripete la GET (innocua) e non prenota due volte.

http
POST /prenotazioni HTTP/1.1
aula_id=1&giorno=2026-10-19&ora_inizio=11%3A00&ora_fine=12%3A00

HTTP/1.1 302
Location: /prenotazioni?giorno=2026-10-19
  • Messaggio flash: la POST lo scrive in sessione, la GET lo mette negli attributi della richiesta e lo rimuove, così si vede una volta sola.
java
// doGet del controller delle prenotazioni
HttpSession sessione = req.getSession();
Object messaggio = sessione.getAttribute("messaggio");       // scritto dalla POST prima del redirect
if (messaggio != null) {
    req.setAttribute("messaggio", messaggio);
    sessione.removeAttribute("messaggio");                   // si mostra una volta sola
}
req.setAttribute("giorno", giorno);
req.setAttribute("aule", aulaDAO.tutte());
req.setAttribute("prenotazioni", prenotazioneDAO.lista(null, giorno));
req.getRequestDispatcher("/WEB-INF/jsp/prenotazioni.jsp").forward(req, resp);
jsp
<c:if test="${not empty messaggio}"><p class="ok"><c:out value="${messaggio}"/></p></c:if>
<c:if test="${not empty errore}"><p class="errore"><c:out value="${errore}"/></p></c:if>
<c:forEach var="p" items="${prenotazioni}">
  <tr><td>${p.aulaId}</td><td>${p.oraInizio}</td><td>${p.oraFine}</td>
      <td><c:out value="${p.studente}"/></td></tr>
</c:forEach>
<input type="time" name="ora_inizio" value="<c:out value='${bozza.oraInizio}'/>" required>
  • Solo per i successi. Per gli errori niente redirect: stesso form compilato, messaggio, stato 4xx.

Errori tipici

  • Scriptlet con SQL nella JSP; EL senza c:out su dati dell'utente (XSS).
  • Viste fuori da WEB-INF; HTML nel controller; HttpServletRequest nei DAO.
  • Aspettarsi gli attributi della richiesta dopo un redirect; rispondere alla POST riuscita con una pagina.
  • forward dopo aver già scritto la risposta: IllegalStateException. Mancanza della dipendenza JSTL nel pom.

Domande d'esame (tracce)

  • Flusso MVC: richiesta →\to servlet →\to DAO →\to attributo →\to forward →\to JSP →\to HTML.
  • Forward interno e invisibile, redirect 302 con nuova GET; dopo una POST riuscita si fa redirect (PRG).
  • L'EL su p.studente non fa escape: c:out per evitare l'XSS.

Collegamenti: Java servlet e Apache Tomcat - ciclo di vita, richieste e sessioniUna servlet è una classe Java che un container (Apache Tomcat: connettore HTTP Coyote, contenitore Catalina, compilatore JSP Jasper) istanzia una sola volta e chiama per ogni richiesta, ciascuna in un thread del pool. Ciclo di vita: caricamento e costruzione, init() una volta, service() per ogni richiesta (HttpServlet lo smista su doGet, doPost, doPut, doDelete), destroy() allo spegnimento. La richiesta (HttpServletRequest) dà metodo, parametri (query string e corpo del form), header, path (contextPath, servletPath, pathInfo) e corpo; la risposta (HttpServletResponse) si costruisce con stato, header e corpo (PrintWriter), con sendRedirect (302 + Location) o sendError. Il mapping si dichiara con @WebServlet o in WEB-INF/web.xml (che prevale). Poiché esiste una sola istanza e molti thread, i campi di istanza sono condivisi: lo stato della richiesta sta in variabili locali, quello condiviso in oggetti thread-safe (AtomicInteger, DataSource). HTTP è senza stato, quindi si usano le sessioni: la servlet crea un HttpSession, tiene i dati sul server e manda al browser solo l'identificatore nel cookie JSESSIONID (Set-Cookie con HttpOnly, Secure, SameSite, Path secondo RFC 6265); al login si cambia l'identificatore (changeSessionId) contro la session fixation. I filtri (Filter, FilterChain) elaborano richiesta e risposta prima e dopo le servlet: codifica, autenticazione, log. Il war ha la struttura WEB-INF/classes, WEB-INF/lib, WEB-INF/web.xml, META-INF/context.xml; l'accesso ai dati usa un DataSource con pool di connessioni dichiarato in context.xml e cercato con JNDI, e un DAO con PreparedStatement e try-with-resources.Java servlet e Apache Tomcat - ciclo di vita, richieste e sessioni →, Servizi REST in Java - progettazione delle risorse e implementazione con servletUn servizio REST espone risorse, non pagine: il server risponde con dati (JSON) e codici di stato, e la presentazione passa al client. Per progettare l'API si individuano i sostantivi del dominio (aula, prenotazione), si dà a collezioni ed elementi un URI (/api/aule, /api/aule/3, /api/prenotazioni, /api/prenotazioni/7: nomi al plurale, mai verbi), si usano i metodi HTTP con la semantica di RFC 9110 (GET sicuro e idempotente, POST crea e non è idempotente, PUT e DELETE idempotenti) e si filtrano le collezioni con la query (?aula=1&giorno=2026-10-19). I codici fanno parte del contratto: 200 lettura, 201 creazione con Location, 204 cancellazione, 400 richiesta malformata, 404 risorsa inesistente, 405 metodo non consentito con Allow, 409 conflitto (orario già occupato), 415 tipo di contenuto, 422 richiesta ben formata ma non elaborabile (aula inesistente), 500 guasto. Il JSON usa i nomi delle colonne (aula_id, ora_inizio, ora_fine) e gli errori hanno un corpo {"errore": "..."}. In Java una servlet mappata su /api/prenotazioni/* fa il dispatch sul path: getPathInfo() nullo o "/" indica la collezione, "/12" l'elemento; doGet, doPost e doDelete implementano i metodi, Gson converte oggetti e JSON, i metodi non previsti rispondono 405 con l'header Allow. Il servizio è senza stato: niente sessione, ogni richiesta porta tutto ciò che serve.Servizi REST in Java - progettazione delle risorse e implementazione con servlet →, Esercizio - Elenco delle aule con MVC e JSP (applicazione web).

Esercizi su questo argomento

Teoria collegata