Contactez-nous

Personnalisation du contexte d'application Spring (`ApplicationContextInitializer`, `EnvironmentPostProcessor`)

Découvrez comment personnaliser finement le démarrage de Spring Boot en utilisant ApplicationContextInitializer et EnvironmentPostProcessor pour agir avant la création du contexte.

Introduction : Intervenir très tôt dans le cycle de vie

Le processus de démarrage de Spring Boot est une séquence bien orchestrée : chargement des propriétés, application de l'auto-configuration, scan des composants, création et injection des beans. La plupart des personnalisations se font via des beans `@Configuration`, des propriétés dans `application.properties`/`yml`, ou des annotations conditionnelles. Cependant, il existe des scénarios où ces mécanismes interviennent trop tard ou ne sont pas assez flexibles.

Parfois, vous avez besoin d'influencer la création même de l'`ApplicationContext` ou de manipuler l'`Environment` (l'ensemble des propriétés) avant que le contexte ne soit créé ou complètement initialisé. Par exemple, vous pourriez vouloir charger des propriétés depuis un emplacement non standard, enregistrer des beans programmatiquement avant le scan des composants, ou configurer le contexte lui-même de manière dynamique.

Pour ces besoins avancés, Spring Boot expose des points d'extension qui permettent d'intervenir très tôt dans le cycle de démarrage. Les deux interfaces principales pour cela sont `EnvironmentPostProcessor` et `ApplicationContextInitializer`. Elles offrent des capacités de personnalisation profondes mais doivent être utilisées avec discernement car elles agissent à un niveau bas et potentiellement sensible du framework.

EnvironmentPostProcessor : Modifier l'environnement avant la création du contexte

L'interface `EnvironmentPostProcessor` permet d'inspecter et de modifier l'`Environment` (qui contient toutes les sources de propriétés : système, variables d'environnement, fichiers `application.*`, etc.) avant que l'`ApplicationContext` ne soit créé et rafraîchi. Cela signifie que vous pouvez ajouter, supprimer, ou réordonner des `PropertySource` de manière programmatique.

Son exécution se situe après le chargement des sources de propriétés standards mais avant qu'elles ne soient utilisées pour configurer l'application ou évaluer les conditions (`@ConditionalOnProperty`). C'est donc l'endroit idéal pour :

  • Charger des configurations depuis des sources personnalisées (ex: base de données, service externe, fichier spécifique).
  • Décrypter des propriétés sensibles avant qu'elles ne soient utilisées.
  • Ajouter des propriétés par défaut calculées dynamiquement.
  • Manipuler l'ordre de priorité des sources de propriétés.

Pour l'implémenter, vous créez une classe qui implémente `EnvironmentPostProcessor` et sa méthode `postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application)`.

Exemple simple : Ajout d'une source de propriétés personnalisée

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import java.util.HashMap;
import java.util.Map;

public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        System.out.println("*** CustomEnvironmentPostProcessor en action ! ***");
        
        // Simule la lecture de propriétés depuis une source externe
        Map customProperties = new HashMap<>();
        customProperties.put("custom.property.source", "Valeur de EnvironmentPostProcessor");
        customProperties.put("server.port", "9090"); // Peut surcharger une propriété existante

        MapPropertySource customPropertySource = 
            new MapPropertySource("customProps", customProperties);

        MutablePropertySources propertySources = environment.getPropertySources();
        
        // Ajoute notre source avec une priorité élevée (peut être ajusté avec addBefore/addAfter)
        propertySources.addFirst(customPropertySource);
        
        System.out.println("Source de propriétés personnalisée ajoutée.");
    }
}

Pour que Spring Boot découvre et exécute votre `EnvironmentPostProcessor`, vous devez le déclarer. La méthode traditionnelle (toujours supportée) est via le fichier `META-INF/spring.factories` :

# META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\com.exemple.myapp.config.CustomEnvironmentPostProcessor

La méthode plus moderne (depuis Spring Boot 2.7) et recommandée pour les auto-configurations est via le fichier `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` :

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.exemple.myapp.config.CustomEnvironmentPostProcessor

ApplicationContextInitializer : Initialiser le contexte avant son rafraîchissement

L'interface `ApplicationContextInitializer` permet d'intervenir sur l'`ApplicationContext` lui-même après sa création et la préparation de l'environnement (donc après l'exécution des `EnvironmentPostProcessor`), mais avant qu'il ne soit rafraîchi (c'est-à-dire avant que les beans ne soient réellement instanciés et configurés).

C'est le bon endroit pour :

  • Enregistrer des beans programmatiquement dans le contexte (via `context.registerBean()` ou `context.getBeanFactory().registerSingleton()`). Utile si la création du bean dépend de logiques complexes non exprimables facilement avec `@Configuration`.
  • Personnaliser le contexte lui-même (ex: définir un ID, un contexte parent).
  • Accéder à l'`Environment` déjà finalisé pour prendre des décisions d'initialisation.
  • Configurer programmatiquement des `BeanFactoryPostProcessor` ou `BeanDefinitionRegistryPostProcessor`.

Vous implémentez la méthode `initialize(ConfigurableApplicationContext applicationContext)`.

Exemple simple : Enregistrement d'un bean et accès à l'environnement

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

public class CustomContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("*** CustomContextInitializer en action ! ***");

        Environment environment = applicationContext.getEnvironment();
        String customProp = environment.getProperty("custom.property.source", "Valeur par défaut");
        System.out.println("Valeur de la propriété personnalisée lue : " + customProp);
        
        // Vérifie si un profil 'special' est actif pour enregistrer un bean
        if (environment.acceptsProfiles(org.springframework.core.env.Profiles.of("special"))) {
            System.out.println("Profil 'special' actif, enregistrement du bean SpecialBean...");
            // Enregistre une instance simple (singleton)
            applicationContext.getBeanFactory().registerSingleton("specialBeanInstance", new SpecialBean());
            // Ou via registerBean pour plus de contrôle (depuis Spring 5)
            // applicationContext.registerBean(SpecialBean.class, () -> new SpecialBean());
        }
    }
    
    // Classe factice pour l'exemple
    public static class SpecialBean {}
}

La déclaration d'un `ApplicationContextInitializer` se fait de la même manière que pour `EnvironmentPostProcessor`, via `META-INF/spring.factories` :

# META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\com.exemple.myapp.config.CustomContextInitializer

Ou via `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` (recommandé pour l'auto-configuration) :

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.exemple.myapp.config.CustomContextInitializer

Il est aussi possible de les enregistrer via la propriété `context.initializer.classes` dans `application.properties` ou programmatiquement via `SpringApplication.addInitializers()`.

Choisir le bon outil et considérations

La principale différence réside dans le moment d'exécution et l'objet manipulé :

  • Utilisez `EnvironmentPostProcessor` si vous avez besoin de modifier l'environnement (les propriétés) avant même la création du contexte. C'est le plus précoce des deux.
  • Utilisez `ApplicationContextInitializer` si vous avez besoin d'interagir avec le contexte applicatif (enregistrer des beans, accéder à l'environnement finalisé) avant le rafraîchissement (instanciation des beans).

Considérations importantes :

  • Complexité : Ces interfaces sont puissantes mais relèvent d'une utilisation avancée. Pour la plupart des cas, les configurations standard (@Configuration, @Bean, application.properties) sont suffisantes et plus simples à gérer et à tester.
  • Ordre d'exécution : Si vous avez plusieurs `EnvironmentPostProcessor` ou `ApplicationContextInitializer`, leur ordre d'exécution peut être important. Vous pouvez le contrôler en implémentant l'interface `org.springframework.core.Ordered` ou en utilisant l'annotation `@org.springframework.core.annotation.Order`. Les post-processeurs/initialiseurs définis via `spring.factories` ou `AutoConfiguration.imports` sont généralement triés.
  • Impact sur le démarrage : Le code exécuté dans ces composants s'ajoute au temps de démarrage de l'application. Gardez-le aussi léger et rapide que possible.
  • Alternatives : Avant d'utiliser ces interfaces, assurez-vous qu'il n'existe pas une solution plus simple utilisant les mécanismes standards de Spring Boot (auto-configuration conditionnelle, profils, configuration de propriétés, `BeanFactoryPostProcessor`, `BeanDefinitionRegistryPostProcessor`, etc.).

En maîtrisant `EnvironmentPostProcessor` et `ApplicationContextInitializer`, vous disposez des outils ultimes pour adapter le cycle de vie de démarrage de Spring Boot à des exigences très spécifiques, bien au-delà des personnalisations habituelles.