Annotations de base de SpringBoot et principes de configuration automatique


1. Introduction

        Le principe de configuration automatique de SpringBoot est un casse-tête. Après un après-midi d'étude, j'ai écrit cet article pour votre référence. S'il y a une similitude, c'est purement fortuite. S'il y a une erreur dans l'article, vous êtes invités à le signaler, et je le modifierai à tout moment.
        Version de SpringBoot : 2.7.5 (notez le numéro de version, sinon il peut être incohérent avec les résultats de débogage de l'article).

2. Annotations de base de SpringBoot

@SpringBootApplication
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

        Lorsque nous créons un projet springboot, nous écrirons une classe de démarrage principale. Chaque fois que nous démarrons le projet, nous n'avons qu'à exécuter la méthode main dans la classe de démarrage principale. L'annotation @SpringBootApplication marquée sur la classe de démarrage principale est l'annotation de base de springboot, mais cette annotation est une annotation synthétique, principalement composée des trois annotations suivantes :

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
    
    @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    AutoConfigurationExcludeFilter.class}
)}
)

L'
        annotation @SpringBootConfiguration @SpringBootConfiguration est utilisée pour marquer la classe en tant que classe de configuration. Il a la même fonction que l'annotation @Configuration, la différence est que @SpringBootConfiguration est une annotation dans springboot, tandis que l'annotation @Configuration est une annotation dans spring. Il ressort du code suivant que @SpringBootConfiguration est une annotation dérivée de @Configuration.

@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    
    
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@ComponentScan
        La fonction de l'annotation @ComponentScan est d'analyser toutes les classes du package spécifié et de ses sous-packages, et d'enregistrer les classes remplies de conditions de filtre dans le conteneur Spring en tant que beans. Par défaut, toutes les classes du package où se trouve la classe de démarrage principale et ses sous-packages sont analysées.
@EnableAutoConfiguration
        La fonction de l'annotation @EnableAutoConfiguration est d'activer la fonction de configuration automatique. Avec cette annotation, le programme chargera différentes classes d'AutoConfiguration enregistrées dans le fichier META-INF/spring.factories. Lorsqu'une classe d'AutoConfiguration répond aux conditions effectives spécifiées, le bean défini dans la classe d'AutoConfiguration sera instancié et finalement injecté dans le conteneur Spring. De cette manière, la configuration automatique du framework dépendant est terminée. Les explications de configuration automatique suivantes sont toutes effectuées via cette annotation.

3. Configuration automatique de SpringBoot

@EnableAutoConfiguration :

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({
    
    AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    
    
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {
    
    };

    String[] excludeName() default {
    
    };
}

        L'annotation @EnableAutoConfiguration est principalement composée de @AutoConfigurationPackage et @Import.

3.1 Annotation @AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    
    
}

Classe de registraire :

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
    
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    
    
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
    
    
			return Collections.singleton(new PackageImports(metadata));
		}
	}

        L'annotation @AutoConfigurationPackage utilise l'annotation @Import en bas pour importer le registraire dans le conteneur. Le rôle de la classe Registrar est d'enregistrer le nom du package de configuration automatique sous forme de programmation pour enregistrer l'entrée de l'analyse du package. Quel nom de package est obtenu ?
insérez la description de l'image ici
insérez la description de l'image ici
        Regardons le paramètre metadata dans la méthode registerBeanDefinitions (ce paramètre représente les métadonnées d'annotation), qui inclut des informations telles que l'emplacement de l'annotation, et l'emplacement de l'annotation est sur la classe de démarrage, donc le nom du package obtenu est le package où se trouve la classe de démarrage. En regardant la figure ci-dessus, le résultat calculé par le code est le package où se trouve la classe de démarrage principale.
        Beaucoup de gens ont des questions ici. L'annotation sur la classe de démarrage est @SpringbootApplication. Pourquoi puis-je obtenir le nom du package ? En fait, la position de l'annotation dans les métadonnées des métadonnées fait référence à la position @AutoConfigurationPackage, et @SpringbootApplication est une annotation synthétique, qui inclut l'annotation @EnableAutoConfiguration, et l'annotation @EnableAutoConfiguration inclut l'annotation @AutoConfigurationPackage, donc le nom du package dans les métadonnées est la classe de démarrage principale Le nom du package.

3.2 @Importation des annotations

        Examinons la deuxième annotation importante @Import dans l'annotation @EnableAutoConfiguration. Sa fonction est d'importer la classe AutoConfigurationImportSelector dans le conteneur en tant que composant. Ensuite, analysons la classe AutoConfigurationImportSelector.

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
    
		if (!isEnabled(annotationMetadata)) {
    
    
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

        Quels composants sont dans le tableau renvoyé par la méthode selectImports, alors le programme importera quels composants. Les informations sur les composants sont acquises via la méthode getAutoConfigurationEntry.

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    
    
		if (!isEnabled(annotationMetadata)) {
    
    
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

        En analysant la méthode getAutoConfigurationEntry, nous pouvons voir que cette méthode doit encore appeler la méthode getCandidateConfigurations pour obtenir des données, puis revenir à la méthode selectImports après une série d'opérations telles que la suppression des composants en double. Ensuite, nous continuons à analyser la méthode getCandidateConfigurations .

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    
    
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
    
    
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    
    
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
    
    
            return result;
        } else {
    
    
            Map<String, List<String>> result = new HashMap();
            try {
    
    
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
    
    
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
    
    
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
    
    
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
    
    
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

        Pour ne pas dire de bêtises, collez simplement tous les codes et analysez-les ensemble. La logique générale est que la méthode getCandidateConfigurations appelle la méthode loadFactoryNames, puis la méthode loadFactoryNames appelle la méthode loadSpringFactories.Nous pouvons analyser directement loadSpringFactories.
        Nous pouvons savoir à partir de la ligne 8 de la méthode loadSpringFactories que l'acquisition des composants est obtenue en scannant spring.factories sous le dossier META-INF.
Résultat :
fichier spring.factories :
insérez la description de l'image ici
Nombre de configurations :
insérez la description de l'image ici
        Hahaha, avez-vous trouvé un nouveau problème. . . Vous avez raison, loin de 144 dans le fichier spring.factories. Après mes efforts, j'ai enfin trouvé le problème. Dans la version springboot2.7.5, la configuration automatique qui doit être chargée a été placée dans META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports au lieu d'être écrite dans le fichier spring.factories.
Texte original du site officiel :
insérez la description de l'image ici
insérez la description de l'image ici
        Il y a exactement 144 classes de configuration qui doivent être chargées dans ce fichier.

4. Activez la configuration automatique si nécessaire

        Ce qui précède explique comment springboot charge toutes les classes de configuration automatiques, mais en fait, toutes les classes de configuration automatiques ne sont pas chargées, mais sont chargées à la demande. Comment charger à la demande, nous donnons deux exemples.

4.1 Prendre AopAutoConfiguration comme exemple

@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    
    
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {
    
    
		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
		static class JdkDynamicAutoProxyConfiguration {
    
    
		}
		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {
    
    
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {
    
    

		@Bean
		static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
    
    
			return (beanFactory) -> {
    
    
				if (beanFactory instanceof BeanDefinitionRegistry) {
    
    
					BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
				}
			};
		}

	}
}

        L'annotation @ConditionalOnProperty est ajoutée à la classe AopAutoConfiguration, indiquant que si spring.aop.auto=true existe dans le fichier de configuration, alors la classe de configuration sera chargée, et matchIfMissing = true signifie que la classe de configuration prendra également effet même si le fichier de configuration n'est pas configuré.
        La 5e ligne de code ci-dessus (@ConditionalOnClass(Advice.class)) signifie que si la classe Advice existe, la classe de configuration AspectJAutoProxyingConfiguration prendra effet ; la 20e ligne de code ci-dessus (@ConditionalOnMissingClass("org.aspectj.weaver.Advice" )) signifie Conseil Si la classe n'existe pas, la classe de configuration ClassProxyingConfiguration prend effet.
        On peut voir que lorsque le package jar lié à aspectj n'est pas importé, AOP dans springboot utilise le proxy dynamique JDK à implémenter par défaut.

4.2 Prendre BatchAutoConfiguration comme exemple

@AutoConfiguration(after = HibernateJpaAutoConfiguration.class)
@ConditionalOnClass({
    
     JobLauncher.class, DataSource.class })
@ConditionalOnBean(JobLauncher.class)
@EnableConfigurationProperties(BatchProperties.class)
@Import({
    
     BatchConfigurerConfiguration.class, DatabaseInitializationDependencyConfigurer.class })
public class BatchAutoConfiguration {
    
    
}

        Comme dans le code ci-dessus (les deuxième et troisième lignes de code), la classe de configuration ne prendra effet que lorsque la classe JobLauncher existe et que le bean correspondant au JobLauncher existe dans le conteneur, sinon la configuration ne sera pas activée.
        Résumé : Dans Springboot, toutes les classes de configuration sont chargées par défaut et les classes de configuration automatiques à la demande sont activées. La fonction de configuration automatique est réalisée via une série d'annotations telles que @ConditionalOnClass, @ConditionalOnProperty et @ConditionalOnMissingClass.

5. Résumé

  • L'annotation principale de SpringBoot est @SpringBootApplication, mais cette annotation est une annotation composite, principalement composée de trois annotations : @SpringBootConfiguration, @EnableAutoConfiguration et @ComponentScan.
  • La configuration automatique de SpringBoot est réalisée en analysant le fichier spring.factories sous le dossier META-INF dans spring-boot-configuration, puis en chargeant la classe de configuration correspondante.
  • La configuration automatique de SpringBoot ne charge pas toutes les classes de configuration, mais se configure à la demande, principalement via une série d'annotations telles que @ConditionalOnClass, @ConditionalOnProperty et @ConditionalOnMissingClass.

Remarque : dans les versions précédentes, SpringBoot analysait le fichier spring.factories sous le dossier META-INF, qui a été remplacé par org.springframework.boot sous le dossier spring dans SpringBoot2.7.5 (je ne sais pas à partir de quelle version). fichier .imports.

Référence : [Silicon Valley] Tutoriel d'introduction à SpringBoot2 Zero-Basic

Je suppose que tu aimes

Origine blog.csdn.net/m0_73845616/article/details/127948559
conseillé
Classement