Принцип загрузки автоматической настройки Spring Boot Starter

Spring Boot

Автоконфигурация Spring Boot Starter — очень мощная вещь, а также важная и умная конструкция. Но если вы не понимаете, как это работает, может возникнуть путаница при определении того, какая автоматическая настройка будет включена. Давайте посмотрим на это с точки зрения исходного кода.

Начало работы: концепции автоконфигурации

Автоконфигурация и конфигурация Java Spring — это не одно и то же. Подробнее см. определение в документации Spring Boot.

Часть 0: процесс запуска приложения Spring Boot

ВидетьСправочная статья.

Часть 1: Знакомство с spring.factories

Начните с @SpringBootApplication

@SpringBootApplicationАннотации определяются следующим образом:

// 前面略
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

Первый взгляд@ComponentScanраздел, используяAutoConfigurationExcludeFilterВ качестве фильтра исключения сканирование пакетов буквально исключает автоматически сконфигурированные пакеты. Таким образом, если Spring считает класс автоматически сконфигурированным, он не будет сканировать его (для создания объявленных bean-компонентов). Конкретная логика далее обсуждаться не будет.

Дело в том, что@EnableAutoConfigurationЭта аннотация:

// 省略不重要的内容
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

обрати внимание на@Import(AutoConfigurationImportSelector.class)Это предложение.@ImportМы все с ним знакомы, он в конфигурации XML<import>заменять. тем не мениеAutoConfigurationImportSelectorЭтот класс не@ConfigurationМодификатор, а не класс конфигурации Java. Так что это странно.

При просмотре исходного кода вы должны сначала просмотреть комментарии.

Посмотрим@ImportЧто показывают комментарии:

Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes

Привет! Оказывается, он может не только импортировать классы конфигурации Java, но и обрабатыватьImportSelectorиImportBeanDefinitionRegistrar.

В центре внимания этого разделаImportSelectorПеред этим также необходимо упомянуть@AutoConfigurationPackage:

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.
 */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

иAutoConfigurationPackages.Registrarровно одинImportBeanDefinitionRegistrar, она имеетregisterBeanDefinitionsМетод интерфейса может напрямую зарегистрировать Bean. Позже (расширенное чтение) вы можете увидеть, что,ImportBeanDefinitionRegistrarЧто более важно, это герой, стоящий за различными аннотациями «@EnableXX».

Хорошо, посмотри еще разImportSelectorинтерфейс:

// 省略不重要注释
/**
 * Interface that determine which @{@link Configuration} class(es) should be imported 
 * based on a given selection criteria, usually one or more annotation attributes.
 */
public interface ImportSelector {
    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

Поэтому он используется для определения@ConfigurationДекорированный класс конфигурации Java, конфигурация которого должна быть импортирована. На основе всей аннотационной информации этого класса конфигурации Java (хранящейся вAnnotationMetadataв).

Хорошо, следующий шаг, давайте посмотримAutoConfigurationImportSelectorКак добиться.

Класс AutoConfigurationImportSelector

такая параImportSelectorРеализация интерфейса следующая:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        	return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
    	.loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
    		annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

один из нихgetAutoConfigurationEntryметод вызывается сноваgetCandidateConfigurationsметод, заключающийся в следующем:

/**
 * Return the auto-configuration class names that should be considered. By default
 * this method will load candidates using {@link SpringFactoriesLoader}
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    	getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
    	+ "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

Итак, чтобы добраться сюда, он начнет с пакетаMETA-INF/spring.factoriesПрочитайте классы автоконфигурации, которые необходимо импортировать из файла. Более подробный код ниже уже не выложен, его можно посмотреть в деталяхSpringFactoriesLoaderэтот класс.

Следующий вопрос,selectImportsКем это было вызвано? Честно говоря, ссылка звонка слишком глубокая, и в ней легко запутаться. Ниже кратко описан основной процесс.

Часть 2: процесс загрузки класса конфигурации Java Spring

Начните с ConfigurationClassPostProcessor

В качестве постпроцессора Spring функция этого класса заключается в обработке классов конфигурации Java, украшенных @Configuration. Сосредоточьтесь на его основном подходе:

// 省略非关键代码
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ConfigurationClassParser parser = new ConfigurationClassParser(
    	this.metadataReaderFactory, this.problemReporter, this.environment,
    	this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    parser.parse(candidates);
    parser.validate();
    
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    // Read the model and create bean definitions based on its content
    if (this.reader == null) {
    	this.reader = new ConfigurationClassBeanDefinitionReader(
    		registry, this.sourceExtractor, this.resourceLoader, this.environment,
    		this.importBeanNameGenerator, parser.getImportRegistry());
    }
    this.reader.loadBeanDefinitions(configClasses);
}

Ключевая логика заключается в вызовеConfigurationClassParserКатегорияparseметод, а затем анализирует всеConfigurationClass, анализирует и загружает внутренне определенные bean-компоненты.

ConfigurationClassParser#parse()

Метод parse() в основном вызываетсяprocessConfigurationClassметод.

// 省略非核心代码
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    	return;
    }
    
    // Recursively process the configuration class and its superclass hierarchy.
    SourceClass sourceClass = asSourceClass(configClass);
    do {
    	sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    
    this.configurationClasses.put(configClass, configClass);
}

Этот метод делает три вещи:

  1. Определите, следует ли пропустить этот класс Java Config на этапе PARSE_CONFIGURATION (в соответствии с условным суждением).
  2. перечислитьdoProcessConfigurationClass()сделать конкретный анализ
  3. Поместите класс конфигурации для параметров метода вconfigurationClassesсередина

И второй шагdoProcessConfigurationClass()Что такое конкретный анализ? На самом деле это рекурсивный обход различных @Imports, внутренних классов, объявленных @ComponentScan и т. д. текущего класса конфигурации Java.parse()метод (илиprocessConfigurationClass()метод), разрешите этот класс. Конечным результатом является добавление всех классов конфигурации Java, которые можно найти.configurationClassesв контейнере.

Часть 3: снова посмотрите на процесс загрузки @SpringBootApplication

Поскольку @SpringBootApplication на самом деле является классом конфигурации Java с @Configuration, из части II известно, что он также будет анализироваться таким же образом.

И аннотации, содержащиеся в @SpringBootApplication, наконец, импортированы.AutoConfigurationImportSelectorкласс, поэтому этот класс будет называться. Ниже приведен вызов этого классаgetCandidateConfigurationsСкриншот результата при использовании метода (упомянутого в первой части):

spring.factories结果

На этом этапе все классы автоконфигурации, определенные в Spring Boot Starter, будут просканированы и проанализированы.

Добавлено: реализация @Conditional

В части 2 упоминалось, чтоprocessConfigurationClassПервая строка этого метода — определить, выполняется ли условное условие. Когда вам нужно отладить, почему автоматическая настройка действует/не действует, вы можете сосредоточиться на этом.

Суммировать:

@Import используется для импорта классов конфигурации, а методы импорта в основном делятся на следующие три типа.

  1. Непосредственно импортируйте класс конфигурации, будьте@ConfigurationМодифицированный класс.
  2. ImportSelectorКласс реализации интерфейса возвращает массив имен классов конфигурации, а затем импортирует эти классы конфигурации.
  3. ImportBeanDefinitionRegistarКласс реализации интерфейса, зарегистрировать бин непосредственно в методе интерфейса.

ImportSelectorКласс реализации интерфейсаAutoConfigurationImportSelectorЗатем контракт с каждым стартером в ClassPathMETA-INF/spring.factoriesРабота по чтению классов автоконфигурации, которые необходимо импортировать из файла.

@SpringBootApplicationАннотации наследуются косвенноAutoConfigurationImportSelectorфункция.

В дальнейшем чтении вы сможете увидеть, как работают различные аннотации @Enable*.

использованная литература