Premier servlet, container Foy, transport Chappe : moins de cinquante lignes de Java, pas de web.xml obligatoire, pas de scan classpath runtime.
Pré-requis
-
Java 25 (Temurin) et Maven 3.9.16 —
.sdkmanrcest versionné à la racine du repo, lancersdk envavant tout. -
Familiarité avec l’API Jakarta Servlet 6.1.
-
Pour la version CDI : un
BeanManagerVauban (voir Vauban — démarrage rapide).
Coordonnées Maven
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>io.vidocq.foy</groupId>
<artifactId>foy-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.vidocq.foy</groupId>
<artifactId>foy-chappe</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
<!-- CDI optionnel via Vauban -->
<dependency>
<groupId>io.vidocq.foy</groupId>
<artifactId>foy-cdi-vauban</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Hello world : une Servlet annotée
package io.example;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello/*")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
var name = req.getPathInfo() == null ? "world" : req.getPathInfo().substring(1);
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().write("Hello, " + name + "!");
}
}
L’annotation @WebServlet est résolue à la compilation par un APT. Aucun scan classpath au démarrage.
Démarrer le container
L’API publique côté Chappe est FoyChappeBoot. Elle prend un BeanManager (CDI) et expose un Handler Chappe à monter sur un ChappeMountPoint.
import io.vidocq.foy.chappe.FoyChappeBoot;
import jakarta.enterprise.inject.spi.CDI;
void main() throws Exception {
var mounted = FoyChappeBoot.builder()
.beanManager(CDI.current().getBeanManager())
.contextPath("/app")
.sessionTimeoutSeconds(1800)
.build()
.orElseThrow(() -> new IllegalStateException("Aucun @WebServlet découvert"));
chappeMountPoint.mount(listener, mounted.mountPrefix(), mounted.handler());
mounted.fireContextInitialized();
}
build() retourne un Optional<Mounted> vide quand l’application ne déclare aucun bean Servlet/Filter/Listener — l’appelant choisit s’il s’agit d’une erreur ou d’un cas dégradé acceptable.
Annotations ou web.xml ?
Foy supporte les deux et applique la sémantique metadata-complete standard.
-
@WebServlet,@WebFilter,@WebListener,@MultipartConfig: découverts à la compilation par APT. -
WEB-INF/web.xmletMETA-INF/web-fragment.xml: lus au démarrage parWebXmlParseret fusionnés viaWebAppDiscovery. -
Si
metadata-complete=true, les annotations sont ignorées (comportement Servlet 6.1 standard).
Intégration avec Chappe
Le pont est ChappeServletBridge, un Handler Chappe qui :
-
expose la
RequestChappe en tant queHttpServletRequestImpl; -
expose la
ResponseChappe en tant queHttpServletResponseImpl; -
invoque la chaîne
Filter→Servlet; -
retourne la réponse via
ServletOutputStreamImpladossé au streaming Chappe — pas de copie intermédiaire.
sdk env
./mvnw -ntp install -DskipTests
./mvnw test
|
TCK Jakarta Servlet 6.1 officiel : |
Et après ?
-
Cas d’usage — filtres, listeners, async, multipart, sessions, dispatchers, virtual hosts, intégration CDI.
-
Concepts — le vocabulaire Servlet, ce qui change avec la 6.1.
-
Fonctionnement interne — séquence d’une requête, modèle de threads, lifecycle container.