Chapitre 4: Annotations de Base Spring Boot
Introduction
Les annotations sont au cœur de Spring Boot. Elles permettent de configurer l'application de manière déclarative, sans XML, et rendent le code plus lisible et maintenable.
Objectifs du Chapitre
À la fin de ce chapitre, vous serez capable de:
- Comprendre le rôle des annotations principales de Spring Boot
- Utiliser
@RestController,@Service,@Repository - Maîtriser les annotations de mapping HTTP
- Injecter des dépendances avec
@Autowired - Créer des beans Spring
Qu'est-ce qu'une Annotation?
Définition
Une annotation est une forme de métadonnée qui fournit des informations sur le code sans en faire partie directement.
Format:
@NomAnnotation
@NomAnnotation(paramettre=valeur)
Exemple:
@RestController
@RequestMapping("/api")
public class UserController {
// ...
}
Pourquoi les Annotations?
Avantages:
- Code plus concis
- Configuration proche du code
- Type-safe (vérification à la compilation)
- Facilite le refactoring
@SpringBootApplication
L'Annotation Maîtresse
package com.example.greeting;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GreetingApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingApplication.class, args);
}
}
Composition de @SpringBootApplication
@SpringBootApplication est une méta-annotation qui combine trois annotations:
@SpringBootApplication
=
@SpringBootConfiguration
+ @EnableAutoConfiguration
+ @ComponentScan
Détail de Chaque Annotation
1. @SpringBootConfiguration
Rôle: Marque la classe comme source de définitions de beans
Équivalent à:
@Configuration
Permet de définir des beans:
@SpringBootApplication
public class GreetingApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. @EnableAutoConfiguration
Rôle: Active la configuration automatique de Spring Boot
Exemple: Si Spring Boot détecte:
spring-boot-starter-web→ Configure Tomcat, Spring MVCspring-boot-starter-data-jpa→ Configure Hibernate, DataSourcePostgreSQL Driver→ Configure PostgreSQL
Configuration automatique:
3. @ComponentScan
Rôle: Scanne le package et les sous-packages à la recherche de composants Spring
Package scanné:
package com.example.greeting; // Package de base
// Spring scannera:
// - com.example.greeting
// - com.example.greeting.controller
// - com.example.greeting.service
// - etc.
Composants détectés:
@Component@Controller/@RestController@Service@Repository@Configuration
Annotations de Stéréotype
Hiérarchie des Composants
@Component
Rôle: Annotation générique pour tout composant Spring
Utilisation:
import org.springframework.stereotype.Component;
@Component
public class EmailValidator {
public boolean isValid(String email) {
return email.contains("@");
}
}
Quand l'utiliser: Pour des classes utilitaires qui ne rentrent pas dans les autres catégories
@Controller
Rôle: Marque une classe comme contrôleur MVC (retourne des vues)
Utilisation:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "Bienvenue!");
return "home"; // Retourne la vue "home.html"
}
}
Architecture:
@RestController
Rôle: Contrôleur REST (retourne des données JSON/XML)
Équivalent à:
@RestController
=
@Controller + @ResponseBody
Utilisation:
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class GreetingController {
@GetMapping("/greeting")
public Greeting greeting() {
return new Greeting(1L, "Hello, World!");
}
}
Architecture:
Différence @Controller vs @RestController:
| Aspect | @Controller | @RestController |
|---|---|---|
| Retour | Nom de vue (String) | Données (JSON/XML) |
| Usage | Applications web traditionnelles | REST APIs |
| Serialization | Non | Automatique (Jackson) |
| @ResponseBody | Requis sur chaque méthode | Implicite |
@Service
Rôle: Marque une classe contenant la logique métier
Utilisation:
import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class GreetingService {
private final AtomicLong counter = new AtomicLong();
private static final String template = "Hello, %s!";
public Greeting createGreeting(String name) {
return new Greeting(
counter.incrementAndGet(),
String.format(template, name)
);
}
}
Responsabilités:
- Logique métier
- Calculs complexes
- Orchestration des repositories
- Transactions
@Repository
Rôle: Marque une classe d'accès aux données
Utilisation avec JPA:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Spring Data JPA génère automatiquement l'implémentation
}
Responsabilités:
- Requêtes à la base de données
- CRUD operations
- Queries personnalisées
Avantage: Traduction automatique des exceptions SQL en exceptions Spring
Annotations de Mapping HTTP
Vue d'Ensemble
@RequestMapping
Rôle: Annotation générique pour mapper des requêtes HTTP
Syntaxe:
@RequestMapping(value = "/path", method = RequestMethod.GET)
Utilisation au niveau de la classe:
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// GET /api/users/123
return userService.findById(id);
}
}
@GetMapping
Rôle: Raccourci pour les requêtes GET
Équivalent à:
@GetMapping("/path")
=
@RequestMapping(value = "/path", method = RequestMethod.GET)
Exemples:
@RestController
@RequestMapping("/api")
public class ProductController {
// GET /api/products
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
}
// GET /api/products/123
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// GET /api/products?category=books
@GetMapping("/products")
public List<Product> getProductsByCategory(@RequestParam String category) {
return productService.findByCategory(category);
}
}
@PostMapping
Rôle: Raccourci pour les requêtes POST (création)
Utilisation:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Requête:
POST /api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
@PutMapping
Rôle: Raccourci pour les requêtes PUT (mise à jour complète)
Utilisation:
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
Requête:
PUT /api/users/123
Content-Type: application/json
{
"name": "John Doe Updated",
"email": "john.updated@example.com"
}
@DeleteMapping
Rôle: Raccourci pour les requêtes DELETE (suppression)
Utilisation:
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
Requête:
DELETE /api/users/123
@PatchMapping
Rôle: Raccourci pour les requêtes PATCH (mise à jour partielle)
Utilisation:
@PatchMapping("/{id}")
public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}
Requête:
PATCH /api/users/123
Content-Type: application/json
{
"email": "newemail@example.com"
}
Tableau Récapitulatif
| Méthode HTTP | Annotation | Action | Idempotent |
|---|---|---|---|
| GET | @GetMapping | Lire | ✅ |
| POST | @PostMapping | Créer | ❌ |
| PUT | @PutMapping | Mettre à jour (complet) | ✅ |
| PATCH | @PatchMapping | Mettre à jour (partiel) | ❌ |
| DELETE | @DeleteMapping | Supprimer | ✅ |
Annotations de Paramètres
@PathVariable
Rôle: Extraire une valeur de l'URL
Syntaxe:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// URL: /users/123
// id = 123
return userService.findById(id);
}
Plusieurs variables:
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId
) {
return orderService.findByUserAndId(userId, orderId);
}
Nom différent:
@GetMapping("/users/{id}")
public User getUser(@PathVariable("id") Long userId) {
return userService.findById(userId);
}
@RequestParam
Rôle: Extraire des paramètres de query string
Syntaxe:
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
// URL: /users?name=John
// name = "John"
return userService.findByName(name);
}
Paramètre optionnel:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(required = false) String name
) {
if (name != null) {
return userService.findByName(name);
}
return userService.findAll();
}
Valeur par défaut:
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.findAll(page, size);
}
Plusieurs paramètres:
@GetMapping("/users")
public List<User> searchUsers(
@RequestParam String name,
@RequestParam int age,
@RequestParam String city
) {
// URL: /users?name=John&age=30&city=Montreal
return userService.search(name, age, city);
}
@RequestBody
Rôle: Mapper le corps de la requête HTTP sur un objet Java
Utilisation:
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
Requête HTTP:
POST /users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
Objet Java reçu:
User user = new User();
user.setName("John Doe");
user.setEmail("john@example.com");
user.setAge(30);
@RequestHeader
Rôle: Extraire un header HTTP
Utilisation:
@GetMapping("/secure")
public String secureEndpoint(
@RequestHeader("Authorization") String token
) {
// token = "Bearer eyJhbGciOiJIUzI1NiIs..."
return "Authenticated!";
}
Tableau Comparatif
| Annotation | Source | Exemple URL | Utilisation |
|---|---|---|---|
| @PathVariable | Chemin URL | /users/123 | Identifiants de ressources |
| @RequestParam | Query string | /users?name=John | Filtres, pagination |
| @RequestBody | Corps HTTP | POST/PUT body | Envoi de données |
| @RequestHeader | Headers HTTP | Authorization: Bearer token | Authentification |
Injection de Dépendances
@Autowired
Rôle: Injecte automatiquement une dépendance
Types d'Injection
1. Injection par Constructeur (Recommandée)
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
Depuis Spring 4.3: @Autowired est optionnel si un seul constructeur
@RestController
public class UserController {
private final UserService userService;
// @Autowired pas nécessaire
public UserController(UserService userService) {
this.userService = userService;
}
}
Avantages:
- Immutabilité (final)
- Facilite les tests
- Obligatoire (pas de NullPointerException)
2. Injection par Setter
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
Avantages:
- Dépendances optionnelles
- Reconfiguration possible
Inconvénients:
- Mutable
- Risque de NullPointerException
3. Injection par Champ (Non Recommandée)
@RestController
public class UserController {
@Autowired
private UserService userService;
}
Inconvénients:
- Difficile à tester
- Couplage fort
- Impossible de créer un objet immutable
Recommandation: Toujours utiliser l'injection par constructeur
Exemple Complet d'Architecture
// Entité
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters/Setters
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
// Service
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
}
public User save(User user) {
return userRepository.save(user);
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Flux des dépendances:
Exercices Pratiques
Exercice 1: Créer un Contrôleur REST
Objectif: Utiliser @RestController et @GetMapping
Instructions:
- Créez une classe
HelloController - Annotez avec
@RestController - Créez une méthode
sayHello()qui retourne "Hello, Spring Boot!" - Annotez avec
@GetMapping("/hello") - Testez avec le navigateur
Exercice 2: Utiliser @PathVariable
Objectif: Extraire des paramètres d'URL
Instructions:
- Créez un endpoint
/greet/\{name\} - Retournez "Hello, {name}!"
- Testez avec
/greet/John
Exercice 3: Créer un Service
Objectif: Séparer la logique métier
Instructions:
- Créez une classe
CalculatorServiceavec@Service - Ajoutez une méthode
add(int a, int b) - Créez un
CalculatorController - Injectez le service par constructeur
- Créez un endpoint
/add?a=5&b=3
Exercice 4: Architecture Complète
Objectif: Créer une architecture à 3 couches
Instructions:
- Créez une classe
Product(model) - Créez
ProductServiceavec une liste en mémoire - Créez
ProductController - Implémentez:
- GET /products (tous)
- GET /products/{id}
- POST /products
Questions de Révision
-
Quelles annotations composent @SpringBootApplication?
-
Quelle est la différence entre @Controller et @RestController?
-
Quand utiliser @Service vs @Component?
-
Quelle est la différence entre @PathVariable et @RequestParam?
-
Pourquoi l'injection par constructeur est-elle recommandée?
-
Que fait l'annotation @RequestBody?
-
Quelle méthode HTTP correspond à @PostMapping?
-
À quoi sert @Repository?
Résumé du Chapitre
Points Clés
@SpringBootApplicationcombine configuration, auto-configuration et scan@RestController=@Controller+@ResponseBody@Servicepour la logique métier,@Repositorypour l'accès aux données- Annotations de mapping:
@GetMapping,@PostMapping, etc. @PathVariablepour l'URL,@RequestParampour les query strings- Injection par constructeur recommandée
Bonnes Pratiques
- Utiliser des annotations spécialisées (@Service, @Repository) plutôt que @Component
- Injection par constructeur avec final
- Un contrôleur par ressource REST
- Séparer clairement les responsabilités (Controller → Service → Repository)
- Nommer les endpoints de manière cohérente (REST conventions)
Prochaines Étapes
Félicitations! Vous avez complété le Module 2.
Dans le Module 3 (à venir), vous approfondirez:
- L'Inversion de Contrôle en détail
- Le cycle de vie des beans
- Les scopes
- La configuration avancée
Mettre en Pratique
Avant de passer au module suivant, assurez-vous de:
- Compléter le TP2.1: Application Greeting
- Refaire les exercices de chaque chapitre
- Créer votre propre mini-projet pour consolider les acquis