Aller au contenu principal

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​

  1. Qu'est-ce qu'un Bean Spring?
  2. Les différentes annotations de stéréotypes
  3. Comment créer des Beans avec @Bean et @Configuration
  4. Comment nommer les Beans
  5. 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?​

AnnotationCoucheUtilisation
@ComponentGénériqueClasses utilitaires, autres composants
@ServiceMétierLogique métier, orchestration
@RepositoryDonnéesAccès base de données
@ControllerWebPages web (HTML)
@RestControllerWebAPI 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?ClasseMéthode
Où?N'importe oùDans @Configuration
ContrôleClasse à vousN'importe quelle classe
UsageClasses métierConfiguration, 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​

  1. Quelle est la différence entre un Bean et un objet normal?
  2. Quand utiliser @Service vs @Component?
  3. Quelle est la différence entre @Service et @Bean?
  4. Comment gérer plusieurs implémentations d'une interface?
  5. À 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

⬅️ Chapitre précédent: Injection de Dépendances

⬅️ Retour au Module 5