Chapitre 3: Beans et Contexte Spring
Introduction
Dans les chapitres précédents, vous avez utilisé @Service, @Repository, @Controller sans vraiment comprendre ce que c'était. Ce chapitre vous révèle ce qu'est un Bean et comment Spring les gère.
Ce que vous allez apprendre
- Qu'est-ce qu'un Bean Spring?
- Les différentes annotations de stéréotypes
- Comment créer des Beans avec @Bean et @Configuration
- Comment nommer les Beans
- Comment choisir entre plusieurs Beans (@Qualifier, @Primary)
Durée: 45 minutes
Qu'est-ce qu'un Bean?
Définition Simple
Un Bean = Un objet géré par Spring
C'est tout! Rien de magique.
Analogie
Imaginez Spring comme un hôtel:
Hôtel Spring
├── Chambre 101: Bean "userService"
├── Chambre 102: Bean "productRepository"
├── Chambre 103: Bean "orderController"
└── Chambre 104: Bean "emailService"
Spring (l'hôtel):
- Crée les objets (chambres)
- Les nomme (numéros de chambre)
- Les garde en mémoire
- Les donne quand on en a besoin
Vous (le client):
- Demandez un Bean: "Je veux userService"
- Spring vous le donne: "Voici, chambre 101!"
Bean vs Objet Normal
Objet Normal (Sans Spring)
// Vous créez
EmailService emailService = new EmailService();
// Vous gérez la vie de l'objet
emailService.configure();
// Vous devez le passer partout
OrderService orderService = new OrderService(emailService);
Problèmes:
- ❌ Vous devez créer manuellement
- ❌ Vous devez gérer les dépendances
- ❌ Vous devez passer les objets partout
Bean Spring
@Service // ← Spring gère cet objet
public class EmailService {
// Configuration automatique
}
@Service
public class OrderService {
// Spring injecte automatiquement EmailService
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Avantages:
- ✅ Spring crée automatiquement
- ✅ Spring gère les dépendances
- ✅ Spring injecte où nécessaire
Création de Beans: Méthode 1 - Annotations de Stéréotype
Les 4 Annotations Principales
Spring propose 4 annotations pour créer des Beans:
@Component // Composant générique
@Service // Couche métier
@Repository // Couche données
@Controller // Couche présentation (web)
Hiérarchie
Toutes héritent de @Component!
1. @Component (Générique)
Utilisation: Pour n'importe quel composant qui ne rentre pas dans les autres catégories.
@Component
public class PriceCalculator {
public double calculateDiscount(double price) {
return price * 0.9;
}
}
Quand l'utiliser?
- Classes utilitaires
- Composants qui ne sont ni Service, ni Repository, ni Controller
2. @Service (Logique Métier)
Utilisation: Pour la couche métier (Service Layer).
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final EmailService emailService;
public OrderService(OrderRepository orderRepository,
EmailService emailService) {
this.orderRepository = orderRepository;
this.emailService = emailService;
}
public void processOrder(Order order) {
// Logique métier
orderRepository.save(order);
emailService.sendConfirmation(order);
}
}
Quand l'utiliser?
- Classes contenant la logique métier
- Orchestration de plusieurs repositories
- Transactions
3. @Repository (Accès Données)
Utilisation: Pour la couche d'accès aux données.
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(Long customerId);
}
Avantages spécifiques:
- ✅ Traduction automatique des exceptions SQL en exceptions Spring
- ✅ Indication claire du rôle (accès données)
Quand l'utiliser?
- Interfaces JPA Repository
- Classes DAO (Data Access Object)
4. @Controller et @RestController (Web)
Utilisation: Pour la couche web.
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping
public List<Order> getAllOrders() {
return orderService.getAllOrders();
}
}
Différence:
@Controller→ Retourne des vues (HTML)@RestController→ Retourne du JSON (API REST)
Résumé: Quand Utiliser Quelle Annotation?
| Annotation | Couche | Utilisation |
|---|---|---|
@Component | Générique | Classes utilitaires, autres composants |
@Service | Métier | Logique métier, orchestration |
@Repository | Données | Accès base de données |
@Controller | Web | Pages web (HTML) |
@RestController | Web | API REST (JSON) |
Règle d'or: Utilisez l'annotation la plus spécifique!
Création de Beans: Méthode 2 - @Bean et @Configuration
Pourquoi Une Autre Méthode?
Problème: Vous ne pouvez pas ajouter @Service sur des classes que vous ne contrôlez pas.
Exemples:
- Classes de bibliothèques externes
- Classes Java standard (ObjectMapper, RestTemplate, etc.)
Solution: @Bean et @Configuration
@Configuration
Classe de configuration Spring:
@Configuration
public class AppConfig {
// Méthodes @Bean ici
}
@Configuration indique à Spring: "Cette classe contient des définitions de Beans"
@Bean
Créer un Bean manuellement:
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
return mapper;
}
}
Ce qui se passe:
1. Spring scanne AppConfig
↓
2. Spring trouve la méthode objectMapper() annotée @Bean
↓
3. Spring appelle objectMapper()
↓
4. Spring garde le résultat comme Bean nommé "objectMapper"
↓
5. Vous pouvez injecter ObjectMapper partout!
Exemple Complet
@Configuration
public class EmailConfig {
// Bean 1: Configuration des emails
@Bean
public EmailSettings emailSettings() {
EmailSettings settings = new EmailSettings();
settings.setHost("smtp.gmail.com");
settings.setPort(587);
return settings;
}
// Bean 2: Service d'envoi (dépend du Bean 1)
@Bean
public EmailSender emailSender(EmailSettings settings) {
// ^^^^^^^^^^^^^^^^^^^^^^
// Spring injecte automatiquement!
return new EmailSender(settings);
}
}
Utilisation:
@Service
public class OrderService {
private final EmailSender emailSender;
// Spring injecte le Bean créé dans EmailConfig
public OrderService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}
@Bean vs @Service
| Aspect | @Service | @Bean |
|---|---|---|
| Sur quoi? | Classe | Méthode |
| Où? | N'importe où | Dans @Configuration |
| Contrôle | Classe à vous | N'importe quelle classe |
| Usage | Classes métier | Configuration, classes externes |
Exemple:
// ✅ Votre classe
@Service
public class MyService { }
// ✅ Classe externe
@Configuration
public class Config {
@Bean
public ExternalLibraryClass externalClass() {
return new ExternalLibraryClass();
}
}
Nommage des Beans
Nom Par Défaut
Avec @Service, @Component, etc.:
@Service
public class UserService { }
// ^^^^^^^^^^^
// Nom du Bean: "userService" (camelCase)
Avec @Bean:
@Bean
public EmailSender emailSender() {
// ^^^^^^^^^^^^
// Nom du Bean: "emailSender"
return new EmailSender();
}
Règle: Première lettre en minuscule (camelCase)
Nom Personnalisé
Avec @Service:
@Service("myCustomName")
public class UserService { }
// Bean nommé: "myCustomName"
Avec @Bean:
@Bean(name = "gmailSender")
public EmailSender emailSender() {
return new GmailEmailSender();
}
// Bean nommé: "gmailSender"
Gérer Plusieurs Implémentations
Problème
public interface PaymentProcessor {
void process(Payment payment);
}
@Service
public class StripePayment implements PaymentProcessor { }
@Service
public class PayPalPayment implements PaymentProcessor { }
// ❌ Spring ne sait pas laquelle injecter!
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(PaymentProcessor processor) {
// Erreur au démarrage!
}
}
Solution 1: @Primary
Marquer une implémentation comme celle par défaut:
@Service
@Primary // ← Cette implémentation par défaut
public class StripePayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Paiement via Stripe");
}
}
@Service
public class PayPalPayment implements PaymentProcessor {
@Override
public void process(Payment payment) {
System.out.println("Paiement via PayPal");
}
}
Résultat:
@Service
public class OrderService {
private final PaymentProcessor processor;
// Spring injecte automatiquement StripePayment (@Primary)
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}
Solution 2: @Qualifier
Choisir une implémentation spécifique:
@Service("stripePayment")
public class StripePayment implements PaymentProcessor { }
@Service("paypalPayment")
public class PayPalPayment implements PaymentProcessor { }
Injection avec choix:
@Service
public class OrderService {
private final PaymentProcessor processor;
public OrderService(@Qualifier("paypalPayment") PaymentProcessor processor) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Choisit explicitement PayPal
this.processor = processor;
}
}
Solution 3: Injecter Tous les Beans
Si vous avez besoin de toutes les implémentations:
@Service
public class PaymentOrchestrator {
private final List<PaymentProcessor> allProcessors;
// Spring injecte une liste avec TOUS les PaymentProcessor
public PaymentOrchestrator(List<PaymentProcessor> allProcessors) {
this.allProcessors = allProcessors;
}
public void processWithAll(Payment payment) {
for (PaymentProcessor processor : allProcessors) {
processor.process(payment);
}
}
}
Le Contexte Spring (ApplicationContext)
Qu'est-ce que le Contexte?
Le Contexte Spring = Le container qui contient tous les Beans
Accéder au Contexte (Rarement Nécessaire)
Normalement, Spring injecte automatiquement. Mais si besoin:
@Service
public class MyService {
private final ApplicationContext context;
// Injecter le contexte lui-même
public MyService(ApplicationContext context) {
this.context = context;
}
public void doSomething() {
// Récupérer un Bean manuellement
UserService userService = context.getBean(UserService.class);
// Récupérer un Bean par nom
EmailSender sender = context.getBean("emailSender", EmailSender.class);
// Lister tous les Beans d'un type
Map<String, PaymentProcessor> processors =
context.getBeansOfType(PaymentProcessor.class);
}
}
⚠️ Évitez de faire ça! C'est un anti-pattern. Utilisez l'injection normale.
Cycle de Vie des Beans
Étapes
1. Instanciation
↓
2. Injection des dépendances
↓
3. Initialisation (@PostConstruct)
↓
4. Bean prêt (utilisable)
↓
5. Destruction (@PreDestroy)
Hooks de Cycle de Vie
Exécuter du code après l'injection:
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Service
public class DatabaseService {
private final DataSource dataSource;
public DatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
// ❌ dataSource pas encore complètement initialisé
}
@PostConstruct // ← Appelé APRÈS l'injection
public void init() {
System.out.println("DatabaseService initialisé");
// ✅ Tout est prêt, vous pouvez faire des initialisations
connectToDatabase();
}
@PreDestroy // ← Appelé AVANT la destruction
public void cleanup() {
System.out.println("DatabaseService détruit");
// Fermer les connexions, libérer les ressources
disconnectFromDatabase();
}
}
Ordre d'exécution:
1. new DatabaseService(dataSource) ← Constructeur
2. init() ← @PostConstruct
3. [Bean utilisé pendant l'application]
4. cleanup() ← @PreDestroy (à l'arrêt)
Exemple Complet
// Interface
public interface NotificationService {
void send(String message);
}
// Implémentation 1
@Service("emailNotification")
public class EmailNotification implements NotificationService {
@PostConstruct
public void init() {
System.out.println("EmailNotification initialisé");
}
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
@PreDestroy
public void cleanup() {
System.out.println("EmailNotification détruit");
}
}
// Implémentation 2
@Service("smsNotification")
@Primary // ← Par défaut
public class SmsNotification implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// Configuration
@Configuration
public class AppConfig {
@Bean
public NotificationLogger logger() {
return new NotificationLogger();
}
}
// Utilisation
@Service
public class AlertService {
private final NotificationService notification;
private final List<NotificationService> allNotifications;
public AlertService(
NotificationService notification, // SmsNotification (@Primary)
List<NotificationService> allNotifications // Les 2
) {
this.notification = notification;
this.allNotifications = allNotifications;
}
public void alert(String message) {
// Utilise SMS (Primary)
notification.send(message);
}
public void alertAll(String message) {
// Utilise tous
allNotifications.forEach(n -> n.send(message));
}
}
// Utilisation avec choix
@Service
public class SpecialAlertService {
private final NotificationService notification;
public SpecialAlertService(
@Qualifier("emailNotification") NotificationService notification
) {
this.notification = notification;
}
}
Résumé du Chapitre
Qu'est-ce qu'un Bean?
Bean = Objet géré par Spring
Créer des Beans
Méthode 1: Annotations de stéréotype
@Component // Générique
@Service // Métier
@Repository // Données
@Controller // Web
Méthode 2: @Bean dans @Configuration
@Configuration
public class Config {
@Bean
public MyClass myBean() {
return new MyClass();
}
}
Gérer Plusieurs Implémentations
@Primary // Implémentation par défaut
@Qualifier("name") // Choisir spécifiquement
List<Interface> // Injecter toutes
Cycle de Vie
@PostConstruct // Après injection
@PreDestroy // Avant destruction
Points Clés
✅ Bean = Objet géré par Spring
✅ @Service, @Repository, @Controller héritent de @Component
✅ @Bean pour classes externes ou configuration complexe
✅ @Primary pour implémentation par défaut
✅ @Qualifier pour choix spécifique
✅ @PostConstruct pour initialisation
✅ @PreDestroy pour nettoyage
Auto-Évaluation
- Quelle est la différence entre un Bean et un objet normal?
- Quand utiliser @Service vs @Component?
- Quelle est la différence entre @Service et @Bean?
- Comment gérer plusieurs implémentations d'une interface?
- À quoi sert @PostConstruct?
Prochaine Étape
Vous comprenez maintenant ce que sont les Beans. Passons aux Scopes pour contrôler leur cycle de vie!
➡️ Chapitre 4: Scopes et Configuration