Springboot Starter 自动配置(Auto-configuration) 源码走读

如何自定义一个starter组件

官方文档

spring-boot-reference-2.4.0 Creating Your Own Auto-configuration

使用场景

如果你在开发共享库的公司工作,或者你在开发开源或商业库,那么你可能希望开发自己的自动配置。自动配置类可以捆绑在外部jar中,并且仍然由Spring Boot加载管理。
自动配置可以关联到一个“启动器”,该启动器提供自动配置代码以及你将与之一起使用的典型库。

组件结构

在这里插入图片描述

典型的 Spring Boot Starter 包含用于自动配置实现的模块(autoconfigure module)和提供组件所需要的一切依赖的启动模块(starter module)。
启动模块实际上可认为是一个空的jar,只有一个pom文件,它的唯一目的是提供组件所需的依赖项。
如果自动配置相对简单,并且没有可选特性,那么可以在启动器中合并这两个模块。

组件命名

在这里插入图片描述
为了与spring官方项目命名(spring-boot-×××/spring-boot-×××-starter)区分开来

  • 自动配置模块命名 ×××-spring-boot
  • 启动模块命名 ×××-spring-boot-starter

实际案例

第三方使用组件时,只需要引入一个starter依赖,即直接配置使用,如引入mybatis:

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>${mybatis.spring.version}</version>
</dependency>

mybatis本身不包含jdbc驱动,需要手动引入,除此之外,其他所有依赖在mybatis-spring-boot-starter包中已经明确引入。

mybatis-spring-boot-starter pom

自定义组件

自动配置模块-定位自动配置候选对象

在这里插入图片描述

springboot会自动检测所有依赖的jar中是否存在META-INF/spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

注意

自动配置必须以这种方式加载。确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。此外,自动配置类不应该启用组件扫描来查找其他组件。应该使用特定的@Imports。

这是spring提供的规范!规范!规范!,如果不按照规范进行组件编码,则会使得项目结构很乱,并且出现很多“莫名其妙”的bug。
如:你从公司项目中抽离了一个公共组件dynamic-thread-pool,但是因为组件和项目的包路径都是pers.ergou,项目启动的时候指定了扫描包路径pers.ergou,会同时扫描dynamic-thread-pool组件,此时,即使组件的自动配置错误失效,组件也可能正常加载到spring中,并且不影响使用。以后其他项目再次使用该公共组件时,如果项目包路径非pers.ergou,就会出现组件没有正常加载的bug。

实例代码

新建maven项目 demo-spring-boot-starter

项目结构
在这里插入图片描述
添加pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>pers.ergou</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    
</project>

DemoProperties 配置属性类

package pers.ergou.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author ergou
 * @since 2023/9/18 18:33
 */
@ConfigurationProperties(prefix = "demo-spring-boot-starter")
public class DemoProperties {
    
    private boolean enable = false;

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }
    
}

DemoAutoConfiguration 自动配置类

package pers.ergou.autoconfigure;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author ergou
 * @since 2023/9/18 18:31
 */
@Configuration
@EnableConfigurationProperties(DemoProperties.class)
public class DemoAutoConfiguration {

//    手动提供bean,注册到上下文中,实现组件功能
//    @Bean
//    public Xxx xxx() {
//        return new Xxx();
//    }

// 这里可以根据需求实现各种各样的自定义功能

}

添加配置文件META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
pers.ergou.autoconfigure.DemoAutoConfiguration

这就是一个简单的starter项目,打包该项目,引入到其他项目中,项目会自动按照配置加载DemoAutoConfiguration配置,并且按条件注册配置类中定义的bean。

源码走读

@EnableAutoConfiguration@Configuration@Import等注解都是在工厂后置处理器ConfigurationClassPostProcessor中进行处理。

调用链路
ConfigurationClassPostProcessor调用链路

ConfigurationClassPostProcessor

类注释

用于@Configuration类的引导处理。
使用时默认注册,也可以像使用任何其他BeanFactoryPostProcessor一样手动声明。
这个后处理器是按优先级排序的(实现PriorityOrdered接口),因为在@Configuration类中声明的任何Bean方法在任何其他BeanFactoryPostProcessor执行之前都要注册相应的Bean定义,这一点很重要。

源码

package org.springframework.context.annotation;

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

	// 从注册中心的配置类派生更多的bean定义。
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		processConfigBeanDefinitions(registry);
	}

	// Build and validate a configuration model based on the registry of Configuration classes.
	// 构建和验证基于配置类注册表的configuration模型。
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 获取已注册的bd
		// SpringApplication#run -> SpringApplication#prepareContext 方法中
		// load(context, getAllSources().toArray(new Object[0]));
		// 将main函数所在类的bd注册到了上下文中
		String[] candidateNames = registry.getBeanDefinitionNames();

		// candidateNames中,除了主函数类bd外,还有几个默认注册的bp,bfbp和事件监听工厂,如下
		// ConfigurationClassPostProcessor  处理@Configuration的bfbp
		// AutowiredAnnotationProcessor 处理@Autowired的bp
		// RequiredAnnotationProcessor 处理@Required的bp
		// CommonAnnotationProcessor 处理@Resource的bp
		// EventListenerProcessor 处理注册侦听器,将EventListener注释方法注册为单个ApplicationListener实例。
		// EventListenerFactory 侦听器工厂
		// CachingMetadataReaderFactory 处元数据缓存
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			// isFullConfigurationClass:
			// 通过检查checkConfigurationClassCandidate的元数据标记,确定给定的bean定义是否指示完整的@Configuration类。
			// 判断是否是full configuration,带有@Configuration注解的类,那么这个类叫做full configuration
			
			// isLiteConfigurationClass: 
			// 通过检查checkConfigurationClassCandidate的元数据标记,确定给定的bean定义是否指示一个轻量版的@Configuration类。
			// 判断是否是lite configuration,带@Component,@ComponentScan,@Import,@ImportResource,@Bean 5个注解中的任一个,那么这个类叫做lite configuration
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			// 检查给定的bean定义是否是配置类(或在配置/组件类中声明的嵌套组件类,也要自动注册)的候选对象,并相应地对其进行标记。
			// 注意注意注意:
			// 上面都是检测bd中的attribute属性,checkConfigurationClassCandidate方法会真正判断注解并设置attribute值(如果符合条件)
			// key为org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
			// @Configuration value设置为full
			// @Component,@ComponentScan,@Import,@ImportResource,@Bean value设置为lite
			// 此外,还会获取@Order注解设置attribute
			// key=org.springframework.context.annotation.ConfigurationClassPostProcessor.order
			// value=@Order指定的数值
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// 上面的类中,如果不手动注册配置类,则默认只会存在主类符合条件
		// @SpringBootApplication包含@Configuration,@ComponentScan,@Import
		// 所以主类是一个FullConfigurationClass
		
		// Return immediately if no @Configuration classes were found
		// 如果没有找到@Configuration类,立即返回
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		// 按先前确定的@Order值排序(如果配置类有进行@Order配置)
		// 直接使用上面设置的attribute属性排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		// 检测通过封闭应用程序上下文提供的任何自定义bean名称生成策略
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}
		// 假如环境变量不存在,新建
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		// 解析每个@Configuration类
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
		// 需要解析的配置类bd
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// 解析完成的配置类
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// ************************************
			// *********  自动配置的核心实现  *******
			// ************************************
			// candidates 为配置类bd集合
			// ConfigurationClassParser#parse源码走读下面有,这里说一下大概步骤:
			// 1、将bd封装为配置类ConfigurationClass;
			// 2、校验注解元数据 @Conditional,判断该配置类是否需要加载;
			// 3、递归解析配置类 (首先递归地处理任何成员(嵌套)类)
			//   3.1、处理配置类的 @PropertySource 注解
			// 		 直接将解析到的source扩展到上下文环境中
			//   3.2、处理配置类的 @ComponentScan 注解
			//		 直接执行指定包路径扫描,如果扫描到的bd属于配置类,则从【1】开始执行
			//   3.3、processImports 处理配置类的 @Import 注解 
			//		首先收集所有@Import指定的类,然后分为三种情况:
			//		 1) ImportSelector接口实现类,如果属于子接口 DeferredImportSelector的实现类,
			//			则存储起来,否则,直接执行ImportSelector#selectImports,
			//			结果集importClassNames重新从【3.3】开始处理。
			//		 2) ImportBeanDefinitionRegistrar接口实现类,
			//			保存(ConfigurationClass$importBeanDefinitionRegistrars)。
			//		 3) 其他类,当做普通配置类处理,重新从【2】开始处理。
			//   3.4、处理配置类的 @ImportResource 注解,
			//		 保存(ConfigurationClass$importedResources)
			//   3.5、处理单独的 @Bean 方法,保存(ConfigurationClass$beanMethods)
			//   3.6、处理接口的默认方法,保存(ConfigurationClass$beanMethods)
			// 4、处理【3.3】@Import 中收集的DeferredImportSelector实现类集合
			//   即执行AutoConfigurationImportSelector#selectImports(源码详细看下文)
			//    执行结果是配置类全限定类名集合,将结果从【3.1】开始处理。
			parser.parse(candidates);
			// 验证每个ConfigurationClass对象。
			// ConfigurationClass对象必须非final
			// ConfigurationClass对象的Bean方法,必须为静态或可重写(以适应CGLIB)的
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			// 阅读模型并根据其内容创建bean定义
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// 注册bd
			// 方法parser.parse(candidates)中,收集的ImportBeanDefinitionRegistrar等实现
			// 收集了 
			// ConfigurationClass$importBeanDefinitionRegistrars
			// ConfigurationClass$importedResources
			// ConfigurationClass$beanMethods
			// loadBeanDefinitions方法实现真正的bd加载注册
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		// 将ImportRegistry注册为bean以支持ImportAware @Configuration类
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			// 清除外部提供的MetadataReaderFactory中的缓存;
			// 这是一个无操作的共享缓存,因为它将被ApplicationContext清除。
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

	// 省略
}

ConfigurationClassParser

类注释

解析Configuration类定义,填充ConfigurationClass对象的集合(解析单个Configuration类可能会产生任意数量的ConfigurationClass对象,因为一个Configuration类可能会使用import注释导入另一个配置类)。
这个类有助于将解析Configuration类的结构的关注点与基于该模型的内容注册BeanDefinition对象的关注点分离开来(@ComponentScan注释除外,它需要立即注册)。
这个基于ASM的实现避免了反射和急于类加载,以便在Spring ApplicationContext中有效地与惰性类加载进行互操作。

源码
ConfigurationClassParser#parse

parse

package org.springframework.context.annotation;

class ConfigurationClassParser {


	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		this.deferredImportSelectors = new LinkedList<>();

		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			// 下面的三个parse方法,入参不同,但是都是将入参封装成一个ConfigurationClass对象,
			// 然后调用processConfigurationClass(ConfigurationClass configClass)方法
			
			// 主类使用AnnotatedBeanDefinitionReader#registerBean(java.lang.Class<?>)
			// 进行bd注册,为AnnotatedGenericBeanDefinition
			// (继承GenericBeanDefinition/实现AnnotatedBeanDefinition)
			try {
				// 带有注解元数据的bd
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				// 具体的、成熟的BeanDefinition类的基类,分解出GenericBeanDefinition、RootBeanDefinition和ChildBeanDefinition的常见属性。
				// 自动连接常量与AutowireCapableBeanFactory接口中定义的常量匹配。
				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);
			}
		}
		// 上面的parse方法中执行了@Import方法的解析,
		// 但对于@Import的类为DeferredImportSelector的情况,并没有直接执行,
		// 而是将其实例化(用DeferredImportSelectorHolder封装),
		// 并添加到ConfigurationClassParser$deferredImportSelectors集合中。
		// 该方法就是用来处理DeferredImportSelector类的

		// AutoConfigurationImportSelector也是DeferredImportSelector的子类之一,
		// 所以自动配置其实实在该方法中实现
		processDeferredImportSelectors();

	}

processConfigurationClass

ConfigurationClassParser#processConfigurationClass

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		// 根据@Conditional注解确定是否应该跳过某项。
		// 校验所有@ConditionalOnXxxx的注解,判断是否应该解析该配置类
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			// isImported:
			// 返回这个配置类是通过@Import注册的还是由于嵌套在另一个配置类中而自动注册的。
			// 如果configClass是通过@Import注册
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					// 假如两个配置类非@Import导入的,
					// 则将给定配置类中的import-by声明合并到现有的这个配置类中。
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				// 否则忽略新导入的配置类;现有的非导入类将覆盖它。
				return;
			}
			// 如果configClass是嵌套在另一个配置类中而自动注册的
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				// 找到显式bean定义,可能替换了导入。
				// 我们把旧的拿掉,换上新的吧。
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		// 递归地处理配置类及其超类层次结构。
		// 获取 SourceClass 对象
		
		// asSourceClass 为工厂方法: 
		// 通过 ConfigurationClass 获取 ConfigurationClassParser.SourceClass
		// 通过 Class 获取 ConfigurationClassParser.SourceClass
		// 通过 class name 获取 ConfigurationClassParser.SourceClass
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			// 通过从源类中读取注解、成员和方法,处理并构建一个完整的ConfigurationClass。
			// 当发现相关源时,可以多次调用此方法。
			// 真正处理@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean和接口默认方法
			// 
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);
		// 缓存配置类
		this.configurationClasses.put(configClass, configClass);
	}

doProcessConfigurationClass

ConfigurationClassParser#doProcessConfigurationClass

	@Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// Recursively process any member (nested) classes first
		// 首先递归地处理任何成员(嵌套)类
		// 注册恰好是配置类本身的成员(嵌套)类。
		processMemberClasses(configClass, sourceClass);

		// Process any @PropertySource annotations
		// 处理任何@PropertySource注解
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				// 处理给定的@PropertySource注解元数据。
				processPropertySource(propertySource);
			}
			else {
				logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
		// 处理任何@ComponentScan 注解
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				// 配置类使用注解@ComponentScan -> 立即执行扫描
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				// 检查扫描的定义集以查找任何进一步的配置类,并在需要时递归地进行解析
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					// 如果扫描到的bd为配置类,直接进行解析(递归parse)
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		//***********************************
		//**********    重点    *************
		//***********************************
		// 开放闭合设计原则
		// @Import有三种情况:
		// 1、候选类是一个ImportSelector
		//	-> 委托它来确定导入
		//  如果为DeferredImportSelector,保存到deferredImportSelectors(如果不为null)
		//  这里是自动配置的核心实现入口
		//	@EnableAutoConfiguration 的 @Import 类为 AutoConfigurationImportSelector.class
		// 2、候选类是一个ImportBeanDefinitionRegistrar 
		//	-> 委托给它来注册额外的bean定义
		// 3、候选类不是ImportSelector或ImportBeanDefinitionRegistrar 
		//	->将其作为@Configuration类处理
		
		// Process any @Import annotations
		// 处理任何@Import 注解
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
		// 处理任何@ImportResource 注解
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		// 处理任何@Bean注解
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		// 处理接口的默认方法
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		// 如果存在父类,则处理父类
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				// 找到超类,返回其注释元数据并递归
				// 调用该方法的上层方法
				/**
				do {
					sourceClass = doProcessConfigurationClass(configClass, sourceClass);
				} while (sourceClass != null);
				*/
				// 直接返回父类则会进行循环处理
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		// 没有超类->处理完成
		return null;
	}

processDeferredImportSelectors

ConfigurationClassParser#processDeferredImportSelectors

该方法会对DeferredImportSelector子类的进行解析处理。

DeterminableImports目前只有两个实现类:

  • AutoConfigurationImportSelector
    处理@EnableAutoConfiguration注解。
    使用@EnableAutoConfiguration时会扫描整个类路径下,包括依赖引入的jar包所有的自动配置类(被注解了@Configuration的类)和'META-INF/spring.factories'文件,尝试进行自动配置。
  • ImportAutoConfigurationImportSelector
    处理@ImportAutoConfiguration注解。
    @ImportAutoConfiguration@Import的增强,限制了它使用的特定范围,只运行在你注解中提供的配置类。
    @ImportAutoConfiguration({配置类.class})
    
    同样会进行全局扫描,但只会加载指定的配置类。
	private void processDeferredImportSelectors() {
		// 获取在parse方法中解析到的DeferredImportSelector集合
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		if (deferredImports == null) {
			return;
		}
		// 排序:PriorityOrdered子类 > Ordered子类 > 其他(默认为Ordered.LOWEST_PRECEDENCE)
		deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);

		// 当使用@EnableAutoConfiguration时,直接指定了 @Import(AutoConfigurationImportSelector.class)
		// DeferredImportSelector 
		// 	为 AutoConfigurationImportSelector
		// DeferredImportSelector.Group 
		// 	为 AutoConfigurationImportSelector.AutoConfigurationGroup

		// DeferredImportSelectorHolder 延迟导入选择器holder,字段如下
		// - ConfigurationClass configurationClass 配置类
		// - DeferredImportSelector importSelector 延迟导入选择器
		// **作用:保存配置类及其对应的导入选择器
		
		// DeferredImportSelector.Group 接口,用于对来自不同导入选择器的结果进行分组。
		// 可以理解为DeferredImportSelector的执行器
		// 方法如下:
		// - process(AnnotationMetadata metadata, DeferredImportSelector selector);
		//   使用指定的DeferredImportSelector处理导入的@Configuration类的AnnotationMetadata。
		// - Iterable<Entry> selectImports();
		//   返回应该为此组导入的类的条目。
		// - Entry 内部类,包含AnnotationMetadata metadata 和 String importClassName
		//   保存导入的Configuration类的AnnotationMetadata和要导入的类名的条目。

		// AutoConfigurationImportSelector.AutoConfigurationGroup 静态内部类
		// DeferredImportSelector.Group 接口的实现类,
		// 用于执行AutoConfigurationImportSelector.selectImports方法

		// DeferredImportSelectorGrouping 为内部类,延迟导入选择器分组 字段如下
		// - DeferredImportSelector.Group group 选择器分组
		// - List<DeferredImportSelectorHolder> deferredImports 分组包含的选择器holder
		

		// selector分组
		Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
		Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
			// 返回一个特定的导入组,如果不需要分组,则返回null。
			// 返回AutoConfigurationImportSelector.AutoConfigurationGroup.class
			Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
			// 创建DeferredImportSelectorGrouping对象,不存在则新建
			// key为AutoConfigurationGroup.class或deferredImport 
			DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
					(group != null ? group : deferredImport),
					key -> new DeferredImportSelectorGrouping(createGroup(group)));
			// 存储deferredImport到DeferredImportSelectorGrouping对象中
			grouping.add(deferredImport);
			// 设置key=元数据,val=配置类
			configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
					deferredImport.getConfigurationClass());
		}
		// 遍历分组
		for (DeferredImportSelectorGrouping grouping : groupings.values()) {
			// grouping.getImports() 方法,
			// 通过持有的group,对deferredImports遍历执行process方法
			// 即执行AutoConfigurationImportSelector.AutoConfigurationGroup#process方法
			// 最后真正执行的是AutoConfigurationImportSelector#selectImports方法
			// 这里就是自动装配的实现逻辑。
			// AutoConfigurationImportSelector#selectImports会返回解析到的配置类(META-INF/spring.factories)的全限定名
			// 即grouping.getImports()实际上可以认为是解析到的所有配置类全限定名集合
			// Map<String, AnnotationMetadata> key为配置类全限定名,AnnotationMetadata元数据都是一样的
			grouping.getImports().forEach(entry -> {
				ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());
				try {
					// asSourceClasses(className) 通过全限定类型加载来,生成SourceClass对象
					// 直接处理配置类,即SourceClass对象
					processImports(configurationClass, asSourceClass(configurationClass),
							asSourceClasses(entry.getImportClassName()), false);
				}
				catch (BeanDefinitionStoreException ex) {
					throw ex;
				}
				catch (Throwable ex) {
					throw new BeanDefinitionStoreException(
							"Failed to process import candidates for configuration class [" +
							configurationClass.getMetadata().getClassName() + "]", ex);
				}
			});
		}
	}
	
	// 省略...
}

AutoConfigurationImportSelector

类注释

DeferredImportSelector 处理自动配置。如果需要@EnableAutoConfiguration的自定义变体,这个类也可以被子类化。

在上面源码走读过程中可知,选择器分组AutoConfigurationImportSelector.AutoConfigurationGroup#process内部实际上就是执行了AutoConfigurationImportSelector#selectImports方法,该方法会返回所有依赖jar以及本身的'META-INF/spring.factories'中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有全限定类名组成的一个集合。

源码

package org.springframework.boot.autoconfigure;

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
	
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 判断是否需要自动装配
		// getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)
		// 配置spring.boot.enableautoconfiguration是否为false
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 加载autoconfigure项目中的"META-INF/spring-autoconfigure-metadata.properties"属性文件(自动配置元数据)
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		// 从注解元数据返回适当的注解属性。默认情况下,此方法将返回getAnnotationClass()的属性。
		// 获取@EnableAutoConfiguration注解的属性(exclude和excludeName)
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 从所有依赖jar和本身的'META-INF/spring.factories'文件中,获取
		// key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
		// 的全限定类名集合
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		// 去除重复的全限定类名
		configurations = removeDuplicates(configurations);
		// 获取注解属性指定的排除配置类全限定名
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 检测指定的排除全限定类名是否符合排除规范
		// 类可加载 && 指定的排除全限定类名存在于configurations中
		// 否则抛出异常 IllegalStateException 
		// The following classes could not be excluded 
		// because they are not auto-configuration classes:XXX
		checkExcludedClasses(configurations, exclusions);
		// 去除指定排除的配置类
		configurations.removeAll(exclusions);
		
		// AutoConfigurationImportFilter接口:
		// 设计目的是允许在读取自动配置类的字节码之前快速删除它们
		// AutoConfigurationImportFilter默认实现类为OnClassCondition
		// OnClassCondition的作用:
		// 主要是针对配置类上的注解@ConditionalOnClass, @ConditionalOnMissingClass

		// 从所有依赖jar和本身的'META-INF/spring.factories'文件中,获取
		// key为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的全限定类名集合
		// 通过全限定类名,加载并实例化AutoConfigurationImportFilter的实现类。
		// 遍历对象集合
		// - 执行Aware接口进行组件织入 invokeAwareMethods(filter)
		// - 对configurations进行过滤处理 filter.match
		configurations = filter(configurations, autoConfigurationMetadata);
		
		// 从所有依赖jar和本身的'META-INF/spring.factories'文件中,获取
		// key为org.springframework.boot.autoconfigure.AutoConfigurationImportListener的全限定类名集合
		// 通过全限定类名,加载并实例化AutoConfigurationImportListener的实现类。
		// 遍历对象集合:
		// 	- 执行Aware接口进行组件织入 invokeAwareMethods(listener)
		//  - 处理自动配置导入事件(AutoConfigurationImportEvent)
		//    AutoConfigurationImportListener#onAutoConfigurationImportEvent
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 返回结果
		return StringUtils.toStringArray(configurations);
	}
	
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		// getSpringFactoriesLoaderFactoryClass() 
		// -> org.springframework.boot.autoconfigure.EnableAutoConfiguration
		// 获取所有依赖jar包中'META-INF/spring.factories'
		// key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
		// 的全限定类名集合
		// SpringFactoriesLoader.loadFactoryNames([key值], [类加载器])
		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

类注释

框架内部使用的通用工厂加载机制。
SpringFactoriesLoader从多个JAR文件的类路径“META-INF”下的spring.factories文件中,加载并实例化给定类型的工厂。

源码

package org.springframework.core.io.support;

public abstract class SpringFactoriesLoader {
	
	// 寻找工厂的地点。可以存在于多个JAR文件中。
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

	// spring.factories文件的结果缓存
	private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

	// 使用指定类加载器,从"META-INF/spring.factories"文件中加载全路径工厂类的实现类。
	// 参数-factoryClass:指定类,获取spring.factories文件中,key为指定类的全路径类名的所有全路径类名
	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
	
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// 先充缓存获取指定类加载器的缓存,如果存在,则代表已经进行过加载,直接返回
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			// 获取"META-INF/spring.factories"资源
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			// 解析文件
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			// 缓存资源
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

	// 省略
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当阅读Spring Boot代码时,可以按照以下步骤进行: 1. 了解项目结构:Spring Boot项目通常遵循标准的MVC(Model-View-Controller)结构,其中包含控制器、服务、数据访问层等模块。首先,了解项目的整体结构有助于理解代码的组织方式。 2. 阅读启动类:Spring Boot应用程序的入口点是一个带有`@SpringBootApplication`注解的启动类。该类通常包含了主方法,用于启动应用程序。可以阅读该类来了解应用程序的配置和初始化过程。 3. 阅读配置文件:Spring Boot使用`application.properties`或`application.yml`等配置文件来配置应用程序。这些文件中包含了一些常用的配置项,如数据库连接、日志配置等。阅读配置文件可以了解应用程序的基本配置信息。 4. 阅读控制器:控制器负责处理HTTP请求,并将请求分派给相应的服务进行处理。通过阅读控制器代码,可以了解应用程序的请求处理逻辑。 5. 阅读服务:服务层是应用程序的核心业务逻辑部分,负责处理业务逻辑、调用数据访问层等。阅读服务层代码可以了解应用程序的核心功能和业务逻辑。 6. 阅读数据访问层:数据访问层负责与数据库进行交互,执行CRUD(创建、读取、更新、删除)操作。阅读数据访问层代码可以了解应用程序与数据库交互的方式和逻辑。 7. 阅读模型:模型类用于定义数据结构,通常与数据库中的表结构相对应。通过阅读模型类的代码,可以了解应用程序的数据结构和字段定义。 8. 阅读测试代码:Spring Boot鼓励进行单元测试和集成测试,测试代码通常位于与代码相同的目录中。阅读测试代码可以了解应用程序的测试覆盖范围和测试用例。 在阅读代码时,可以结合官方文档、注释和命名规范等辅助信息来理解代码的含义和作用。还可以使用调试工具来跟踪代码的执行过程,以更深入地理解代码的运行流程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值