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
Dropgéré pour toi).
@Service // (1)!
public class TodoService {
private final TodoRepository repository;
public TodoService(TodoRepository repository) { // (2)!
this.repository = repository;
}
}
- « Ceci est un bean » : Spring le trouvera au démarrage et en créera une instance unique.
- Spring voit ce constructeur, cherche un bean de type
TodoRepositoryet le passe en argument. Tu n'écris nulle partnew 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-webmvcapporte 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 avecjava -jar, un peu comme le binaire produit parcargo build. - Les conventions
- Configuration dans
src/main/resources/application.properties, port 8080, scan des composants à partir du paquet de la classe principale, scripts SQL dansdb/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
--debugpour 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é
Todoreprésente une ligne de la tabletodo; 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¶
- Premier projet : générer le projet, comprendre
@SpringBootApplication, écrireGET /hello, produire un jar exécutable. - Injection de dépendances : beans, conteneur, injection par constructeur, portées et proxies.
- API REST :
TodoController, les records DTO, la validation, les codes HTTP et les erreurs Problem Details. - Configuration :
application.properties, profils,@ConfigurationProperties, variables d'environnement. - Base de données (JPA) : l'entité
Todo,JpaRepository, les requêtes dérivées, les transactions et les migrations Flyway. - Tests : tests unitaires avec Mockito,
@WebMvcTest,@DataJpaTestet un test de bout en bout. - Déployer : profil de production, PostgreSQL, image Docker et Actuator.
Le projet complet¶
Le code final, compilé et testé, est téléchargeable :
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
mainavec 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.