CodeGym /Cours /Spring Core /Points d'extension ContextFlow

Points d'extension ContextFlow

Spring Core
Niveau 19, Leçon 4
Disponible

1. Conception des points d'extension

Lorsqu'on découvre BFPP/BPP, on a naturellement envie de se dire : « Oh, super ! Maintenant je vais tout vérifier, tout corriger, tout remplacer… et de toute façon, pourquoi aurais-je besoin d'architecture alors que j'ai des post-processeurs ? » C'est un peu comme voir du ruban adhésif et décider qu'on n'a plus besoin de clous. Oui, c'est possible. Mais à un moment donné, la porte reste dans vos mains avec le ruban, et vous faites comme si c'était prévu.

Dans ContextFlow, notre objectif est différent : ne pas transformer le projet en cirque d'astuces de conteneurs, mais obtenir deux effets très pragmatiques. Le premier effet est les vérifications précoces, qui fournissent une erreur claire au démarrage si l'assemblage du contexte est incorrect (par exemple, s'il n'y a aucun NotificationSender). Le deuxième effet est le diagnostic, qui aide à voir ce que le conteneur expose comme bean : quel beanName, quelle Class réelle, quels objets ont été initialisés.

Après la carte refresh(), BFPP, BPP et l'analyse de l'infrastructure d'annotations, il reste à assembler tout cela dans un schéma de câblage stable du projet. Sinon, les post-processeurs resteront un ensemble de démonstrations distinctes plutôt qu'une partie normale de l'application.

Et là, un point subtil est important : BFPP et BPP ne sont pas « de la logique métier placée ailleurs ». Ce sont des crochets d'infrastructure. Ils doivent fonctionner comme un tourniquet de métro : le tourniquet vérifie que le ticket est présent et valide, mais il ne décide pas où vous allez ni pourquoi vous êtes sorti de chez vous. Si BFPP commence à « ajuster » des décisions métier, vous obtiendrez une application impossible à lire sans connaître les phases internes de refresh().

2. Règle des couches : support et application

Pour que BFPP/BPP ne deviennent pas un « second Spring dans Spring », il faut adopter à l'avance une règle architecturale simple : la couche métier (domain et application) n'a pas à savoir que le conteneur possède des post-processeurs. Cette connaissance est strictement infrastructurelle. C'est pourquoi nous avons des paquets support.* et config.* : ils sont comme la « salle des administrateurs système » où les utilisateurs ordinaires (les services métier) ne vont pas.

Dans ContextFlow, il est pratique de fixer cela directement par la structure des paquets. Au sommet : le domaine et les ports qui décrivent « ce que nous voulons » (notifier, écrire un audit, sauvegarder une commande). Ensuite, les services application qui orchestrent les scénarios. Et séparément, infrastructure et support qui s'occupent du « comment cela fonctionne exactement » et « comment le diagnostiquer ». BFPP/BPP sont des résidents typiques de support.postprocessor, car ils utilisent le SPI Spring (BeanFactoryPostProcessor, BeanPostProcessor) et communiquent directement avec le conteneur.

Il est utile d'avoir en tête ce tableau pour ne pas confondre les rôles et ne pas importer la mécanique du conteneur dans le modèle domaine – cela se termine presque toujours mal :

Couche du projet Ce qu'elle contient Peut-elle dépendre de Spring ? Exemples
domain.* entités, événements, ports (interfaces) de préférence minimal Order, Customer, OrderCreatedEvent, NotificationSender
application.* services use-case et scénarios acceptable, mais sans « cerveau conteneur » OrderPlacementService, ScenarioRunner
infrastructure.* implémentations des ports oui, mais sans exotisme SPI ConsoleNotificationSender, FileAuditWriter
support.* extensions infrastructurelles du conteneur oui, c'est leur rôle BFPP/BPP, convertisseurs, helpers de cycle de vie
config.* câblage et enregistrement des beans oui AppConfig, SupportConfig

Pour faire court : BFPP/BPP doivent être ressentis comme le « câblage dans les murs » : c'est important, rien ne fonctionne sans, mais vous ne prenez pas de décision métier « créer une commande » au niveau du câblage.

3. SupportConfig : enregistrement des BFPP/BPP

L'un des problèmes les plus désagréables pour un débutant avec Spring est « pourquoi cela a fonctionné ? » ou « pourquoi cela a soudainement cessé de fonctionner ? ». Et presque toujours, la réponse est liée au fait que l'infrastructure s'est « activée » quelque part sans être formulée comme une partie compréhensible de la configuration. C'est pourquoi dans ContextFlow, nous n'enregistrons pas les post-processeurs « n'importe où », mais nous créons un module de configuration séparé : SupportConfig. Un module, une importation dans AppConfig, un point d'entrée clair pour toute l'infrastructure spécifique au conteneur.

Imaginez qu'une personne ouvre le dépôt et essaie de comprendre ce qui se passe. Si les BFPP/BPP se trouvent à côté des services métier ou sont activés via un @Component aléatoire au fond d'un paquet, alors la « magie » devient de la vraie magie — dans le mauvais sens. Mais s'il existe un module SupportConfig, les extensions du conteneur deviennent une partie explicite de l'assemblage : comme un tableau de fusibles dans une boîte. Tu veux ? Regarde quels « disjoncteurs » sont installés ; tu veux ? Désactive temporairement une partie du diagnostic par profil.

Voici un exemple minimal de SupportConfig qui enregistre BFPP et BPP dans notre projet :

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SupportConfig {

    @Bean
    static BeanFactoryPostProcessor contextFlowSanityChecks() {
        // BFPP doit être connecté le plus tôt possible, donc @Bean est static.
        // Ainsi Spring pourra l'appeler avant la création de l'instance de la classe de configuration.
        return new ContextFlowSanityCheckBeanFactoryPostProcessor();
    }

    @Bean
    BeanPostProcessor trackedComponents() {
        // BPP — c'est déjà un diagnostic au niveau des objets réels (après la création des beans).
        return new TrackedComponentBeanPostProcessor();
    }
}

Notez le static sur @Bean pour BFPP. Nous n'en faisons pas un rituel mystique, mais nous fixons une idée importante : BFPP est un « participant précoce au démarrage ». Moins il y a de chances qu'il soit créé « trop tard », moins il y a de surprises.

Maintenant, il faut connecter SupportConfig à la configuration racine de l'application. Dans un projet réel, AppConfig rassemble déjà probablement les modules via @Import, et nous en ajoutons un de plus :

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({
        // Noyau métier (use-cases, services domaine, etc.)
        CoreConfig.class,
        // Implémentations des ports (fichiers, console, base de données, etc.)
        InfrastructureConfig.class,
        // Extensions infrastructurelles du conteneur : BFPP/BPP et diagnostic
        SupportConfig.class
})
public class AppConfig {
}

Et enfin, le point d'entrée (conditionnellement main()) reste honnête et court : on démarre le contexte et on lance le scénario via ScenarioRunner, comme avant.

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ContextFlowMain {
    public static void main(String[] args) {
        // try-with-resources garantit la fermeture correcte du contexte et l'appel des shutdown hooks.
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            // On récupère le scénario de haut niveau depuis le conteneur et on l'exécute.
            context.getBean(ScenarioRunner.class).run();
        }
    }
}

Oui, cela semble « ennuyeux ». Et c'est un compliment : un bootstrap ennuyeux est le signe que le chaos ne se cache pas là où il sera impossible à déboguer plus tard.

4. BFPP : vérifications précoces (sanity checks)

BFPP est un excellent outil pour rendre le démarrage du contexte aussi prévisible que possible : si l'assemblage est cassé, il vaut mieux échouer immédiatement et expliquer clairement ce qui ne va pas. Mais pour que BFPP ne devienne pas une source de douleur, nous l'utilisons comme un « contrôle sanitaire » : nous vérifions la structure du contexte, la présence des rôles obligatoires, les exigences minimales d'assemblage — et c'est tout. Sans tentative de « décider à votre place » des décisions métier.

Ci-dessous un exemple de BFPP pour ContextFlow qui vérifie qu'il existe au moins un NotificationSender dans le conteneur. Ce n'est pas une règle métier du niveau « à qui envoyer », c'est une règle structurelle : le projet est présenté comme une application avec notifications, donc au moins un expéditeur doit exister.

import com.example.contextflow.domain.ports.NotificationSender;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class ContextFlowSanityCheckBeanFactoryPostProcessor
        implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        // Important : ici on vérifie seulement "ce qui est enregistré", pas "ce qui est déjà créé".
        // On utilise donc getBeanNamesForType(...) au lieu de getBean(...).
        String[] senders = bf.getBeanNamesForType(NotificationSender.class, true, false);

        // true  -> prend en compte non seulement les singletons, mais aussi les autres scopes / fabriques (en gros : "cherche plus large").
        // false -> NE force PAS la création des beans pour la vérification (sinon on pourrait lancer la moitié de l'application).
        if (senders.length == 0) {
            // Le message est délibérément orienté "projet", pas Spring : ainsi l'erreur est plus facile à lire au démarrage.
            throw new IllegalStateException("ContextFlow: at least one NotificationSender is required");
        }
    }
}

On utilise ici spécifiquement getBeanNamesForType(...). En langage humain, cela signifie : « regarde ce qui est enregistré par type, mais n'essaie pas de créer des beans pour cela ». Dans une vérification sanitaire, nous avons besoin du niveau d'assemblage, pas du lancement de la moitié de l'application au milieu du démarrage.

En général, dans ContextFlow, on souhaite vérifier non pas un seul type, mais plusieurs ports « de base ». Alors BFPP se transforme rapidement en copier-coller, on peut donc créer un petit helper à l'intérieur de la classe — oui, même à l'intérieur de BFPP on peut écrire du code normal, nous ne sommes pas dans un temple du minimalisme :

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

// Fragment : méthode helper que l'on peut garder dans la classe BFPP (par exemple, en private static).
private static void requireType(ConfigurableListableBeanFactory bf, Class
   type) {
    // Vérifie la "présence par type" sans créer d'instances.
    String[] names = bf.getBeanNamesForType(type, true, false);
    if (names.length == 0) {
        // Le message est rendu aussi clair que possible : quel type obligatoire manque.
        throw new IllegalStateException("ContextFlow: missing bean of type " + type.getSimpleName());
    }
}

Et on l'utilise ainsi :

// Ce sont précisément les "supports structurels" de l'application : sans eux, le projet n'est pas considéré comme assemblé.
requireType(bf, NotificationSender.class);
requireType(bf, AuditWriter.class);
requireType(bf, OrderStore.class);

Notez le style des erreurs. Dans les sanity checks, il est important que le message soit « orienté projet », pas « concernant les internes de Spring ». Un débutant ne doit pas voir un NoSuchBeanDefinitionException à 70 lignes de profondeur, mais une phrase compréhensible du type : « AuditWriter non trouvé — le projet nécessite au moins une implémentation d'audit ».

Et encore un point pratique : BFPP est un mauvais endroit pour des « corrections silencieuses ». Si vous modifiez automatiquement la moitié des BeanDefinition (rendre lazy, changer le scope, insérer des valeurs par défaut), dans deux semaines personne ne comprendra pourquoi l'application se comporte différemment de ce qui est écrit dans la configuration. Dans un projet pédagogique, c'est particulièrement dangereux : l'étudiant commence à penser que Spring « devine tout seul ».

5. BPP : diagnostic des beans

Si BFPP est une vérification du « plan du bâtiment » avant le début de la construction, alors BPP est le moment où la maison est déjà construite, et vous vous promenez avec une lampe de poche pour regarder où sont les prises, les interrupteurs, et pourquoi il y a soudain un lustre dans la salle de bain. BPP voit les instances réelles des beans, et c'est très utile pour le diagnostic : on peut comprendre quels objets vivent réellement dans le contexte après la phase init, et quelles classes le conteneur expose.

Dans ContextFlow, nous n'avons pas besoin de « traiter tout ce qui bouge » — sinon cela se transforme rapidement en bruit de log et en souffrances de performance. Nous choisissons un critère étroit : par exemple, nous traquons uniquement les services (par convention, *Service). Ainsi la sortie reste lisible et aide à comprendre ce que le conteneur a assemblé.

import org.springframework.beans.factory.config.BeanPostProcessor;

public class TrackedComponentBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // Filtre étroit : on traque uniquement ce qui nous intéresse vraiment de lire dans la console.
        if (beanName.endsWith("Service")) {
            // Diagnostic : affiche le beanName et la classe réelle (utile en cas de proxy et décorateurs).
            System.out.println("[track] " + beanName + " -> " + bean.getClass().getName());
            // exemple de sortie : [track] orderPlacementService -> com.example...OrderPlacementService
        }

        // Important : BPP doit retourner l'objet (le même ou une enveloppe), sinon le bean "disparaît" du contexte.
        return bean;
    }
}

Il y a ici un « détail qui vaut un million de dollars » que les débutants oublient souvent : BPP doit retourner l'objet avec lequel le conteneur continuera à vivre. Si vous retournez null, alors commencera une aventure amusante intitulée « pourquoi je n'ai soudainement plus mon bean alors qu'il existait ». C'est pourquoi nous terminons toujours par return bean; (ou nous retournons une enveloppe — mais c'est une autre histoire, et nous utilisons ici un diagnostic sans modifier le comportement).

Parfois, au lieu de vérifier par le nom, il est plus pratique de limiter par le paquet, surtout si le projet a ses propres conventions de nommage. On peut alors écrire le critère ainsi :

// Alternative au filtre par nom : filtre par paquet (pratique si les noms ne sont pas stricts).
String pkg = bean.getClass().getPackageName();
if (pkg.startsWith("com.example.contextflow.application")) {
    // Affiche le nom court de la classe pour que la sortie soit plus compacte.
    System.out.println("[app] " + beanName + " -> " + bean.getClass().getSimpleName()); // [app] ...
}

Le sens est le même : étroit, prévisible, sans tentative de « traiter absolument tout dans le monde ». Dans un projet pédagogique, cela protège aussi contre la situation où vous commencez accidentellement à imprimer les beans internes d'infrastructure de Spring, et l'étudiant regarde ensuite la console comme la matrice, en demandant : « où est mon code là-dedans ? ».

Effet à l'exécution : ordre de démarrage

Lorsque vous avez ajouté BFPP/BPP, vous voulez vraiment vous assurer qu'ils ont bien fonctionné, et dans la bonne phase. Le moyen le plus simple est de produire une sortie qui puisse être « lue à l'œil » sans transformer le lancement en enquête de niveau criminalistique. Nous ne construisons pas de journalisation de production, mais nous rendons les messages suffisamment stables pour voir l'ordre et le sens.

Il est utile d'avoir en tête ce schéma de démarrage — il aidera aussi à expliquer pourquoi BFPP et BPP ne sont pas interchangeables :

flowchart TD
    A["BeanDefinitions chargés"] --> B["BFPP : vérifications / corrections des métadonnées"]
    B --> C["Création des beans singleton"]
    C --> D["BPP before init (si existant)"]
    D --> E["Phase init : @PostConstruct / initMethod"]
    E --> F["BPP after init (notre traçage)"]
    F --> G["Contexte prêt"]

La vérification pratique devient presque « évidente » : si BFPP échoue, vous verrez l'erreur avant même que les messages du BPP sur les services créés n'apparaissent. Et si BFPP réussit, après la création des beans, vous verrez le traçage.

Par exemple, une sortie — très simplifiée — ressemblerait à ceci :

[track] orderPlacementService -> com.example.contextflow.application.service.OrderPlacementService
[track] orderCancellationService -> com.example.contextflow.application.service.OrderCancellationService
[track] reportingService -> com.example.contextflow.application.reporting.ReportingService

Et oui, c'est un cas où un « println en trop » est un println utile : il transforme un modèle abstrait de pipeline en un comportement observable. Et plus tard, lorsque vous commencerez à inclure d'autres mécanismes d'infrastructure, il vous sera plus facile de distinguer un « objet ordinaire » d'un « truc de conteneur ».

6. Erreurs typiques avec BFPP/BPP

Erreur n°1 : placer les BFPP/BPP à côté des services métier parce que « c'est plus rapide à trouver ».
Cela conduit presque toujours à ce que le projet devienne illisible : à côté de OrderPlacementService apparaît soudainement une classe qui modifie BeanDefinition, et le cerveau du lecteur fait un salto. Il est correct de garder les processeurs dans support.postprocessor et l'enregistrement dans le module config, pour montrer clairement : c'est de l'infrastructure, pas une partie du domaine.

Erreur n°2 : essayer dans BFPP d'obtenir des beans ordinaires via getBean() et de les utiliser comme objets prêts.
Dans la phase BFPP, vous devez penser « conteneur » : il y a des descriptions, un ensemble de définitions, une structure. Si vous commencez à extraire des beans, vous provoquez soit une initialisation précoce, soit vous obtenez des états semi-fonctionnels étranges, ou, plus souvent, vous créez simplement un problème impossible à expliquer à un débutant.

Erreur n°3 : vérifier dans BFPP les données d'exécution du scénario métier.
Parfois, on a envie d'écrire une « vérification » : y a-t-il au moins une commande dans OrderStore ? Mais ce n'est plus une vérification de santé du conteneur, c'est une logique de scénario. Le BFPP du conteneur ne doit pas savoir quelles commandes seront créées et quand. Il est seulement responsable de s'assurer que l'application est assemblée de manière au moins viable : ports présents, implémentations présentes, beans d'infrastructure clés présents.

Erreur n°4 : rendre le critère du BPP trop large et traiter « absolument tous les beans ».
Techniquement, BPP a du pouvoir sur chaque objet dans le contexte. Et si vous l'utilisez sans filtres, vous obtenez du bruit dans la console, des effets de bord imprévisibles, et la question « pourquoi c'est devenu lent ». Dans ContextFlow, traquez seulement des rôles sélectionnés : services, ports spécifiques, paquets spécifiques. Un critère étroit n'est pas une limitation, c'est un moyen de rester sain d'esprit.

Erreur n°5 : oublier de retourner le bean depuis la méthode BPP ou retourner une enveloppe incompatible.
BPP est l'endroit où une seule erreur accidentelle transforme l'application en surréalisme. Si vous retournez null, le bean disparaît. Si vous retournez un objet d'un autre type, l'autowiring peut casser à un endroit inattendu. C'est pourquoi dans un BPP de diagnostic, la bonne réponse est presque toujours : « retourné le même objet, juste affiché des informations ».

1
Mission
Spring Core, niveau 19, leçon 4
Bloqué
`SupportConfig` séparé pour la vérification précoce de la structure de l'application
`SupportConfig` séparé pour la vérification précoce de la structure de l'application
1
Mission
Spring Core, niveau 19, leçon 4
Bloqué
Diagnostic ciblé des beans service depuis la couche support
Diagnostic ciblé des beans service depuis la couche support
1
Étude/Quiz
Flux du contexte, niveau 19, leçon 4
Indisponible
Flux du contexte
Étapes du démarrage de Spring
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION