Aller au contenu

Spring et Spring Boot

Spring est le framework Java le plus utilisé pour écrire des services web, et Spring Boot est la façon moderne de s'en servir : cette partie t'apprend les deux en construisant une vraie API REST.

Dans ce chapitre

  • Ce qu'est Spring : un conteneur qui crée et relie tes objets à ta place
  • Ce qu'ajoute Spring Boot : starters, auto-configuration, serveur embarqué, conventions
  • Ce qui est explicite en Rust ou en Python, et implicite chez Spring
  • L'architecture en couches controller → service → repository
  • Le fil rouge : une API de gestion de tâches (« todos »), chapitre après chapitre

Spring Framework : un conteneur d'objets

En Rust ou en Python, c'est ton main qui assemble l'application : il ouvre le pool de connexions, le passe au service, qui est passé au routeur, puis il démarre le serveur. En Spring, tu déclares des classes avec des annotations, et c'est le framework qui les instancie et les relie entre elles.

C'est l'inversion de contrôle (IoC, Inversion of Control) : tu n'appelles plus le framework pour construire l'appli, c'est lui qui appelle ton code. Le cœur de Spring est donc un conteneur IoC, qu'on appelle aussi le contexte d'application (application context) :

  • il trouve tes classes annotées (@Service, @RestController...) en parcourant tes paquets : le scan des composants ;
  • il crée une instance de chacune : ces objets gérés par Spring s'appellent des beans ;
  • il passe à chaque bean les autres beans dont il a besoin, en général via son constructeur : c'est l'injection de dépendances ;
  • il gère leur cycle de vie : création au démarrage, fermeture propre à l'arrêt (comme un Drop géré pour toi).
TodoService.java (extrait)
@Service // (1)!
public class TodoService {

    private final TodoRepository repository;

    public TodoService(TodoRepository repository) { // (2)!
        this.repository = repository;
    }
}
  1. « Ceci est un bean » : Spring le trouvera au démarrage et en créera une instance unique.
  2. Spring voit ce constructeur, cherche un bean de type TodoRepository et le passe en argument. Tu n'écris nulle part new TodoService(...).

Sous le capot

Pas de génération de code à la compilation comme avec les macros Rust : Spring lit les annotations à l'exécution, par réflexion (voir Annotations et réflexion). Pour ajouter un comportement autour de tes méthodes (transactions, sécurité, cache), il place devant certains beans des proxies : des objets générés à la volée qui interceptent les appels avant de les transmettre au vrai objet. C'est ce qui fait fonctionner @Transactional.

Autour de ce conteneur s'est construit un écosystème de modules qui s'y branchent tous de la même façon :

Module Rôle
Spring MVC applications web et API REST (modèle des servlets)
Spring Data JPA accès aux bases SQL, repositories générés
Spring Security authentification et autorisations
Spring WebFlux variante web réactive (non bloquante)
Spring Batch, Spring Cloud... traitements par lots, microservices...

Ce cours utilise Spring Framework 7, avec Spring MVC et Spring Data JPA.

Spring Boot : Spring prêt à l'emploi

Spring seul demande beaucoup de configuration : quel serveur web, quel pool de connexions, quel convertisseur JSON, et comment les relier. Spring Boot fait ces choix à ta place, avec des valeurs par défaut raisonnables que tu peux toujours changer.

Les starters
Des dépendances Maven « tout-en-un ». spring-boot-starter-webmvc apporte d'un coup Spring MVC, le serveur Tomcat et Jackson (la bibliothèque JSON), dans des versions testées ensemble. Tu choisis des fonctionnalités, pas des bibliothèques une par une.
L'auto-configuration
Au démarrage, Spring Boot regarde ce qui est présent (classes dans le classpath, propriétés, beans déjà déclarés) et crée les beans qui manquent. Le pilote H2 est là et aucune base n'est configurée ? Il crée une base en mémoire et un pool de connexions. Jackson est là ? Il configure la conversion JSON. Tu as déclaré ton propre bean ? Il s'efface.
Le serveur embarqué
Pas de serveur d'applications à installer : Tomcat est une simple bibliothèque, démarrée par ton main. Toute l'appli tient dans un jar exécutable que tu lances avec java -jar, un peu comme le binaire produit par cargo build.
Les conventions
Configuration dans src/main/resources/application.properties, port 8080, scan des composants à partir du paquet de la classe principale, scripts SQL dans db/migration... Tu ne configures que ce qui s'écarte de la convention.
Les outils de production
Actuator expose /actuator/health (l'appli et sa base vont-elles bien ?) et d'autres endpoints de supervision ; toute propriété peut venir d'une variable d'environnement, ce qui colle parfaitement à Docker.

Spring Boot ne remplace pas Spring

Boot est une couche au-dessus de Spring Framework. Les annotations que tu écris (@RestController, @Service, @Transactional) viennent de Spring Framework ; Boot se charge de tout configurer et de tout démarrer. Pour chercher de la doc : « Spring MVC » pour les contrôleurs, « Spring Data JPA » pour les repositories, « Spring Boot » pour la configuration et le démarrage.

Ce cours utilise Spring Boot 4.1. Si tu lis des tutoriels en ligne, beaucoup visent Spring Boot 2 ou 3 : l'essentiel est identique, mais certains noms ont changé (le starter web s'appelait spring-boot-starter-web, les annotations de test ont changé de paquet).

Explicite chez toi, implicite chez Spring

Côté Rust, Axum et Actix sont des bibliothèques que tu assembles toi-même ; Flask joue le même rôle en Python. FastAPI en fait un peu plus (injection avec Depends, validation avec Pydantic). Django est le plus proche de Spring Boot : « piles incluses », conventions, ORM et migrations intégrés. Spring Boot va plus loin dans l'implicite : presque tout est découvert et câblé automatiquement.

Aspect Rust (Axum) Python (FastAPI) Spring Boot
Routes Router::new().route(...) dans main décorateur @app.get(...) @GetMapping, classe trouvée par scan
Serveur axum::serve(listener, app) lancé par uvicorn Tomcat démarré par SpringApplication.run
Dépendances State<AppState> passé au routeur Depends(get_db) à chaque route injection par constructeur, automatique
JSON #[derive(Serialize)] + Json<T> modèles Pydantic Jackson, automatique (records)
Validation crate validator, appel explicite Pydantic, automatique @Valid + @NotBlank, @Size...
Base de données sqlx : pool et SQL écrits à la main SQLAlchemy : session explicite interface JpaRepository, code généré
Transactions pool.begin() puis tx.commit() session.commit() @Transactional (proxy)
Configuration lecture explicite (crate config) pydantic-settings application.properties + variables d'env

L'avantage : très peu de code d'assemblage, et des choix cohérents d'un projet Spring à l'autre. L'inconvénient : quand quelque chose ne marche pas, il n'y a pas d'appel à suivre depuis main.

Piège : la magie se débogue autrement

Un bean introuvable, une route ignorée, une base inattendue ? Ne cherche pas dans main. Les bons réflexes :

  • lire les logs de démarrage (port, base utilisée, nombre de repositories trouvés...) ;
  • relancer avec --debug pour obtenir le rapport des conditions : chaque auto-configuration, appliquée ou non, avec la raison ;
  • vérifier que tes classes sont dans le paquet de la classe principale ou en dessous, sinon le scan ne les voit pas.

L'architecture en couches

Une application Spring classique se découpe en trois couches, chacune avec un rôle précis. Voici celles de l'API que tu vas construire :

flowchart LR
    client([Client HTTP])
    subgraph app [todo-api]
        controller["TodoController<br/>@RestController"]
        service["TodoService<br/>@Service"]
        repository["TodoRepository<br/>JpaRepository"]
        advice["ApiExceptionHandler<br/>@RestControllerAdvice"]
    end
    db[(H2 ou PostgreSQL)]
    client -->|requête JSON| controller
    controller -->|appel Java| service
    service --> repository
    repository -->|SQL| db
    controller -. exception .-> advice
    advice -. Problem Details .-> client
Couche Classe Rôle Elle connaît
Web TodoController traduit HTTP ↔ appels Java, valide les entrées le service, les DTO
Métier TodoService règles métier, transactions le repository, l'entité
Persistance TodoRepository lit et écrit en base l'entité
Erreurs ApiExceptionHandler exceptions → réponses d'erreur JSON les exceptions

Chaque couche ne parle qu'à celle du dessous. Deux types d'objets circulent :

  • l'entité Todo représente une ligne de la table todo ; elle ne sort jamais du service ;
  • les DTO (Data Transfer Objects), écrits en record, décrivent ce qui entre et sort en JSON : CreateTodoRequest, UpdateTodoRequest, TodoResponse. Le format de l'API reste ainsi indépendant du schéma de la base.

Le code est rangé par fonctionnalité et non par couche : tout ce qui concerne les tâches vit dans le paquet xyz.dunkan.todo.todo, comme un module Rust qui regroupe un domaine.

Pourquoi s'imposer ces couches ?

Pour pouvoir tester chaque morceau isolément. Au chapitre Tests, le service sera testé avec un faux repository (sans base), et le contrôleur avec un faux service (sans logique métier). Le découpage sert aussi les transactions : elles s'ouvrent et se ferment au niveau du service, pas dans la couche web.

Le fil rouge : une API de tâches

Toute cette partie construit une seule application, todo-api, une API REST qui gère une liste de tâches. Chaque chapitre lui ajoute une brique.

Méthode Chemin Effet Réponses
GET /api/todos liste les tâches (?done=true ou false pour filtrer) 200
GET /api/todos/{id} lit une tâche 200, 404
POST /api/todos crée une tâche 201, 400, 409
PATCH /api/todos/{id} modifie le titre et/ou l'état 200, 400, 404, 409
DELETE /api/todos/{id} supprime une tâche 204, 404

S'y ajoutent GET /hello, ton tout premier endpoint, et GET /actuator/health pour la supervision. Une règle métier donne un peu de matière : au plus 50 tâches non terminées à la fois (valeur configurable), sinon 409 Conflict.

Voici un vrai échange avec l'API terminée :

$ curl -i -X POST http://localhost:8080/api/todos -H 'Content-Type: application/json' -d '{"title": "Acheter du pain"}'
HTTP/1.1 201 
Location: http://localhost:8080/api/todos/1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 21 Oct 2026 15:40:49 GMT

{"id":1,"title":"Acheter du pain","done":false,"createdAt":"2026-10-21T15:40:49.827439Z"}

Et une erreur de validation, au format standard Problem Details (RFC 9457) :

$ curl -X POST http://localhost:8080/api/todos -H 'Content-Type: application/json' -d '{"title": ""}'
{"detail":"Invalid request content.","instance":"/api/todos","status":400,"title":"Bad Request","errors":{"title":"must not be blank"}}

Le plan de cette partie

  1. Premier projet : générer le projet, comprendre @SpringBootApplication, écrire GET /hello, produire un jar exécutable.
  2. Injection de dépendances : beans, conteneur, injection par constructeur, portées et proxies.
  3. API REST : TodoController, les records DTO, la validation, les codes HTTP et les erreurs Problem Details.
  4. Configuration : application.properties, profils, @ConfigurationProperties, variables d'environnement.
  5. Base de données (JPA) : l'entité Todo, JpaRepository, les requêtes dérivées, les transactions et les migrations Flyway.
  6. Tests : tests unitaires avec Mockito, @WebMvcTest, @DataJpaTest et un test de bout en bout.
  7. Déployer : profil de production, PostgreSQL, image Docker et Actuator.

Le projet complet

Le code final, compilé et testé, est téléchargeable :

Télécharger todo-api.zip

Il te faut seulement un JDK 25 : le wrapper Maven ./mvnw télécharge le reste.

cd todo-api
./mvnw verify            # compile et lance les 20 tests
./mvnw spring-boot:run   # démarre l'API sur http://localhost:8080

Le fichier README.md du projet liste un exemple curl pour chaque endpoint.

Garde le code ouvert à côté du cours

Chaque classe est courte et commentée. Les chapitres en citent des extraits : lire le fichier complet en parallèle aide à voir comment les morceaux s'emboîtent.

À retenir

  • Spring Framework est un conteneur IoC : il crée tes objets (les beans) et leur injecte leurs dépendances.
  • Les annotations sont lues à l'exécution par réflexion, et des proxies ajoutent des comportements comme les transactions.
  • Spring Boot ajoute des starters, l'auto-configuration, un Tomcat embarqué et des conventions : très peu de code d'assemblage.
  • Ce qui est explicite dans main avec Axum ou FastAPI est implicite chez Spring : en cas de souci, lis les logs ou lance avec --debug.
  • Une appli Spring se découpe en couches controller → service → repository, et n'expose jamais ses entités, seulement des DTO.
  • Toute cette partie construit la même API todo-api, téléchargeable en entier.