Как SpringBoot реализует автоматическую настройку? Исходный код SpringBoot (4)

Spring Boot
Как SpringBoot реализует автоматическую настройку? Исходный код SpringBoot (4)

Примечание. Анализ исходного кода соответствует версии SpringBoot 2.1.0.RELEASE.

1. Введение

Эта статья продолжаетConditionalOnXXX Анализ условных аннотаций для помощи в автоматической настройке SpringBoot — исходный код SpringBoot (3)

После рассмотрения старого и изучения нового давайте кратко рассмотрим содержание предыдущей статьи. В предыдущей статье мы проанализировали соответствующий исходный код условной аннотации SpringBoot @ConditionalOnXxx. Ключевые моменты резюмируются следующим образом:

  1. Весь SpringBoot@ConditionalOnXxxкласс состоянияOnXxxConditionнаследуются отSpringBootConditionбазовый класс, в то время какSpringBootConditionпонял сноваConditionинтерфейс.
  2. SpringBootConditionБазовый класс в основном используется для печати журналов некоторых отчетов об оценке условных аннотаций, и вся эта информация об условной оценке поступает из класса условий аннотаций его подкласса.OnXxxCondition, поэтому он также абстрагирует метод шаблонаgetMatchOutcomeПодклассам остается реализовать оценку того, подходят ли их условные аннотации.
  3. В предыдущей статье у нас также есть важное знание, которое еще не было проанализировано и которое связано с логикой фильтрации классов автоматической конфигурации.AutoConfigurationImportFilterИнтерфейс, давайте заполним эту яму в этой статье.

Ранее мы анализировали встроенные аннотации условий, которые тесно связаны с автоматической настройкой SpringBoot.@ConditionalOnXxxПосле этого мы начнем получать соответствующий исходный код автоматической настройки SpringBoot.

2 @SpringBootApplication аннотация

Прежде чем мы начнем, давайте подумаем, почему SpringBoot помечен@SpringBootApplicationАннотированный класс запуска выполняет простуюrunМетод может реализовать большое количество SpringBootStarterА автоматическая настройка? На самом деле, автоматическая конфигурация SpringBoot такая же, как@SpringBootApplicationЭта аннотация связана, давайте сначала посмотрим на исходный код этой аннотации:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration 
@ComponentScan(excludeFilters = { 
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...省略非关键代码
}

@SpringBootApplicationОтмечено много аннотаций, мы видим, что есть одна аннотация, связанная с автоматической настройкой SpringBoot, а именно@EnableAutoConfiguration, так что автоматическая настройка SpringBoot обязательно должна следовать@EnableAutoConfigurationтесно связаны (что@ComponentScanаннотированныйexcludeFiltersСвойства также имеют классAutoConfigurationExcludeFilter, Этот класс также имеет некоторое отношение к автоматической настройке, но он не является объектом нашего внимания). Теперь давайте откроем@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 {};
}

Видеть@EnableAutoConfigurationАннотация отмечена@AutoConfigurationPackageи@Import(AutoConfigurationImportSelector.class)Две аннотации, как следует из названия,@AutoConfigurationPackageАннотация определенно связана с автоматически настроенным пакетом, иAutoConfigurationImportSelectorЭто связано с автоматическим импортом выбора конфигурации SpringBoot (в SpringImportSelectorиспользуется для импорта классов конфигурации, обычно на основе некоторых условных аннотаций@ConditionalOnXxxxрешить, следует ли импортировать класс конфигурации).

Следовательно, можно видеть, чтоAutoConfigurationImportSelectorКласс находится в центре внимания этой статьи, потому что автоматическая конфигурация SpringBoot должна иметь класс конфигурации, и импорт этого класса конфигурации должен полагаться наAutoConfigurationImportSelectorЭтот чувак делает это.

Далее мы сосредоточимся наAutoConfigurationImportSelectorПосле этого класса, давайте кратко проанализируем его.@AutoConfigurationPackageЛогика этой аннотации.

3 Как найти метод входа в логику реализации автоматической конфигурации SpringBoot?

Совершенно очевидно, что логика автоматической настройки SpringBoot должна быть связана с классом AutoConfigurationImportSelector, так как же нам найти метод входа для логики реализации автоматической настройки SpringBoot?

Прежде чем искать метод входа в логику реализации автоматической конфигурации SpringBoot, давайте посмотримAutoConfigurationImportSelectorСоответствующие диаграммы классов, хорошо иметь общее представление. Посмотрите на картинку ниже:

Рисунок 1

можно увидетьAutoConfigurationImportSelectorСуть в том, чтобы добитьсяDeferredImportSelectorинтерфейс и различныеAwareинтерфейс, затемDeferredImportSelectorИнтерфейс снова наследуетImportSelectorинтерфейс.

Естественно, мы обратим внимание наAutoConfigurationImportSelectorкопироватьDeferredImportSelectorРеализация интерфейсаselectImportsметод, потому чтоselectImportsЭтот метод связан с импортом класса автоматической конфигурации, и этот метод часто является методом входа в выполнение программы. Обнаружено после отладкиselectImportsМетод очень запутанный,selectImportsМетод чем-то связан с логикой, связанной с автоматической настройкой, но не связан по существу.

В настоящее время развитие сюжета не соответствует здравому смыслу.Как мы можем найти метод входа, связанный с логикой автоматической конфигурации?

Самый простой способ -AutoConfigurationImportSelectorПоставьте точку останова на каждый метод класса, а затем выполните отладку, чтобы увидеть, какой метод выполняется первым. Но мы не можем этого сделать, напомним, настроитьStarterмы собираемсяspring.factoriesНастраивается в конфигурационном файле

EnableAutoConfiguration=XxxAutoConfiguration

Следовательно, можно сделать вывод, что принцип автоматической настройки SpringBoot должен следоватьspring.factoriesЭто связано с загрузкой классов автоматической конфигурации в конфигурационный файл, поэтому комбинируйтеAutoConfigurationImportSelectorаннотации метода, мы нашлиgetAutoConfigurationEntryметод. Таким образом, мы ставим точку останова в этом методе, в это время мы можем видеть, где находится метод входа верхнего уровня, вызывая кадр стека, а затем начинаем анализ с метода входа более высокого уровня, связанного с автоматической настройкой.

фигура 2

На рисунке 1 мы видим, что метод входа, связанный с логикой автоматической настройки, находится вDeferredImportSelectorGroupingКатегорияgetImportsметод, поэтому мы начинаем сDeferredImportSelectorGroupingКатегорияgetImportsметод для начала анализа исходного кода автоконфигурации SpringBoot.

4 автоматический анализ конфигурации Принцип SpringBoot

так как нашелConfigurationClassParser.getImports()方法Это метод входа, связанный с автоматической настройкой, тогда давайте действительно проанализируем исходный код автоматической настройки SpringBoot.

Первый взглядgetImportsКод метода:

// ConfigurationClassParser.java

public Iterable<Group.Entry> getImports() {
    // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    	// 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
    	this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    			deferredImport.getImportSelector());
    }
    // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
    return this.group.selectImports();
}

отметка【1】Код at — это тот, который мы проанализироваливысший приоритетБольшая часть логики, связанной с автоматической настройкой, находится здесь, а углубленный анализ основной логики автоматической настройки проанализирован в 4.1. Такthis.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); главное сделатьthis.groupкоторыйAutoConfigurationGroupобъектprocessметод, входящийAutoConfigurationImportSelectorОбъект, чтобы выбрать некоторые классы автоконфигурации, которые соответствуют условиям, и отфильтровать некоторые классы автоконфигурации, которые не соответствуют условиям, вот и все, ничего больше.

Примечание:

  1. AutoConfigurationGroup:ДаAutoConfigurationImportSelectorВнутренний класс в основном используется для обработки логики, связанной с автоматической настройкой.processиselectImportsметод, а затемentriesиautoConfigurationEntriesСвойства коллекции, эти две коллекции соответственно хранят обработанные подходящие классы автоконфигурации, мы знаем, что этого достаточно;
  2. AutoConfigurationImportSelector: отвечает за большую часть логики автоматической настройки, отвечает за выбор некоторых подходящих классов автоматической настройки;
  3. metadata: отмечено в классе запуска SpringBoot@SpringBootApplicationМетаданные аннотации

отметка【2】изthis.group.selectImportsМетод в основном направлен на предыдущееprocessКласс автоматической конфигурации, обработанный методом, далее выборочно импортируется, что будет дополнительно проанализировано в разделе 4.2 Выборочный импорт классов автоматической конфигурации.

4.1 Анализ основной логики автоматической настройки

Здесь мы продолжаем углубляться в предыдущий раздел 4, чтобы проанализировать принцип автоматической настройки SpringBoot.【1】гдеthis.group.processКак метод обрабатывает логику, связанную с автоматической настройкой.

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata,
		DeferredImportSelector deferredImportSelector) {
	Assert.state(
			deferredImportSelector instanceof AutoConfigurationImportSelector,
			() -> String.format("Only %s implementations are supported, got %s",
					AutoConfigurationImportSelector.class.getSimpleName(),
					deferredImportSelector.getClass().getName()));
	// 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
			.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
					annotationMetadata);
	// 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
	this.autoConfigurationEntries.add(autoConfigurationEntry); 
	// 【3】,遍历刚获取的自动配置类
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		// 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
		this.entries.putIfAbsent(importClassName, annotationMetadata); 
	}
}

В приведенном выше коде давайте посмотрим на стандартный【1】МетодыgetAutoConfigurationEntry, этот метод в основном используется для получения класса автоконфигурации и выполняет основную логику автоконфигурации. Перейдите непосредственно к коду:

// AutoConfigurationImportSelector.java

// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(
		AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	// 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
	// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
	// 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 【1】得到spring.factories文件配置的所有自动配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata,
			attributes);
	// 利用LinkedHashSet移除重复的配置类
	configurations = removeDuplicates(configurations);
	// 得到要排除的自动配置类,比如注解属性exclude的配置类
	// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
	// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
	checkExcludedClasses(configurations, exclusions);
	// 【2】将要排除的配置类移除
	configurations.removeAll(exclusions);
	// 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
	// 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分析一下
	configurations = filter(configurations, autoConfigurationMetadata);
	// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
	// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
	// 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
	fireAutoConfigurationImportEvents(configurations, exclusions);
	// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
	return new AutoConfigurationEntry(configurations, exclusions); 
}

AutoConfigurationEntryГлавное, что делает этот метод, — это получить подходящие классы автоконфигурации, чтобы избежать загрузки ненужных классов автоконфигурации и не тратить память. Мы резюмируем нижеAutoConfigurationEntryГлавное, что делает метод:

【1】отspring.factoriesзагрузить в файл конфигурацииEnableAutoConfigurationКласс автоматической настройки (обратите внимание, что на этот раз оттайникПолученный класс автоматической конфигурации показан на рисунке 3. Здесь мы знаем, что делает этот метод, и позже будет статья с его подробным описанием.spring.factoriesпринцип;

【2】Если@EnableAutoConfigurationПодождите, пока аннотация будет отмеченаexcludeКласс автоматической настройки, затем исключите этот класс автоматической настройки;

【3】ИсключитьexcludeПосле класса автоконфигурации, а затем вызовитеfilterМетод выполняет дальнейшую фильтрацию и снова исключает некоторые классы автоконфигурации, не соответствующие условиям;Это будет подробно проанализировано позже.

[4] После повторной фильтрации снова запустить в это времяAutoConfigurationImportEventсобытие, расскажиConditionEvaluationReportОбъект репортера оценки условий для записи подходящих классов автоконфигурации (это подробно анализируется в разделе 6 AutoConfigurationImportListener).

[5] Наконец, верните классы автоматической конфигурации, соответствующие условиям.

изображение 3

В итогеAutoConfigurationEntryПосле основной логики метода давайте рассмотрим подробнееAutoConfigurationImportSelectorизfilterметод:

// AutoConfigurationImportSelector.java

private List<String> filter(List<String> configurations,
			AutoConfigurationMetadata autoConfigurationMetadata) {
	long startTime = System.nanoTime();
	// 将从spring.factories中获取的自动配置类转出字符串数组
	String[] candidates = StringUtils.toStringArray(configurations);
	// 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
	boolean[] skip = new boolean[candidates.length];
	boolean skipped = false;
	// getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
	// 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
		// 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
		// 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
		invokeAwareMethods(filter);
		// 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
		// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
		// 注意candidates数组与match数组一一对应
		/**********************【主线,重点关注】********************************/
		boolean[] match = filter.match(candidates, autoConfigurationMetadata);
		// 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
		for (int i = 0; i < match.length; i++) {
			// 若有不匹配的话
			if (!match[i]) {
				// 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一对应
				skip[i] = true;
				// 因为不匹配,将相应的自动配置类置空
				candidates[i] = null;
				// 标注skipped为true
				skipped = true; 
			}
		}
	} 
	// 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
	if (!skipped) {
		return configurations;
	}
	// 建立result集合来装匹配的自动配置类
	List<String> result = new ArrayList<>(candidates.length); 
	for (int i = 0; i < candidates.length; i++) {
		// 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
		if (!skip[i]) { 
			result.add(candidates[i]);
		}
	}
	// 打印日志
	if (logger.isTraceEnabled()) {
		int numberFiltered = configurations.size() - result.size();
		logger.trace("Filtered " + numberFiltered + " auto configuration class in "
				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
				+ " ms");
	}
	// 最后返回符合条件的自动配置类
	return new ArrayList<>(result);
}

AutoConfigurationImportSelectorизfilterГлавное, что делает метод, это вызываетAutoConfigurationImportFilterинтерфейсmatchметод для определения условных аннотаций (если они есть) для каждого автоматически настроенного класса@ConditionalOnClass,@ConditionalOnBeanили@ConditionalOnWebApplicationСоблюдено ли условие, если да, верните true, указав совпадение, если нет, верните false, чтобы указать несоответствие.

теперь мы знаемAutoConfigurationImportSelectorизfilterМетод в основном делает то, что делает, и сейчас нам не нужно его слишком глубоко изучать.AutoConfigurationImportFilterинтерфейсmatchЭтот метод будет подробно проанализирован в разделе 5 AutoConfigurationImportFilter, чтобы заполнить пробел, оставленный в нашем предыдущем анализе исходного кода условной аннотации.

Примечание. Мы придерживаемся принципа основной строки в первую очередь, а другой побочный код здесь не обсуждается, чтобы не потерять основную строку.

4.2 Выборочный импорт классов автоконфигурации

Здесь мы продолжаем углубляться в предыдущий раздел 4, чтобы проанализировать принцип автоматической настройки SpringBoot.【2】гдеthis.group.selectImportsМетод заключается в том, как дополнительно выборочно импортировать классы автоконфигурации. Посмотрите прямо на код:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

public Iterable<Entry> selectImports() {
	if (this.autoConfigurationEntries.isEmpty()) {
		return Collections.emptyList();
	} 
	// 这里得到所有要排除的自动配置类的set集合
	Set<String> allExclusions = this.autoConfigurationEntries.stream()
			.map(AutoConfigurationEntry::getExclusions)
			.flatMap(Collection::stream).collect(Collectors.toSet());
	// 这里得到经过滤后所有符合条件的自动配置类的set集合
	Set<String> processedConfigurations = this.autoConfigurationEntries.stream() 
			.map(AutoConfigurationEntry::getConfigurations)
			.flatMap(Collection::stream)
			.collect(Collectors.toCollection(LinkedHashSet::new));
	// 移除掉要排除的自动配置类
	processedConfigurations.removeAll(allExclusions); 
	// 对标注有@Order注解的自动配置类进行排序,
	return sortAutoConfigurations(processedConfigurations,
			getAutoConfigurationMetadata())
					.stream()
					.map((importClassName) -> new Entry(
							this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
}

можно увидеть,selectImportsМетод в основном направлен на устранениеexcludeот и поAutoConfigurationImportFilterКлассы автоматической конфигурации, удовлетворяющие условиям после фильтрации интерфейса, дополнительно исключаются.excludeЗатем классы автоконфигурации сортируются. Логика очень проста и подробно описываться не будет.

Но есть вопрос, предыдущийexcludeЭто было однажды, почему ты снова здесьexcludeоднажды?

5 AutoConfigurationImportFilter

Здесь мы продолжаем углубляться в предыдущий раздел 4.1.AutoConfigurationImportSelector.filterметод фильтрации классов автоконфигурацииboolean[] match = filter.match(candidates, autoConfigurationMetadata);этот код.

Итак, продолжаем анализAutoConfigurationImportFilterинтерфейс, анализировать егоmatchметод, но и к предыдущему@ConditionalOnXxxЯмы, оставленные в статье анализа исходного кода, заполнены.

AutoConfigurationImportFilterтолько один интерфейсmatchМетод используется для фильтрации автоматически настроенных классов, которые не соответствуют условиям.

@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(String[] autoConfigurationClasses,
    		AutoConfigurationMetadata autoConfigurationMetadata);
}

Так же при анализеAutoConfigurationImportFilterинтерфейсmatchПеред методом давайте взглянем на его диаграмму классов:

Рисунок 4

можно увидеть,AutoConfigurationImportFilterИнтерфейс имеет конкретный класс реализацииFilteringSpringBootCondition,FilteringSpringBootConditionЕсть также три конкретных подкласса:OnClassCondition,OnBeanCondtitionиOnWebApplicationCondition.

Итак, какова связь между этими классами?

FilteringSpringBootConditionДостигнутоAutoConfigurationImportFilterинтерфейсmatchметод, то вFilteringSpringBootConditionизmatchвызов методаgetOutcomesЭтот абстрактный метод шаблона возвращает информацию о том, соответствует ли автоматически сконфигурированный класс. При этом, самое главноеFilteringSpringBootConditionтри подклассаOnClassCondition,OnBeanCondtitionиOnWebApplicationConditionЭтот метод шаблона будет переопределен для реализации собственной логики совпадающих оценок.

хорошо,AutoConfigurationImportFilterОбщая взаимосвязь интерфейса была ясна, теперь мы вводим его конкретный класс реализации.FilteringSpringBootConditionизmatchметод, чтобы увидеть, как он автоматически настраивает классы на основе условной фильтрации.

// FilteringSpringBootCondition.java

@Override
public boolean[] match(String[] autoConfigurationClasses,
		AutoConfigurationMetadata autoConfigurationMetadata) {
	// 创建评估报告
	ConditionEvaluationReport report = ConditionEvaluationReport
			.find(this.beanFactory);
	// 注意getOutcomes是模板方法,将spring.factories文件种加载的所有自动配置类传入
	// 子类(这里指的是OnClassCondition,OnBeanCondition和OnWebApplicationCondition类)去过滤
	// 注意outcomes数组存储的是不匹配的结果,跟autoConfigurationClasses数组一一对应
	/*****************************【主线,重点关注】*********************************************/
	ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
			autoConfigurationMetadata);
	boolean[] match = new boolean[outcomes.length];
	// 遍历outcomes,这里outcomes为null则表示匹配,不为null则表示不匹配
	for (int i = 0; i < outcomes.length; i++) {
		ConditionOutcome outcome = outcomes[i];
		match[i] = (outcome == null || outcome.isMatch());
		if (!match[i] && outcomes[i] != null) {
			// 这里若有某个类不匹配的话,此时调用父类SpringBootCondition的logOutcome方法打印日志
			logOutcome(autoConfigurationClasses[i], outcomes[i]);
			// 并将不匹配情况记录到report
			if (report != null) {
				report.recordConditionEvaluation(autoConfigurationClasses[i], this,
						outcomes[i]);
			}
		}
	}
	return match;
}

FilteringSpringBootConditionизmatchГлавное, что делает метод, это вызывает метод абстрактного шаблонаgetOutcomesфильтровать классы автоконфигурации на основе условий, переопределяяgetOutcomesСуществует три подкласса шаблонных методов, которые не будут анализироваться здесь по отдельности.выбрать толькоOnClassConditionдублируетсяgetOutcomesметод для анализа.

5.1 OnClassCondition

Сначала идите прямоOnClassConditionдублируетсяgetOutcomesКод метода:

// OnClassCondition.java

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
		AutoConfigurationMetadata autoConfigurationMetadata) {
	// Split the work and perform half in a background thread. Using a single
	// additional thread seems to offer the best performance. More threads make
	// things worse
	// 这里经过测试用两个线程去跑的话性能是最好的,大于两个线程性能反而变差
	int split = autoConfigurationClasses.length / 2;
	// 【1】开启一个新线程去扫描判断已经加载的一半自动配置类
	OutcomesResolver firstHalfResolver = createOutcomesResolver(
			autoConfigurationClasses, 0, split, autoConfigurationMetadata);
	// 【2】这里用主线程去扫描判断已经加载的一半自动配置类
	OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
			autoConfigurationClasses, split, autoConfigurationClasses.length,
			autoConfigurationMetadata, getBeanClassLoader());
	// 【3】先让主线程去执行解析一半自动配置类是否匹配条件
	ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
	// 【4】这里用新开启的线程取解析另一半自动配置类是否匹配
	// 注意为了防止主线程执行过快结束,resolveOutcomes方法里面调用了thread.join()来
	// 让主线程等待新线程执行结束,因为后面要合并两个线程的解析结果
	ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
	// 新建一个ConditionOutcome数组来存储自动配置类的筛选结果
	ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
	// 将前面两个线程的筛选结果分别拷贝进outcomes数组
	System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
	System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
	// 返回自动配置类的筛选结果
	return outcomes;
}

можно увидеть,OnClassConditionизgetOutcomesМетод в основном анализирует, соответствует ли класс автоматической конфигурации условиям соответствия.Конечно, это условие соответствия относится к аннотациям в классе автоматической конфигурации.@ConditionalOnClassСуществует ли указанный класс вclasspath, если он существует, он возвращает совпадение, если нет, возвращает несоответствие.

Поскольку синтаксический анализ соответствия класса автоконфигурации занимает много времени, мы можем видеть из приведенного выше кода, чтоfirstHalfResolverиsecondHalfResolverДва объекта синтаксического анализа, каждый из которых соответствует потоку для анализа того, соответствует ли загруженный класс автоконфигурации условиям, и, наконец, возвращают совпадающие результаты классов синтаксического анализа и автоконфигурации двух объединенных потоков.

Итак, каков процесс разбора и оценки того, соответствует ли класс автоматической конфигурации условиям? Теперь давайте посмотрим на комментарии к коду, отмеченные выше.【1】,【2】,【3】и【4】место.

5.1.1 createOutcomesResolver

Это соответствует аннотации комментария к коду в предыдущем разделе 5.1.【1】гдеOutcomesResolver firstHalfResolver = createOutcomesResolver(...);Методы:

// OnClassCondition.java

private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
		int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
	// 新建一个StandardOutcomesResolver对象
	OutcomesResolver outcomesResolver = new StandardOutcomesResolver(
			autoConfigurationClasses, start, end, autoConfigurationMetadata,
			getBeanClassLoader());
	try {
		// new一个ThreadedOutcomesResolver对象,并将StandardOutcomesResolver类型的outcomesResolver对象作为构造器参数传入
		return new ThreadedOutcomesResolver(outcomesResolver);
	}
	// 若上面开启的线程抛出AccessControlException异常,则返回StandardOutcomesResolver对象
	catch (AccessControlException ex) {
		return outcomesResolver;
	}
}

можно увидетьcreateOutcomesResolverметод создает инкапсуляциюStandardOutcomesResolverКатегорияThreadedOutcomesResolverРазобрать объект. давайте посмотрим еще разThreadedOutcomesResolverЭтот класс разбора потока инкапсулируетStandardOutcomesResolverКакова цель этого объекта? Давайте проследим за кодом:

// OnClassCondtion.java

private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
	// 这里开启一个新的线程,这个线程其实还是利用StandardOutcomesResolver的resolveOutcomes方法
	// 对自动配置类进行解析判断是否匹配
	this.thread = new Thread(
			() -> this.outcomes = outcomesResolver.resolveOutcomes());
	// 开启线程
	this.thread.start();
}

можно увидеть в конструкцииThreadedOutcomesResolverобъект, оказалось, что поток был открыт, а затем этот поток фактически вызвал только что переданный.StandardOutcomesResolverобъектresolveOutcomesметод для разрешения классов автоконфигурации. Как это проанализировать? Мы проанализируем позже【3】кодsecondHalfResolver.resolveOutcomes();время для дальнейшего расследования.

5.1.2 new StandardOutcomesResolver

Это соответствует предыдущему разделу 5.1.【2】код вOutcomesResolver secondHalfResolver = new StandardOutcomesResolver(...);, логика очень проста, это создатьStandardOutcomesResolverОбъект используется для последующего анализа соответствия класса автоматической конфигурации В то же время новый поток также использует его для завершения анализа класса автоматической конфигурации.

5.1.3 Метод StandardOutcomesResolver.resolveOutcomes

Это соответствует отмеченному в предыдущем разделе 5.1.【3】кодConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();.

здесьStandardOutcomesResolver.resolveOutcomesЭтот метод берет на себя всю логику разбора того, соответствует ли класс автоматической конфигурации или нет, и это метод, на котором мы хотим сосредоточиться.resolveOutcomesСпособ, наконец, присваивает результат анализируемого класса автоматического конфигурации дляsecondHalfмножество. Так как же решить, соответствует ли класс автоконфигурации условию?

// OnClassCondition$StandardOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
	// 再调用getOutcomes方法来解析
	return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
			this.autoConfigurationMetadata);
}

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
		int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { // 只要autoConfigurationMetadata没有存储相关自动配置类,那么outcome默认为null,则说明匹配
	ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
	// 遍历每一个自动配置类
	for (int i = start; i < end; i++) {
		String autoConfigurationClass = autoConfigurationClasses[i];
		// TODO 对于autoConfigurationMetadata有个疑问:为何有些自动配置类的条件注解能被加载到autoConfigurationMetadata,而有些又不能,比如自己定义的一个自动配置类HelloWorldEnableAutoConfiguration就没有被存到autoConfigurationMetadata中
		if (autoConfigurationClass != null) {
			// 这里取出注解在AutoConfiguration自动配置类类的@ConditionalOnClass注解的指定类的全限定名,
			// 举个栗子,看下面的KafkaStreamsAnnotationDrivenConfiguration这个自动配置类
			/**
			 * @ConditionalOnClass(StreamsBuilder.class)
			 * class KafkaStreamsAnnotationDrivenConfiguration {
			 * // 省略无关代码
			 * }
			 */
			// 那么取出的就是StreamsBuilder类的全限定名即candidates = org.apache.kafka.streams.StreamsBuilder
			String candidates = autoConfigurationMetadata
					.get(autoConfigurationClass, "ConditionalOnClass"); // 因为这里是处理某个类是否存在于classpath中,所以传入的key是ConditionalOnClass
			// 若自动配置类标有ConditionalOnClass注解且有值,此时调用getOutcome判断是否存在于类路径中
			if (candidates != null) {
				// 拿到自动配置类注解@ConditionalOnClass的值后,再调用getOutcome方法去判断匹配结果,若该类存在于类路径,则getOutcome返回null,否则非null
				/*******************【主线,重点关注】******************/
				outcomes[i - start] = getOutcome(candidates);
			}
		}
	}
	return outcomes;
}

можно увидетьStandardOutcomesResolver.resolveOutcomesметод вызывается сноваgetOutcomesметод, в основном изautoConfigurationMetadataАннотации к классу автоконфигурации получаются из объекта@ConditionalOnClassПолное имя указанного класса, переданное затем в качестве параметраgetOutcomeМетод используется для загрузки класса из пути к классам.Если он может быть загружен, он укажет аннотацию@ConditionalOnClassЕсли условия соблюдены, автоматическое сопоставление классов конфигурации выполнено успешно.

Но не забывай, это только что прошло@ConditionalOnClassАннотировать этот уровень, если класс автоматической конфигурации имеет другие аннотации, такие как@ConditionalOnBean, если@ConditionalOnBeanЕсли аннотация не соответствует условиям, конечный результат также не соответствует. Здесь немного натянуто, давайте вернемсяOnClassCondtionЛогика суждения, продолжайте вводитьgetOutcomeспособ увидеть, как он оценивается@ConditionalOnClassАннотации не соответствуют условиям.

// OnClassCondition$StandardOutcomesResolver.java

// 返回的outcome记录的是不匹配的情况,不为null,则说明不匹配;为null,则说明匹配
private ConditionOutcome getOutcome(String candidates) {
	// candidates的形式为“org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=org.aspectj.lang.annotation.Aspect,org.aspectj.lang.reflect.Advice,org.aspectj.weaver.AnnotatedElement”
	try {// 自动配置类上@ConditionalOnClass的值只有一个的话,直接调用getOutcome方法判断是否匹配
		if (!candidates.contains(",")) {
			// 看到因为传入的参数是 ClassNameFilter.MISSING,因此可以猜测这里应该是得到不匹配的结果
			/******************【主线,重点关注】********************/
			return getOutcome(candidates, ClassNameFilter.MISSING, 
					this.beanClassLoader);
		}
		// 自动配置类上@ConditionalOnClass的值有多个的话,则遍历每个值(其值以逗号,分隔)
		for (String candidate : StringUtils
				.commaDelimitedListToStringArray(candidates)) {
			ConditionOutcome outcome = getOutcome(candidate,
					ClassNameFilter.MISSING, this.beanClassLoader);
			// 可以看到,这里只要有一个不匹配的话,则返回不匹配结果
			if (outcome != null) { 
				return outcome;
			}
		}
	}
	catch (Exception ex) {
		// We'll get another chance later
	}
	return null;
}

можно увидеть,getOutcomeметод снова вызывает перегруженный методgetOutcomeЧтобы дополнительно судить о аннотации@ConditionalOnClassНезависимо от того, существует ли указанный класс в пути к классам, продолжайте следовать основной строке:

// OnClassCondition$StandardOutcomesResolver.java

private ConditionOutcome getOutcome(String className,
		ClassNameFilter classNameFilter, ClassLoader classLoader) {
	// 调用classNameFilter的matches方法来判断`@ConditionalOnClass`指定的类存不存在类路径中
	/******************【主线,重点关注】********************/
	if (classNameFilter.matches(className, classLoader)) { // 这里调用classNameFilter去判断className是否存在于类路径中,其中ClassNameFilter又分为PRESENT和MISSING两种;目前只看到ClassNameFilter为MISSING的调用情况,所以默认为true的话记录不匹配信息;若传入ClassNameFilter为PRESENT的话,估计还要再写一个else分支
		return ConditionOutcome.noMatch(ConditionMessage
				.forCondition(ConditionalOnClass.class)
				.didNotFind("required class").items(Style.QUOTE, className));
	}
	return null;
}

Мы очистили слой за слоем, и наконец-то мы добрались до сути.Это действительно требует достаточного терпения, нет никакого способа, исходный код может быть только по крупицам, хе-хе. Вы можете видеть, что последний вызовClassNameFilterизmatchesметод судить@ConditionalOnClassСуществует ли указанный класс в пути к классам, если он не существует, возвращается несоответствие.

мы продолжаем следитьClassNameFilterИсходный код:

// FilteringSpringBootCondition.java

protected enum ClassNameFilter {
	// 这里表示指定的类存在于类路径中,则返回true
	PRESENT {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			return isPresent(className, classLoader);
		}

	},
	// 这里表示指定的类不存在于类路径中,则返回true
	MISSING {

		@Override
		public boolean matches(String className, ClassLoader classLoader) {
			return !isPresent(className, classLoader); // 若classpath不存在className这个类,则返回true
		}

	};
	// 这又是一个抽象方法,分别被PRESENT和MISSING枚举类实现
	public abstract boolean matches(String className, ClassLoader classLoader);
	// 检查指定的类是否存在于类路径中	
	public static boolean isPresent(String className, ClassLoader classLoader) {
		if (classLoader == null) {
			classLoader = ClassUtils.getDefaultClassLoader();
		}
		// 利用类加载器去加载相应类,若没有抛出异常则说明类路径中存在该类,此时返回true
		try {
			forName(className, classLoader); 
			return true;
		}// 若不存在于类路径中,此时抛出的异常将catch住,返回false。
		catch (Throwable ex) {
			return false;
		}
	}
	// 利用类加载器去加载指定的类
	private static Class<?> forName(String className, ClassLoader classLoader)
			throws ClassNotFoundException {
		if (classLoader != null) {
			return classLoader.loadClass(className);
		}
		return Class.forName(className);
	}

}

можно увидетьClassNameFilterэто былоFilteringSpringBootConditionВнутренний класс перечисления , реализующий определение того, существует ли указанный класс вclasspathЛогика в этом классе очень проста и не будет здесь подробно описываться.

5.1.4 Метод ThreadedOutcomesResolver.resolveOutcomes

Это соответствует аннотации в предыдущем разделе 5.1.【4】кодConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes().

Предыдущий метод анализа 5.1.3 StandardOutcomesResolver.resolveOutcomes был укоренен в корень, и детали относительно глубоки Теперь нам нужно выпрыгнуть и продолжить смотреть на ранее отмеченные【4】кодConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes()метод Ха.

Вот вызов с недавно открытым потокомStandardOutcomesResolver.resolveOutcomesМетод анализирует, соответствует ли другая половина класса автоматической конфигурации, поскольку это новый поток, здесь может быть такая ситуация: после того, как основной поток разберет половину класса автоматической конфигурации, которая принадлежит его собственному анализу, он продолжает бежать вниз в течение длительного времени.Будет ждать только что открытого дочернего потока.

Следовательно, чтобы основной поток завершил синтаксический анализ, нам нужно позволить основному потоку продолжать ждать дочернего потока, который выполняет синтаксический анализ, пока дочерний поток не завершится. Итак, давайте продолжим следить за областью кодаThreadedOutcomesResolver.resolveOutcomesМетод заключается в том, как заставить основной поток ждать дочерний поток:

// OnClassCondition$ThreadedOutcomesResolver.java

public ConditionOutcome[] resolveOutcomes() {
	try {
		// 调用子线程的Join方法,让主线程等待
		this.thread.join();
	}
	catch (InterruptedException ex) {
		Thread.currentThread().interrupt();
	}
	// 若子线程结束后,此时返回子线程的解析结果
	return this.outcomes;
}

можно увидеть б/уThread.join()метод, чтобы заставить основной поток ожидать дочерний поток, который анализирует класс автоконфигурации, который также следует использовать здесьCountDownLatchчтобы основной поток дождался завершения дочернего потока. Наконец, назначьте проанализированный результат дочернего потокаfirstHalfмножество.

5.2 OnBeanCondition и OnWebApplicationCondition

Предыдущий раздел 5.1 OnClassCondition был подробно проанализирован.OnClassConditionКак отфильтровать класс автоматической настройки, тогда класс автоматической настройки должен пройтиOnClassConditionфильтровать, но и пройтиOnBeanConditionиOnWebApplicationConditionФильтрация этих двух классов условий здесь подробно описываться не будет, и заинтересованные партнеры могут проанализировать ее самостоятельно.

6 AutoConfigurationImportListener

Здесь мы продолжаем углубляться в предыдущий раздел 4.1.AutoConfigurationImportSelector.getAutoConfigurationEntryМетод вызывает событие о том, что класс автоматической конфигурации был отфильтрован.fireAutoConfigurationImportEvents(configurations, exclusions);этот код.

мы нажимаем напрямуюfireAutoConfigurationImportEventsметод, чтобы увидеть, как он запускает событие:

// AutoConfigurationImportSelector.java

private void fireAutoConfigurationImportEvents(List<String> configurations,
		Set<String> exclusions) {
	// 从spring.factories总获取到AutoConfigurationImportListener即ConditionEvaluationReportAutoConfigurationImportListener
	List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); 
	if (!listeners.isEmpty()) {
		// 新建一个AutoConfigurationImportEvent事件
		AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
				configurations, exclusions);
		// 遍历刚获取到的AutoConfigurationImportListener
		for (AutoConfigurationImportListener listener : listeners) {
			// 这里调用各种Aware方法用于触发事件前赋值,比如设置factory,environment等
			invokeAwareMethods(listener);
			// 真正触发AutoConfigurationImportEvent事件即回调listener的onXXXEveent方法。这里用于记录自动配置类的评估信息
			listener.onAutoConfigurationImportEvent(event); 
		}
	}
}

Как указано выше,fireAutoConfigurationImportEventsМетод делает две вещи:

  1. перечислитьgetAutoConfigurationImportListenersметод изspring.factorisРеализация получения файла конфигурацииAutoConfigurationImportListenerСлушатель событий интерфейса, как показано на рисунке ниже, вы можете видеть, что полученоConditionEvaluationReportAutoConfigurationImportListener:

  1. Пройдите полученные прослушиватели событий, а затем вызовите различные прослушивателиAwareМетод присваивает значение прослушивателю и, наконец, по очереди вызывает прослушиватель событий.onAutoConfigurationImportEventметод, который выполняет логику для прослушивания событий.

Теперь давайте посмотрим наConditionEvaluationReportAutoConfigurationImportListenerПосле того, как слушатель прослушает событие, егоonAutoConfigurationImportEventЧто именно делает метод:

// ConditionEvaluationReportAutoConfigurationImportListener.java

public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
	if (this.beanFactory != null) {
		// 获取到条件评估报告器对象
		ConditionEvaluationReport report = ConditionEvaluationReport
				.get(this.beanFactory);
		// 将符合条件的自动配置类记录到unconditionalClasses集合中
		report.recordEvaluationCandidates(event.getCandidateConfigurations());
		// 将要exclude的自动配置类记录到exclusions集合中
		report.recordExclusions(event.getExclusions()); 
	}
}

можно увидеть,ConditionEvaluationReportAutoConfigurationImportListenerПосле того, как слушатель прослушает событие, он делает очень просто, просто записывает подходящие иexcludeКласс автоконфигурации.

7 AutoConfigurationPackages

Подробно описан принцип автоматической настройки SpringBoot, и, наконец, аннотации, связанные с автоматической настройкой SpringBoot.@AutoConfigurationPackageЕще не проанализировано, давайте посмотрим на исходный код этой аннотации:

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

можно увидеть@AutoConfigurationPackageАннотация связана с пакетом, в котором SpringBoot настраивается автоматически, а пакет, в котором класс, в который добавлена ​​аннотация, управляется как пакет автоматической конфигурации.

Далее мы смотрим наAutoConfigurationPackages.RegistrarЧто делает класс, смотрите прямо в исходный код:

//AutoConfigurationPackages.Registrar.java

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
    		BeanDefinitionRegistry registry) {
    	register(registry, new PackageImport(metadata).getPackageName());
    }
    
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
    	return Collections.singleton(new PackageImport(metadata));
    }
}

можно увидетьRegistrarклассAutoConfigurationPackagesСтатический внутренний класс, реализующийImportBeanDefinitionRegistrarиDeterminableImportsдва интерфейса. Сейчас мы в основном ориентируемся наRegistrarосуществленныйregisterBeanDefinitionsметод, как следует из названия, этот метод зарегистрированbeanопределенный метод. см., что это называется сноваAutoConfigurationPackagesизregisterметод, продолжайте следовать исходному коду:

// AutoConfigurationPackages.java

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition
				.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0,
				addBasePackages(constructorArguments, packageNames));
	}
	else {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
				packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

Как и выше, вы можете видетьregisterметод регистрируетpackageNamesТо есть аннотации класса автоматической конфигурации@EnableAutoConfigurationИмя пакета, в котором он находится, связано сbean. затем зарегистрируйте этоbeanс какой целью? В сочетании с аннотациями официального веб-сайта зарегистрируйте это имя пакета автоматической конфигурации, связанное сbeanНа него следует ссылаться в других местах, таких какJPA entity scanner, я не знаю, для чего он использовался долгое время, поэтому не буду здесь вникать.

8 Резюме

Что ж, анализ исходного кода автоматической настройки SpringBoot находится здесь, он относительно длинный, а некоторые места и очень подробные, для его чтения требуется определенное количество терпения.

Наконец, давайте обобщим принцип автоматической настройки SpringBoot, в основном выполняя следующие действия:

  1. Загрузите классы автоконфигурации из конфигурационного файла spring.factories;
  2. Исключено из загруженных классов автоконфигурации@EnableAutoConfigurationаннотированныйexcludeКласс автоконфигурации, указанный свойством;
  3. затем используйтеAutoConfigurationImportFilterИнтерфейс для фильтрации, соответствует ли класс автоконфигурации его аннотации аннотации (если есть аннотация)@ConditionalOnClass,@ConditionalOnBeanи@ConditionalOnWebApplicationЕсли все условия соблюдены, будет возвращен соответствующий результат;
  4. затем вызватьAutoConfigurationImportEventсобытие, расскажиConditionEvaluationReportОбъект отчета об оценке условий для записи соответствующих условий иexcludeКласс автоконфигурации.
  5. Наконец, spring импортирует окончательный отфильтрованный класс автоконфигурации в контейнер IOC.

Наконец, оставьте вопрос для себя, и я надеюсь, что большой парень, который знает ответ, сможет ответить на него.Спасибо здесь.:

Чтобы избежать потери памяти, вызванной загрузкой ненужных классов автоконфигурации,FilteringSpringBootConditionдля фильтрацииspring.factoriesкласс автоконфигурации файла, в то время какFilteringSpringBootConditionпочему толькоOnOnBeanCondition,OnClassConditionиonWebApplicationConditionЭти три класса условий используются для фильтрации, почему бы и нет?onPropertyCondtion,onResourceConditionКак насчет условных классов для фильтрации классов автоконфигурации?

Анонс следующего раздела: Каков процесс запуска SpringBoot? -- Исходный код SpringBoot (5)

Оригинальность не так проста, пожалуйста, дайте мне лайк!

В связи с ограниченным уровнем автора, если есть ошибки в тексте, просьба указать, спасибо.

Ссылаться на:

1,Аннотация @AutoConfigurationPackage