SpringBoot source code (1) automatic configuration

foreword

I use automatic configuration as the first chapter of SpringBoot, because from SpringMvc to SpringBoot, it achieves zero configuration, and there are many default configurations. When understanding the source code later, there may be some places that I don’t understand, and the idea is not clear. So first understand the part of automatic configuration clearly, and know how one of its configurations is loaded, and the subsequent learning should be smoother.

SpringBoot's automatic configuration @EnableAutoConfigurationis enabled by annotations, so let's start with this annotation to see how it implements automatic configuration and conditional filtering.

principle

External jar packages or third-party packages are loaded in the same way. To achieve automatic configuration, you need to follow the spring specification, usually configuration META-INFO/spring.factoriesand META-INF/spring-autoconfigure-metadata.propertiestwo files, so as to realize automatic configuration.

In the Spring chapter spring source code (6) configuration class parsing process , I learned how Spring injects beans through configuration classes, as follows.

Spring judges it as a configuration class, and there are several annotations @Configuration、@Component、@ComponentScan、@Import、@ImportResource:@Bean

All, Spring’s conventional ways of injecting beans are (Spring judges from beanDefinition, all premise is a beanDefinition):

  1. @Component/@Configuration

  2. @Component/@Configuration + @Bean

  3. @Component/@Configuration + @CompnentScan

  4. @Component/@Configuration + @Import

  5. @Component/@Configuration + @ImportResource

Then you can probably guess the way to introduce other external classes. You can use @ComponentScanthe scanning method to scan in the external package, and this method is to apply the active scanning. This active method is not flexible for the framework of component integration. , For example, if you open an interface to a third party, and then you cannot call the interface, you have to add this interface in your configuration, does it feel very inconsistent, like, I develop an idea Plug-in, I have done it according to the specification of idea, released it, and I also use it to notify the official of idea, let them enable the use permission of my plug-in, is it outrageous, no one cares about you.

So what we should pay attention to should be passively accepting components, and then scanning, just like assembling a car, with spring providing the framework, main control chip, and engine, we will assemble the rest.

In Tomcat, Tomcat initializes the application by META-INF/services/javax.servlet.ServletContainerInitializerreading the application implementation class, and spring is the same, it META-INFO/spring.factoriesloads the configuration by reading this file.

For example, the dependency package of mybatis

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

When imported, it relies on a

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

It can be seen that there are two configuration classes configured in it, and these two are the cores that mybatis can automatically configureimage-20221022195751583

Then let's look at the automatic configuration of SpringBoot. Spring will introduce the following automatic configuration dependency. There are many automatic configuration classes in it. Basically all the automatic configurations of spring-boot are here. If you look carefully, you will see to aop, jpa, MongoDB, kafka, servlet, etc.

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

image-20221022231936137

@EnableAutoConfiguration

Parsing Import

So how does SpringBoot load external packages, or third-party packages?

When starting the SpringBoot application, annotations will be added @SpringBootApplication, and this annotation is the key

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

SpringApplication.run(App.class);This is to start the web container, the spring container, and then the Spring set.

The definition we looked at @SpringBootApplication, there is one in it @EnableAutoConfiguration, this is the annotation for automatic configuration loading

image-20221022233013456

image-20221022233052400

@EnableAutoConfigurationThere is such a note in the note@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();

}

We can find out through annotations that its value can be set to four types of classes:

  1. Annotated @Configurationconfiguration classes
  2. class that implements ImportSelectorthe interface
  3. implemented ImportBeanDefinitionRegistrarclass
  4. common componentclass

When Spring starts, it will create an ioc container, and add our configuration class ( @SpringBootApplicationmarked startup class) as a configuration class, which is equivalent to one Configurationand the first configuration class, through which other configuration classes or beans are scanned; When parsing this configuration class, it will importsbe parsed regardless of whether it is present or not

The location of the parsing configuration class: org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

Analysis importer location: org.springframework.context.annotation.ConfigurationClassParser#processImports

The function of this step is to find out the ImportSelector class in imports, and also find out the beanDefinition register in the Import annotation, just find out, and do nothing.

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

Take a look at: ParserStrategyUtils.instantiateClass(...), its instantiation is not a simple instantiation, but also initializes the instantiated 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;
	}

It can be seen from this step that @ImportsSpring gives it the greatest support for the imported classes: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);
			}
		}
	}

After finding the class that needs to be imported above, execute 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();
	}

**Summary:** The above steps are to importparse the annotations, and then parse the annotated classes, including recursive parsing.

There are three cases of import:

  1. If the interface is implemented ImportSelector, then this type of interface needs to be imported by executing the interface method, so it will be added to thedeferredImportSelectorHandler
  2. ImportBeanDefinitionRegistrarThe interface is implemented. This interface belongs to the registration interface of beanDefinition. The creation of beans in spring is based on beanDefinition, so it should be based on unified steps, and in the process of parsing configuration classes, it has not yet been created . The steps of beans are still in the preparation stage, so they will be instantiated together with other bean definitions later, so they are all put in here, and after the configurationClassesconfiguration class is parsed later, it will be taken out and merged
  3. In other cases, ordinary configuration classes, such as Configuration componentetc., are also put in as configuration configurationClassesclasses

Then the next step ( this.deferredImportSelectorHandler.process();) is to execute the class @Importobtained through the annotation , which can be said to be a deep import. This is our description of its method function, which may not be accurate.ImportSelector

Execute import class handler

Here's deferredImportSelectorHandler.processhow to do it, but here's deferredImportSelectorHandler.handlea way to mention it.

位置:org.springframework.context.annotation.ConfigurationClassParser#processImports

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

It has a null judgment, but the class is new when it is initialized, and there is no null situation.

What I want to say is that the entire null situation is after the selector scans, and he will execute the process method ( this.deferredImportSelectorHandler.process();), in the process method, it loops deferredImportSelectorsand then executes, and deferredImportSelectorsit is not thread-safe, and parsing the configuration class is also a cycle, so This process cannot guarantee that add will occur during execution, so the process will be empty first, so that when handling, you know that the current handle set is already being processed and cannot be added, then execute directly Bar;

The meaning of renewing a list in the process is the same, to ensure that the current cycle is not destroyed.

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

Then look at the process method

位置:org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process

deferredImports.forEach(handler::register);Adding group to process AutoConfigurationImportSelectoris returned directly by the methodAutoConfigurationGroup.class

So two things are initialized here:

  1. group -> AutoConfigurationGroup.class automatically configures the group, and the group is aimed at two scenarios:
    1. Current jar package - "DefaultDeferredImportSelectorGroup.class This article does not analyze
    2. External jar package - "AutoConfigurationGroup.class ( the focus of this analysis )
  2. deferredImports -> DeferredImportSelector, a subclass of DeferredImportSelector in @Import.value, it needs
		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()The method actually executed:

Here group refers to AutoConfigurationImportSelector.AutoConfigurationGroup, which is fixed, while deferredImport is obtained from annotations, and is not fixed. Group.process can be regarded as an executor, and deferredImport is implemented as a third-party interface

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

Here is the construction of the class (List) read from the spring.fatories file AutoConfigurationEntry, which can be regarded as a configuration class object, followed by parsing

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



Here is List<String> configurationsthe logic it gets, except for the part that reads the file. From this paragraph, it can be seen that it loads the value SpringFactoriesLoader.loadFactoryNamesaccording to the full class name. The bottom layer of this method is to read directlyEnableAutoConfiguration 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());
    }

So SpringBoot's automatic configuration is to read the value of this key

image-20221023202016235

**Summary:** This step is DeferredImportSelectorthe class to be executed. @ImportIf there are subclasses introduced in it, it will be executed here. @EnableAutoConfigurationWhat is in the annotation is AutoConfigurationImportSelector, so getAutoConfigurationEntrythe method will be executed to read

META-INF/spring.factoriesThe key below org.springframework.boot.autoconfigure.EnableAutoConfigurationis the key value, which is loaded at this time List<String>, and then read the configuration class conditional filter processor to filter, then build it into an entry and put it in , and then traverse the recursion autoConfigurationEntriesobtained from spring.factories to parse the import start.autoConfigurationEntriesprocessImports

SpringBootCondition

In automatic configuration, there is another important type of annotation Conditional, which is also a function of automatic configuration, which can inject beans according to environmental conditions.

for example:

@ConditionalOnBean

@ConditionalOnMissingBean

@ConditionalOnClass

In SpringBoot, it is the basis of all conditional processing classes, and the rest can be judged SpringBootConditionby implementing its interface.getMatchOutcome

For example @ConditionalOnBean, if it is also marked on it @Conditional(OnBeanCondition.class), then its processing class isOnBeanCondition

image-20221026231312435

Here I also want to mention one point, because I did not elaborate above;

The above @Importannotation will read META-INF/spring.factoriesthe configuration class below, and will read the key values ​​of two keys in total:

EnableAutoConfiguration -> This is the class of import

AutoConfigurationImportFilter -> This is the implementation class of the Condition condition processing class, which is used to process the import class

At this time, the imports are all of String type, and the class has not been read yet. Because there are annotations like this, there ConditionalOnClasswill be a filter before loading the class, and the filter condition will be configured and saved in META-INF/spring-autoconfigure-metadata.properties; its key=full name of the class. The name of the Condition annotation indicates which condition annotations are marked in the configuration class. You can find them in the packages of spring and mybatis.

image-20221026230342257

image-20221026225808585

judgement

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

This class is a verification condition verification class that will be called before parsing the configuration class. It is called to judge whether the class is loaded.shouldSkip

位置:org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata, org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase)

ConfigurationPhase has two values:

  1. PARSE_CONFIGURATION -> indicates that the parsing configuration is in the parsing configuration stage at this time, the shouldSkip judgment fails, and the class will not be loaded
  2. REGISTER_BEAN -> indicates that the time is in the bean registration stage, shouldSkipt fails to judge, and the bean will not be registered
/**
 * @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;
	}

For the following judgment, the conditional verification of the configuration class is all implemented ConfigurationCondition, but for different configurations, the timing of parsing and loading is different, and there will be differences.

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

Take OnBeanConditionit as an example, it needs REGISTER_BEANto be judged in stages, because the condition of onBeanConditional is for bean objects, then he needs to prepare the class, and when a configuration class is scanned, it can be taken out directly when it is in missingBean or onBean.

OnBeanConditionActual matching method:

@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;
	}

Here ClassNameFilter.MISSINGis a method

MISSING {
    
    

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

		};

then returnorg.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)

It has many subclasses, and their implementations are different, but the matching logic is similar.

Summarize

@Importimport support

  1. Annotated @Configurationconfiguration classes
  2. class that implements ImportSelectorthe interface
  3. implemented ImportBeanDefinitionRegistrarclass
  4. common componentclass

Used in SpringBoot automatic configuration , it reads the key value of the key @Import(AutoConfigurationImportSelector.class)through AutoConfigurationImportSelector , loads it into the configuration class list, and then reads the conditional filter processor of the key, and the configuration class conditional configuration of the file, filters and obtains the configuration The entry of the class, and then traverse the obtained entry, and then go through the import analysis process (recursive).META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfigurationorg.springframework.boot.autoconfigure.AutoConfigurationImportFilterMETA-INF/spring-autoconfigure-metadata.properties

So if we want to implement our custom spring-boot-starter, we need to write one META-INF/spring.factories, just like mybatis

image-20221022195751583

This process is carried out when the configuration class is parsed, and after the configuration class is parsed, the parsed configuration class will be registered in and ConfigurationClassBeanDefinitionReaderinstantiated later.

We can also define the configuration class condition processor by ourselves, just inherit it SpringBootCondition, and then customize an annotation. I’m lazy here, and I didn’t write a demo. I have time to write one in the future.

Guess you like

Origin blog.csdn.net/qq_28911061/article/details/127544104