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.
<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.nomenon accede al campo: chiamagetNome(). Per questo il modello è fatto di JavaBean (classe pubblica, costruttore vuoto, campi privati,getX/setX;isXper i booleani). UnrecordJava, che hanome()e nongetNome(), non va bene con le versioni di EL che non lo conoscono. - Ricerca per nome. Un nome libero come
auleviene cercato negli scope (ambiti) in ordine:pageScope(la sola pagina),requestScope(la richiesta, quello che le servlet riempiono consetAttribute),sessionScope(la sessione),applicationScope(l'applicazione, cioè ilServletContext). Se non c'è, il risultato è vuoto, non un errore: l'espressione subozza.oraInizio, conbozzaassente, scrive niente. - Oggetti impliciti:
param(parametri),header,cookie,pageContext(da cuipageContext.request.contextPathper costruire URL), più i quattro scope. - Operatori:
==,!=,<,>,and,or,not,empty(vero pernull, stringa vuota, collezione vuota),? :. - L'EL non fa escape. Se
p.studentecontiene<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):
<%@ 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.
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);
}
}
}<%@ 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
auleimpostato nella servlet è visto dalla JSP con l'EL sul nomeaule: stessa richiesta, attributi condivisi. c:outsul nome dell'aula stampa con escape; l'EL sua.postiva bene senzac:outperché è un numero.empty aulegestisce 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:
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);
}
}<%@ 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:
- successo: salva in sessione il messaggio, risponde con un redirect alla
GET; - 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 stato400o409e si fa il forward alla stessa pagina; - 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 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.
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%3A00HTTP/1.1 302
Location: /prenotazioni?giorno=2026-10-19GET /prenotazioni?giorno=2026-10-19 HTTP/1.1La 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
HttpServletRequestnei DAO. - Aspettarsi gli attributi della richiesta dopo un redirect: sono perduti (usare la sessione, il parametro dell'URL o il flash).
- Rispondere alla
POSTriuscita con una pagina invece che con un redirect. - Fare
forwarddopo aver già scritto sulwritero chiamatosendRedirect:IllegalStateException(risposta già committed). - Dimenticare la dipendenza JSTL nel pom: errore di
taglibnon 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.
<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
HttpServletRequestnel model. Le viste inWEB-INF/jsp/. - Controller delle aule:
req.setAttribute("aule", aulaDAO.tutte());poiforwarda/WEB-INF/jsp/aule.jsp; la JSP usac:chooseconempty auleec:forEachconvar="a"sull'attributoaule.
<%@ 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>doPostdelle prenotazioni: successo messaggio in sessione e redirect; errore dell'utentebozzaeerrorenegli attributi, stato400o409, forward alla stessa pagina; guastoServletException(500).
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.
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
POSTlo scrive in sessione, laGETlo mette negli attributi della richiesta e lo rimuove, così si vede una volta sola.
// 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);<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:outsu dati dell'utente (XSS). - Viste fuori da
WEB-INF; HTML nel controller;HttpServletRequestnei DAO. - Aspettarsi gli attributi della richiesta dopo un redirect; rispondere alla
POSTriuscita con una pagina. forwarddopo aver già scritto la risposta:IllegalStateException. Mancanza della dipendenza JSTL nel pom.
Domande d'esame (tracce)
- Flusso MVC: richiesta servlet DAO attributo forward JSP HTML.
- Forward interno e invisibile, redirect
302con nuovaGET; dopo unaPOSTriuscita si fa redirect (PRG). - L'EL su
p.studentenon fa escape:c:outper 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).