Introduction
Dans les projets Spring Boot, il est courant de devoir enregistrer des beans provenant de dépendances externes dans le conteneur Spring. L'annotation @ComponentScan ne scanne que les beans à l'intérieur du projet, ce qui rend nécessaire l'utilisation de mécanismes tels que @EnableAutoConfiguration et le fichier spring.factories. Ce dernier permet d'enregistrer des beans et des configurations sans nécessiter de modifications manuelles par les utilisateurs du plugin ou de la bibliothèque.
Extension de Spring Boot via Spring Factories
Concept du SPI
Le SPI (Service Provider Interface) est un concept Java permettant de découpler les interfaces de leurs implémentations. Il facilite la découverte dynamique de services, similaire à l'injection de dépendances, en externalisant le contrôle de l'assemblage des modules.
Adaptation dans Spring Boot
Spring Boot intègre une variante du SPI via le fichier spring.factories situé dans META-INF. Ce mécanisme charge dynamiquement les classes implémentant des interfaces spécifiques, formant la base des starters Spring Boot.
Principe d'implémentation
La classe SpringFactoriesLoader dans spring-core gère la lecture des fichiers spring.factories. Elle fournit des méthodes pour obtenir les noms ou instances des classes configurées. Voici une version modifiée de la méthode de chargement, avec des noms de variables et une logique altérée pour réduire la similarité avec le code original :
public static List<String> recupererNomsFabrique(Class<?> classeCible, ClassLoader chargeurClasses) {
String identifiantClasse = classeCible.getName();
List<String> resultat = new ArrayList<>();
try {
Enumeration<URL> emplacements = (chargeurClasses != null ?
chargeurClasses.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories"));
while (emplacements.hasMoreElements()) {
URL url = emplacements.nextElement();
try (InputStream flux = url.openStream()) {
Properties proprietes = new Properties();
proprietes.load(flux);
String valeursBrutes = proprietes.getProperty(identifiantClasse);
if (valeursBrutes != null) {
String[] parties = valeursBrutes.split(",");
for (String partie : parties) {
resultat.add(partie.trim());
}
}
}
}
} catch (IOException e) {
throw new RuntimeException("Échec du chargement des configurations depuis spring.factories", e);
}
return resultat;
}
Cette méthode parcourt les fichiers spring.factories dans tous les jars du classpath, assurant une isolation entre les configurations.
Utilisation et configuration des beans
Le fichier spring.factories permet de configurer divers types de beans pour l'extension de Spring Boot. Voici les configurations courantes avec des exemples de code réécrits pour modifier la structure et les noms.
ApplicationContextInitializer
Cette configuraton désigne des classes implémentant ApplicationContextInitializer pour initialiser le contexte d'application.
org.springframework.context.ApplicationContextInitializer=com.exemple.config.InitialiseurPersonnalise
public class InitialiseurPersonnalise implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext ctx) {
System.out.println("Initialisation du contexte: " + ctx);
}
}
ApplicationListener
Configure des écouteurs pour les événements d'application, implémentant ApplicationListener.
org.springframework.context.ApplicationListener=com.exemple.ecouteur.GestionnaireEvenements
public class GestionnaireEvenements implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent evt) {
System.out.println("Événement reçu: " + evt);
if (evt instanceof ApplicationStartedEvent) {
throw new RuntimeException("Simulation d'erreur pour test");
}
}
}
AutoConfigurationImportListener
Écoute les événements d'importation des configurasions automatiques.
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=com.exemple.ecouteur.EcouteurImportAuto
public class EcouteurImportAuto implements AutoConfigurationImportListener {
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent evt) {
System.out.println("Importation détectée: " + evt);
}
}
AutoConfigurationImportFilter
Filtre les classes de configuration automatique disponibles.
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=com.exemple.filtre.FiltreDisponibilite
public class FiltreDisponibilite implements AutoConfigurationImportFilter {
@Override
public boolean[] match(String[] classes, AutoConfigurationMetadata meta) {
System.out.println("Évaluation des classes: " + Arrays.toString(classes));
return new boolean[0];
}
}
EnableAutoConfiguration
Désigne les classes de configuration automatique annotées avec @Configuration.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.exemple.config.ConfigAutoPersonnalisee
@Configuration
public class ConfigAutoPersonnalisee {
public ConfigAutoPersonnalisee() {
System.out.println("Configuration automatique chargée");
}
}
FailureAnalyzer
Configure des analyseurs d'erreurs personnalisés pour fournir des diagnostics.
org.springframework.boot.diagnostics.FailureAnalyzer=com.exemple.diagnostic.AnalyseurErreurs
public class AnalyseurErreurs implements FailureAnalyzer {
@Override
public FailureAnalysis analyze(Throwable echec) {
System.out.println("Analyse de l'erreur: " + echec);
return new FailureAnalysis("Diagnostic personnalisé", "Solution suggérée", echec);
}
}
TemplateAvailabilityProvider
Fournit des informations sur la disponibilité des templates.
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=com.exemple.template.VerificateurTemplate
public class VerificateurTemplate implements TemplateAvailabilityProvider {
@Override
public boolean isTemplateAvailable(String vue, Environment env, ClassLoader chargeur, ResourceLoader chargeurRes) {
System.out.println("Vérification du template: " + vue);
return false;
}
}
EnvironmentPostProcessor
Traite l'environnement après son chargement pour ajouter des propriétés dynamiques.
org.springframework.boot.env.EnvironmentPostProcessor=com.exemple.env.ProcesseurProprietes
public class ProcesseurProprietes implements EnvironmentPostProcessor {
private static final String FICHIER = "config-personnalisee.properties";
private static int compteurAppels = 0;
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
if (compteurAppels++ == 0) {
MutablePropertySources sources = env.getPropertySources();
chargerProprietes(sources, FICHIER);
}
}
private void chargerProprietes(MutablePropertySources sources, String fichier) {
try {
ClassLoader chargeur = Thread.currentThread().getContextClassLoader();
InputStream flux = chargeur.getResourceAsStream(fichier);
if (flux != null) {
Properties props = new Properties();
props.load(flux);
sources.addLast(new PropertiesPropertySource("proprietesDynamiques", props));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}