SpringBootのソースコード (1) 自動設定

序文

SpringMvc から SpringBoot まではゼロコンフィグレーションを実現しており、デフォルトコンフィグレーションが多いため、SpringBoot の最初の章として自動コンフィグレーションを使用しています。アイデアが明確ではないので、まず自動構成の部分を明確に理解し、その構成の 1 つがどのようにロードされるかを知ると、その後の学習がスムーズになるはずです。

SpringBoot の自動構成は@EnableAutoConfigurationアノテーションによって有効になるため、このアノテーションから始めて、自動構成と条件付きフィルターがどのように実装されるかを見てみましょう。

原理

外部の jar パッケージまたはサードパーティのパッケージも同様にロードされますが、自動構成を実現するには、Spring 仕様 (通常は構成META-INFO/spring.factoriesMETA-INF/spring-autoconfigure-metadata.properties2 つのファイル) に従う必要があります。

Spring の章Spring のソース コード (6) 構成クラスの解析プロセスでは、次のように Spring が構成クラスを通じて Bean をどのように注入するかを学びました。

Spring はそれを構成クラスとして判断し、いくつかのアノテーションがあります@Configuration、@Component、@ComponentScan、@Import、@ImportResource@Bean

Spring の従来の Bean 注入方法は次のとおりです (Spring は beanDefinition から判断します。すべての前提は beanDefinition です)。

  1. @Component/@Configuration

  2. @Component/@Configuration + @Bean

  3. @Component/@Configuration + @CompnentScan

  4. @Component/@Configuration + @Import

  5. @Component/@Configuration + @ImportResource

そうすれば、他の外部クラスを導入する方法が推測できると思います。@ComponentScan外部パッケージ内でスキャンするにはスキャン メソッドを使用できますが、このメソッドはアクティブ スキャンを適用します。このアクティブ メソッドはコンポーネント統合のフレームワークに対して柔軟性がありません。たとえば、サードパーティにインターフェイスを開いた後、そのインターフェイスを呼び出すことができない場合は、このインターフェイスを構成に追加する必要があります。非常に一貫性がないように感じますか?アイデアの仕様に従ってそれを実行し、リリースし、アイデアの公式にプラグインの使用許可を有効にするために通知するためにも使用します。それはとんでもないことですか、誰もあなたのことを気にしません。

したがって、私たちが注意すべきことは、コンポーネントを受動的に受け入れてから、車を組み立てるのと同じようにスキャンして、フレームワーク、メイン制御チップ、エンジンを提供するスプリングを使用して、残りを組み立てることです。

Tomcat では、Tomcat はアプリケーション実装クラスを読み取ってアプリケーションを初期化しますMETA-INF/services/javax.servlet.ServletContainerInitializerが、Spring も同様で、META-INFO/spring.factoriesこのファイルを読み取って設定を読み込みます。

たとえば、mybatis の依存関係パッケージ

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

インポート時に依存するのは、

    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>

2 つの構成クラスが構成されていることがわかります。これら 2 つは、mybatis が自動的に構成できるコアです。画像-20221022195751583

次に SpringBoot の自動構成を見てみましょう。Spring では次の自動構成の依存関係が導入されます。その中には多くの自動構成クラスがあります。基本的に spring-boot の自動構成はすべてここにあります。注意深く見ると、aop が表示されます。 、jpa、MongoDB、kafka、サーブレットなど。

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

画像-20221022231936137

@EnableAutoConfiguration

インポートを解析しています

では、SpringBoot はどのようにして外部パッケージ、またはサードパーティのパッケージをロードするのでしょうか?

SpringBootアプリケーションを起動するとアノテーションが追加されます@SpringBootApplicationが、このアノテーションがキーとなります。

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

SpringApplication.run(App.class);これにより、Web コンテナ、Spring コンテナ、Spring セットが開始されます。

私たちが調べた定義には@SpringBootApplication、その中に 1 つあります@EnableAutoConfiguration。これは自動構成ロード用の注釈です。

画像-20221022233013456

画像-20221022233052400

@EnableAutoConfigurationメモにこんな記載がありました@Import({AutoConfigurationImportSelector.class})

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    
    

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}

アノテーションを通じて、その値を 4 種類のクラスに設定できることがわかります。

  1. 注釈付きの@Configuration構成クラス
  2. ImportSelectorインターフェースを実装するクラス
  3. 実装されたImportBeanDefinitionRegistrarクラス
  4. 共通componentクラス

Spring が起動すると、ioc コンテナが作成され、構成クラス (@SpringBootApplicationスタートアップ クラスとマークされている) が構成クラスとして追加されますConfiguration。これは、他の構成クラスまたは Bean がスキャンされる最初の構成クラスに相当します。構成クラスの場合、存在するかどうかに関係なくimports解析されます。

解析構成クラスの場所: org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

解析importor的位置:org.springframework.context.annotation.ConfigurationClassParser#processImports

このステップの機能は、インポート内の ImportSelector クラスを検索し、Import アノテーション内の beanDefinition レジスタを検索することです。検索するだけで、何も行いません。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
    
    

    // 当配置类没有impots时不再进行
		if (importCandidates.isEmpty()) {
    
    
			return;
		}
    // 判断是否循环引入
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    
    
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
    
    
            // 标记正在import的配置类
			this.importStack.push(configClass);
			try {
    
    
				for (SourceClass candidate : importCandidates) {
    
    
                    
					if (candidate.isAssignable(ImportSelector.class)) {
    
    
						// 判断import的类是否是ImportSelector子类
                        // @SpringBootApplication里的import注解走的就是这里
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
                        // 排除选项
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
    
    
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						if (selector instanceof DeferredImportSelector) {
    
    
                            // ImportSelector还有另一个子类接口DeferredImportSelector,
                            // 这里只做了一个动作:添加importHandler
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
    
    
                            // 普通的imports
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    
    
                        // 判断import的类是否是ImportBeanDefinitionRegistrar子类
                        // 这个子类从命名上看就是一个beanDefinition注册器,spring中的所有bean都是先通过beanDefinition注册后,再根据beanDefinition实例化的,所有这里的注册器后面也要添加到已经载入的注册器中
                        // 可以查看:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
                        // 它在parse(配置类)后有这么一句:this.reader.loadBeanDefinitions(configClasses);
                        // 这句就是将这里找出的注册器都添加的到已经加入的spring容器里的BeanDefinitionReader
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
    
    
						// import的类不是ImportBeanDefinitionRegistrar,ImportSelector,那么就作为配置类进行载入
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
    
    
				throw ex;
			}
			catch (Throwable ex) {
    
    
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
    
    
				this.importStack.pop();
			}
		}
	}

を見てください。ParserStrategyUtils.instantiateClass(...)そのインスタンス化は単純なインスタンス化ではなく、インスタンス化された Bean も初期化します。

org.springframework.context.annotation.ParserStrategyUtils#instantiateClass

	static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo, Environment environment,
			ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
    
    

        // 二次校验
		Assert.notNull(clazz, "Class must not be null");
		Assert.isAssignable(assignableTo, clazz);
		if (clazz.isInterface()) {
    
    
            // import 的类不能是接口
			throw new BeanInstantiationException(clazz, "Specified class is an interface");
		}
		ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
				((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
        // 反射实例化,这里AutoConfigurationImportSelector是无参构造
		T instance = (T) createInstance(clazz, environment, resourceLoader, registry, classLoader);
        // 这一步就是为这个实例化的AutoConfigurationImportSelector进行遍历初始化,
		ParserStrategyUtils.invokeAwareMethods(instance, environment, resourceLoader, registry, classLoader);
		return instance;
	}

@Importsこのステップから、 Spring がインポートされたクラスに対して最大限のサポートを提供していることがわかります。BeanClassLoaderAware、BeanFactoryAware、EnvironmentAware、ResourceLoaderAware

	private static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
			ResourceLoader resourceLoader, BeanDefinitionRegistry registry, @Nullable ClassLoader classLoader) {
    
    

		if (parserStrategyBean instanceof Aware) {
    
    
			if (parserStrategyBean instanceof BeanClassLoaderAware && classLoader != null) {
    
    
				((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
			}
			if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
    
    
				((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
			}
			if (parserStrategyBean instanceof EnvironmentAware) {
    
    
				((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
			}
			if (parserStrategyBean instanceof ResourceLoaderAware) {
    
    
				((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
			}
		}
	}

上記でインポートする必要があるクラスを見つけたら、ImportSelectorHandle を実行します。

場所:org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
    
    
        // 解析配置类
		for (BeanDefinitionHolder holder : configCandidates) {
    
    
			BeanDefinition bd = holder.getBeanDefinition();
			try {
    
    
				if (bd instanceof AnnotatedBeanDefinition) {
    
    
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    
    
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
    
    
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
    
    
				throw ex;
			}
			catch (Throwable ex) {
    
    
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

        // importSelectorHandler执行深度import
		this.deferredImportSelectorHandler.process();
	}

**概要:** 上記の手順では、importアノテーションを解析し、次に再帰解析を含めてアノテーション付きクラスを解析します。

インポートには 3 つのケースがあります。

  1. インターフェイスが実装されている場合ImportSelector、このタイプのインターフェイスはインターフェイス メソッドを実行してインポートする必要があるため、deferredImportSelectorHandler
  2. ImportBeanDefinitionRegistrarインターフェースが実装されています。このインターフェースは beanDefinition の登録インターフェースに属します。Spring での Bean の作成は beanDefinition に基づいているため、統一された手順に基づく必要があり、構成クラスの解析プロセスではまだ作成されていませんBean のステップはまだ準備段階なので、後で他の Bean 定義と一緒にインスタンス化されるので、すべてここに入れて、configurationClasses後で構成クラスを解析した後、取り出してマージします
  3. その他の場合には、 などの通常の設定クラスも設定クラスConfiguration componentとして入れられます。configurationClasses

次のステップ ( this.deferredImportSelectorHandler.process();) は、@Importアノテーションによって取得されたImportSelectorクラスを実行することですが、これは深いインポートと言えます。これはそのメソッド関数の説明であり、正確ではない可能性があります。

インポートクラスハンドラの実行

deferredImportSelectorHandler.process方法は次のとおりですが、deferredImportSelectorHandler.handle言及する方法は次のとおりです。

場所:org.springframework.context.annotation.ConfigurationClassParser#processImports

this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);

null 判定はありますが、初期化された時点では新しいクラスであり、null の状況はありません。

私が言いたいのは、null の状況全体はセレクターのスキャン後であり、セレクターは process メソッド ( this.deferredImportSelectorHandler.process();) を実行します。 process メソッドではループしdeferredImportSelectorsてから実行され、deferredImportSelectorsスレッドセーフではなく、設定を解析しています。クラスもサイクルであるため、このプロセスは実行中に追加が発生することを保証できません。そのため、プロセスは最初は空になります。そのため、処理するときに、現在のハンドル セットがすでに処理中であり追加できないことがわかり、その後、直接実行されます。バー;

プロセス内でリストを更新することの意味は同じで、現在のサイクルが破壊されないようにします。

// deferredImportSelectorHandler.handle		
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
    
    
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
            // 当要添加时,发现为null,表明这个集合已经在循环处理了,这里可以直接执行
			if (this.deferredImportSelectors == null) {
    
    
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder);
				handler.processGroupImports();
			}
			else {
    
    
				this.deferredImportSelectors.add(holder);
			}
		}

// deferredImportSelectorHandler.process
	public void process() {
    
    
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
    
    
                // 这里执行必然不会是null,这里new List()是为了避免在循环过程中,handle方法又进行添加对象
				if (deferredImports != null) {
    
    
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
    
    
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
	}

次に、プロセスメソッドを見てください

場所:org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process

プロセスへのdeferredImports.forEach(handler::register);グループの追加はAutoConfigurationImportSelectorメソッドによって直接返されますAutoConfigurationGroup.class

したがって、ここでは 2 つのものが初期化されます。

  1. group -> AutoConfigurationGroup.class はグループを自動的に構成します。このグループは次の 2 つのシナリオを目的としています。
    1. 現在の jar パッケージ - 「DefaultDeferredImportSelectorGroup.class」この記事は分析しません
    2. 外部 jar パッケージ - 「AutoConfigurationGroup.class (この分析の焦点)」
  2. deferredImports -> DeferredImportSelector、@Import.value の DeferredImportSelector のサブクラス、必要です
		public void register(DeferredImportSelectorHolder deferredImport) {
    
    
            // 这里其实是有值的,AutoConfigurationImportSelector它重写了这个方法,并返回了AutoConfigurationGroup.class
			Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
			DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
					(group != null ? group : deferredImport),
					key -> new DeferredImportSelectorGrouping(createGroup(group)));
            // 把handle添加到grouping
            // 实际执行是:this.deferredImports.add(deferredImport);
			grouping.add(deferredImport);
			this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
					deferredImport.getConfigurationClass());
		}
		public void processGroupImports() {
    
    
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    
    
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                // 这个getImports实际上是通过上一步添加的group.processs执行的
				grouping.getImports().forEach(entry -> {
    
    
                    // 遍历得到的configurationEntry
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
    
    
                        // 递归
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
    
    
						throw ex;
					}
					catch (Throwable ex) {
    
    
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

grouping.getImports()実際に実行されたメソッドは次のとおりです。

ここで、グループは AutoConfigurationImportSelector.AutoConfigurationGroup を参照しますが、これは固定されていますが、deferredImport はアノテーションから取得され、固定されていません。Group.process は実行者とみなされ、deferredImport はサードパーティ インターフェイスとして実装されます。

// grouping.getImports()
public Iterable<Group.Entry> getImports() {
    
    
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    
    
                // 这里通过AutoConfigurationImportSelector.AutoConfigurationGroup#process执行
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
    // 这里将导入的类返回
			return this.group.selectImports();
		}

// group.process
/**
 * @param annotationMetadata 配置类的注解信息
 * @param deferredImportSelector 配置类上@Import.value里的类
 */
@Override
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()));
    // 这里它会去获取自动配置的entry
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(annotationMetadata);
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    
    
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

以下は、 spring.fatories ファイルから読み取られたクラス (List) の構築ですAutoConfigurationEntry。これは構成クラス オブジェクトと見なすことができ、その後に解析が続きます。

	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);
        // 加载配置过滤
        // 读取spring.factories中key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的3个条件处理类,作为导入配置类的过滤处理器(import filter)
        // 然后读取META-INF/spring-autoconfigure-metadata.properties下的键值(配置类的条件,格式className.condition)
        // 这里这样处理,是因为在这一步并没有加载class,还知识className,通过spring-autoconfigure-metadata.properties配置,将条件写进到里面,然后进行过滤,下面还会提到,这里只是说明一下
        
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}



ファイルを読み取る部分を除いて、取得するロジックは次のとおりです。この段落から、完全なクラス名に従って値をロードしている List<String> configurationsことがわかります。このメソッドの最下層は直接読み取ります。SpringFactoriesLoader.loadFactoryNamesEnableAutoConfiguration META-INF/spring.factories

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
        // getSpringFactoriesLoaderFactoryClass() = () -> return EnableAutoConfiguration.class;
        // 所以这里它是通过 EnableAutoConfiguration 全类名获取对应 META-INF/spring.factories 下的value
		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;
	}

// 上面一步的:SpringFactoriesLoader.loadFactoryNames方法实现

    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());
    }

したがって、SpringBoot の自動構成は、このキーの値を読み取ることになります。

画像-20221023202016235

**概要:** このステップはDeferredImportSelector実行されるクラスです。@Importサブクラスが導入されている場合は、ここで実行されます。@EnableAutoConfigurationアノテーションにあるのは であるためAutoConfigurationImportSelectorgetAutoConfigurationEntryメソッドが実行されて読み取られます。

META-INF/spring.factories以下のキーはorg.springframework.boot.autoconfigure.EnableAutoConfigurationキー値であり、この時点でロードされList<String>、構成クラスの条件付きフィルタープロセッサを読み取ってフィルタリングし、それをエントリに構築して に配置し、 autoConfigurationEntriesspring.factories から取得したautoConfigurationEntries再帰をprocessImports走査して解析します。輸入開始。

スプリングブート状態

自動構成には、もう 1 つの重要なタイプのアノテーションがありますConditional。これも自動構成の機能であり、環境条件に従って Bean を注入できます。

例えば:

@ConditionalOnBean

@ConditionalOnMissingBean

@ConditionalOnClass

SpringBoot では、これがすべての条件処理クラスの基礎となり、残りはSpringBootConditionそのインターフェイスを実装することでgetMatchOutcome判断できます。

たとえば@ConditionalOnBean、それもマークされている場合@Conditional(OnBeanCondition.class)、その処理クラスは次のようになります。OnBeanCondition

画像-20221026231312435

上では詳しく説明しなかったので、ここで 1 点だけ触れておきたいと思います。

上記の@Importアノテーションは以下の構成クラスを読み取りMETA-INF/spring.factories、合計 2 つのキーのキー値を読み取ります。

EnableAutoConfiguration -> これはインポートのクラスです

AutoConfigurationImportFilter -> インポートクラスの処理に使用されるCondition条件処理クラスの実装クラスです。

このときインポートは全てString型でクラスはまだ読み込まれていませんが、このようなアノテーションがあるのでクラスをConditionalOnClass読み込む前にフィルタがあり、そのフィルタ条件を設定して保存しますMETA-INF/spring-autoconfigure-metadata.properties。 key= クラスの完全名。Condition アノテーションの名前は、設定クラスでどの条件アノテーションがマークされているかを示します。それらは spring および mybatis のパッケージで見つけることができます。

画像-20221026230342257

画像-20221026225808585

判定

org.springframework.context.annotation.ConditionEvaluator#ShouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)

このクラスは、構成クラスを解析する前に呼び出される検証条件検証クラスであり、クラスがロードされているかどうかを判定するために呼び出されます。shouldSkip

場所:org.springframework.context.annotation.ConditionEvaluator#ShouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)

ConfigurationPhase には 2 つの値があります。

  1. PARSE_CONFIGURATION -> は、現時点で解析構成が解析構成段階にあり、 shouldSkip 判定が失敗し、クラスがロードされないことを示します
  2. REGISTER_BEAN -> 現在 Bean の登録段階にあることを示し、 shouldSkipt の判断に失敗し、Bean は登録されません
/**
 * @param metadata 注解元数据信息
 * @param phase 执行方法的阶段,时机
*/
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    
    
    // 是否有条件注解
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    
    
			return false;
		}

		if (phase == null) {
    
    
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
    
    
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
    // 从条件注解中获取到条件处理类,如@ConditionalOnMissingBean,拿到的就是这个注解上面的@Conditional(OnBeanCondition.class)的OnBeanCondition
		for (String[] conditionClasses : getConditionClasses(metadata)) {
    
    
			for (String conditionClass : conditionClasses) {
    
    
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
    
    
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
    
    
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
            // 这里就是条件判断,看matches
            // 同时这还有一个判断,判断当前配置类上的条件注解需要在什么进行校验的判断
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    
    
				return true;
			}
		}

		return false;
	}

以下の判定では、設定クラスの条件検証は全て実装されていますConfigurationConditionが、設定が異なると解析と読み込みのタイミングが異なり、差異が生じます。

// 这里就是条件判断,看matches
            // 同时这还有一个判断,判断当前配置类上的条件注解需要在什么进行校验的判断
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    
    
				return true;
			}

OnBeanCondition例として、 onBeanConditional の条件は Bean オブジェクトに対するものであるため、段階的に判断する必要がありますREGISTER_BEANその後、クラスを準備する必要があり、構成クラスがスキャンされると、それが存在するときに直接取り出すことができます。 missingBean または onBean。

OnBeanCondition実際のマッチング方法:

@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
    
    
        // 定义结果数组
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
		for (int i = 0; i < outcomes.length; i++) {
    
    
			String autoConfigurationClass = autoConfigurationClasses[i];
			if (autoConfigurationClass != null) {
    
    
                // 这里是获取当前配置类,在条件ConditionalOnBean下所需要的处理类
                // 这里get的是spring-autoconfigure-metadata.properties读取到的key value
				Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
                // 查找当前需要的bean类型,返回返回结果信息
				outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
				if (outcomes[i] == null) {
    
    
                    // 这里也是一样,查找需要的类型,返回结果信息
					Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
							"ConditionalOnSingleCandidate");
					outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
				}
			}
		}
		return outcomes;
	}
	
/**
 * @param requiredBeanTypes 需要查找的bean的类全名
 * @param annotation 条件注解,这里因为是ConditionalOnBean代码,所以这里是ConditionalOnBean
 */
private ConditionOutcome getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) {
    
    
        // 这里是通过需要的类型,去Class.forName反射加载,能加载就将当前的类名添加到missing里
		List<String> missing = filter(requiredBeanTypes, ClassNameFilter.MISSING, getBeanClassLoader());
		if (!missing.isEmpty()) {
    
    
            // 构建条件检索结果
			ConditionMessage message = ConditionMessage.forCondition(annotation)
					.didNotFind("required type", "required types").items(Style.QUOTE, missing);
			return ConditionOutcome.noMatch(message);
		}
		return null;
	}

ここにClassNameFilter.MISSING方法があります

MISSING {
    
    

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
    
    
                // 里面其实是通过反射`Class.forName`加载,加载失败那就是没有,返回false
				return !isPresent(className, classLoader);
			}

		};

それから戻りますorg.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match

	@Override
	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    
    
		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
        // 这里就是上面的执行方法,查找需要的类,返回返回结果
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
		boolean[] match = new boolean[outcomes.length];
		for (int i = 0; i < outcomes.length; i++) {
    
    
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
			if (!match[i] && outcomes[i] != null) {
    
    
                // 日志输出
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
    
    
                    // 日志记录
					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
				}
			}
		}
		return match;
	}

主な方法は:org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)

多くのサブクラスがあり、それらの実装は異なりますが、一致するロジックは似ています。

要約する

@Import輸入サポート

  1. 注釈付きの@Configuration構成クラス
  2. ImportSelectorインターフェースを実装するクラス
  3. 実装されたImportBeanDefinitionRegistrarクラス
  4. 共通componentクラス

SpringBoot 自動構成で使用され、 @Import(AutoConfigurationImportSelector.class)AutoConfigurationImportSelector を通じてMETA-INF/spring.factoriesキーのキー値を読み取りorg.springframework.boot.autoconfigure.EnableAutoConfiguration、構成クラス リストにロードしてから、org.springframework.boot.autoconfigure.AutoConfigurationImportFilterキーの条件付きフィルター プロセッサーとMETA-INF/spring-autoconfigure-metadata.propertiesファイルの構成クラスの条件付き構成を読み取り、フィルター処理して、構成クラスのエントリを取得し、取得したエントリを走査し、インポート分析プロセスを実行します (再帰的)。

したがって、カスタムの spring-boot-starter を実装したい場合は、META-INF/spring.factoriesmybatis のように1 つを記述する必要があります。

画像-20221022195751583

この処理は、構成クラスが解析されるときに実行され、構成クラスが解析された後、解析された構成クラスが に登録され、ConfigurationClassBeanDefinitionReader後でインスタンス化されます。

また、設定クラスの条件プロセッサを自分で定義し、それを継承してSpringBootCondition、アノテーションをカスタマイズすることもできます。私はここでは怠け者なので、デモを書いていません。将来デモを書く時間はあります。

おすすめ

転載: blog.csdn.net/qq_28911061/article/details/127544104