前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot(一)自动配置

SpringBoot(一)自动配置

作者头像
用针戳左手中指指头
发布2022-11-07 16:27:19
4440
发布2022-11-07 16:27:19
举报
文章被收录于专栏:学习计划

文章目录

前言

我以自动配置为SpringBoot的第一篇章,是因为从SpringMvc到SpringBoot,它实现了零配置,并出现了很多默认配置,在进行后面的源码理解时可能会有部分地方不理解,念头不通达,所以先将自动配置这部分给了解清楚,知道它的一个配置是怎么加载的,对后面的学习应该会更流畅一点。

SpringBoot的自动配置由注解@EnableAutoConfiguration开启,所以我们从这个注解入手,看看它是怎么实现的自动配置,和条件过滤的。

原理

在Spring章节spring源码篇(六)配置类解析过程,学习了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扫描的方式将外部包扫描进来,而这种方式是应用主动的去扫描,这种主动的方式对于组件整合这种框架就不灵活了,比如你开了一个接口给第三方,然后你在调用接口的时候调用不到,你还要在你的配置上将这个接口添加进来,是不是感觉很违和,就像,我开发一个idea插件,我根据idea的规范做好了,发布了,我还用通知idea官方,让他们将我的插件开通下使用权限,是不是离谱了,没人理你。

所以我们应该关注的应该时被动的去接受组件,然后扫描,就像组装车子一样,有了spring提供框架,主控芯片,发动机,其他就由我们去组装了。

Tomcat中,Tomcat初始化应用程序是通过META-INF/services/javax.servlet.ServletContainerInitializer读取到应用实现类,而spring也是一样的,它是通过读取META-INFO/spring.factories这个文件来加载配置的。

比如mybatis的依赖包

代码语言:javascript
复制
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

在引入时,会依赖一个

代码语言:javascript
复制
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>

可以看到,它里面配置了两个配置类,这两个就是mybatis能够自动配置的核心

之前有提到过,Tomcat加载应用的初始化程序,它是通过META-INF/services/javax.servlet.ServletContainerInitializer读取到应用实现类,而spring也是一样的,它是通过读取META-INFO/spring.factories这个文件来加载配置的

然后我们在来看SpringBoot的自动配置,Spring是会引入下面的一个自动配置依赖,它里面有很多自动配置类,基本上所有的spring-boot的自动配置都在这里,仔细看的话,你会看到aop、jpa、MongoDB、kafka、servlet等等。

代码语言:javascript
复制
 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

@EnableAutoConfiguration

解析Import

那么SpringBoot是怎么加载的呢?

在启动SpringBoot应用时,都会加上注解@SpringBootApplication,这个注解就是关键

代码语言:javascript
复制
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

SpringApplication.run(App.class);这个是启动web容器,spring容器,然后就是Spring的那一套,

我们查看@SpringBootApplication的定义,它里面有一个@EnableAutoConfiguration,这个就是自动配置加载的注解

@EnableAutoConfiguration注解里有这样一个注解@Import({AutoConfigurationImportSelector.class})

代码语言:javascript
复制
@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();

}

通过注释我们可以找到,它的value可以设置四种类型class:

  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

这一步的作用是将imports里的ImportSelector类找出来,还有将Import注解里的beanDefinition注册器也找出来,只是找出来,并没有做什么处理。

代码语言:javascript
复制
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

代码语言:javascript
复制
	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

代码语言:javascript
复制
	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);
			}
		}
	}

在上面找到了需要import的类后,执行ImportSelectorHandle

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

代码语言:javascript
复制
	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注解,然后将注解了的类进行解析,包含递归的解析。

import分三种情况:

  1. 实现了ImportSelector接口,那么这一类的接口属于是需要执行接口方法进行导入的,所以在解析时都会添加到deferredImportSelectorHandler
  2. 实现了ImportBeanDefinitionRegistrar接口的,这个接口属于是beanDefinition的注册接口,spring中的创建bean都有依据beanDefinition来的,所以应该是以统一的步骤为基础,并且在这个解析配置类的过程中,还没有到创建bean的步骤,还在准备阶段,所以后面是要和其他的beanDefinition一起实例化的,所以这里都是放到了configurationClasses里,后面解析完配置类后再从取出来进行合并
  3. 其他情况,普通的配置类,如Configuration component等,它同样是作为配置类放到configurationClasses

然后下一步(this.deferredImportSelectorHandler.process();),对通过@Import注解拿到的ImportSelector类进行执行,可以说是深度import,这个是我们对它的一个方法功能描述,可能不太准确。

执行导入类处理器

这里是执行deferredImportSelectorHandler.process方法,但这里要提一下deferredImportSelectorHandler.handle方法。

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

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

它有一个null的判断,但是类初始化时就new了,不存在null的情况,

我要说的是整个null的情况是在selector扫描完了,他会执行process的方法(this.deferredImportSelectorHandler.process();),在process方法中,它循环deferredImportSelectors然后执行,并且deferredImportSelectors是非线程安全的,再者解析配置类也是一个循环,所以这个process它不能保证执行过程中会出现add的情况,所以,process会先置空,这样,在handle时,就知道当前的handle集合已经在处理了,不能在往里添加了,那么就直接执行吧;

而process里重新new了一个list的意义也是一样的,要保证当前循环不被破坏。

代码语言:javascript
复制
// 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<>();
			}
		}
	}

那么在接着看process方法

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

process中deferredImports.forEach(handler::register);添加group,AutoConfigurationImportSelector是方法直接返回的AutoConfigurationGroup.class

所以这里初始化了两个东西:

  1. group -》 AutoConfigurationGroup.class 自动配置group,group针对两种场景:
    1. 当前jar包 -》DefaultDeferredImportSelectorGroup.class 本篇不分析
    2. 外部jar包 -》 AutoConfigurationGroup.class (本篇分析的重点
  2. deferredImports -》 DeferredImportSelector,@Import.value里DeferredImportSelector的子类,它需要
代码语言:javascript
复制
		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());
		}
代码语言:javascript
复制
		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()实际执行的方法:

这里group指的是AutoConfigurationImportSelector.AutoConfigurationGroup,是固定的,而deferredImport它是从注解里获取的,并非是固定的,可以把group.process看做一个执行器,deferredImport作为第三方接口实现

代码语言:javascript
复制
// 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,可以看做一个配置类对象,后面还有进行解析

代码语言:javascript
复制
	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.loadFactoryNames根据EnableAutoConfiguration 全类名加载的value,这个方法的底层是直接读取META-INF/spring.factories

代码语言:javascript
复制
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的自动配置是读取这个键的值

**总结:**这一步是执行DeferredImportSelector的类,在@Import中有引入其子类的,这里都会执行,@EnableAutoConfiguration注解里的是AutoConfigurationImportSelector,所以会执行getAutoConfigurationEntry方法,从而读取

META-INF/spring.factories下的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的键值,这时加载到的是List<String>,然后再读取配置类条件过滤处理器进行过滤,之后构建成entry放到autoConfigurationEntries,之后再遍历从spring.factories里得到的autoConfigurationEntries递归走processImports从解析import开始。

SpringBootCondition

在自动配置中,还有一个比较重要的一类注解Conditional

比如:

@ConditionalOnBean @ConditionalOnMissingBean @ConditionalOnClass

而在SpringBoot中,SpringBootCondition是所有条件处理类基础,其他都是通过实现它的getMatchOutcome接口完成判定就行。

比如@ConditionalOnBean,它上面还标注了@Conditional(OnBeanCondition.class),那么它的处理类就是OnBeanCondition

这里我还要提一个点,因为上面我没有详细说明;

上面@Import注解会读取META-INF/spring.factories下的配置类,一共会读取两个key的键值:

EnableAutoConfiguration -> 这个是import的类 AutoConfigurationImportFilter -> 这个是Condition条件处理类的实现类,用来处理import的类

而这时导入的都是String类型的,还没有读取class,因为存在像这样的ConditionalOnClass注解,所以加载class前会有一次过滤,过滤条件就配置就保存在META-INF/spring-autoconfigure-metadata.properties;它的key=class类全名.Condition注解名称,表示配置类标注了哪些条件注解,可以翻看spring和mybatis的包,都会找到的。

判定

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有两个值:

  1. PARSE_CONFIGURATION -》 表示解析配置该时机正处于解析配置阶段,shouldSkip判断失败,不会加载class
  2. REGISTER_BEAN -》 表示该时机正处于注册bean阶段,shouldSkipt判断失败,不会注册bean
代码语言:javascript
复制
/**
 * @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,但对于不同的配置,解析加载的时机不同,也会有所差别。

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

OnBeanCondition为例,它是需要REGISTER_BEAN阶段才进行判断,因为,onBeanConditional这个条件针对bean对象,那么他需要准备好class,当扫描到一个配置类,它在missingBean,或onBean时,才能直接取出。

OnBeanCondition实际的匹配方法:

代码语言:javascript
复制
@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;
	}
代码语言:javascript
复制
/**
 * @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是一个方法

代码语言:javascript
复制
MISSING {

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

		};

然后返回org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match

代码语言:javascript
复制
	@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下的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的键值,加载到配置类列表,然后再读取key为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的条件过滤处理器,和META-INF/spring-autoconfigure-metadata.properties文件的配置类条件配置,进行过滤,得到配置类的entry,然后在遍历得到的entry,再走一遍import的解析过程(递归)。

所以我们要实现我们自定义的spring-boot-starter,就要写一个META-INF/spring.factories,就像mybatis这样

这一过程是在解析配置类的时候进行,再解析完配置类后,会把解析的配置类注册到ConfigurationClassBeanDefinitionReader中,待后面进行实例化。

配置类条件处理器,我们也可以自己定义,继承SpringBootCondition就行,再自定义一个注解,这里偷懒了,没有写demo,以后有时间写一个看看。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 原理
  • @EnableAutoConfiguration
    • 解析Import
      • 执行导入类处理器
        • SpringBootCondition
          • 判定
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档