从注解入手扒一扒SpringBoot的自动配置原理


说明

SpringBoot的两大核心,自动配置和依赖管理。SpringBoot的项目,往往只是依靠一个入口便能完成整个项目的加载和启动。在此从启动类的注解来查查启动的大概过程,以下代码基于 springboot的 2.7.4 其使用的Spring版本为 5.3.23

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

}



@SpringBootApplication整体结构


@SpringBootApplication注解的声明,可以看出是一个组合注解。
@Target(ElementType.TYPE)//注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME)//表示注解的生命周期,Runtime运行时
@Documented//表示注解可以记录在javadoc中
@Inherited//表示可以被子类继承该注解
    
@SpringBootConfiguration// 标明该类为配置类
@EnableAutoConfiguration// 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

@SpringBootApplication注解的注解组成图

一、@SpringBootConfiguration注解

@SpringBootConfiguration的声明

@Target(ElementType.TYPE) //元注解
@Retention(RetentionPolicy.RUNTIME)//元注解
@Documented//元注解

@Configuration //核心注解
@Indexed//spring5.0后新增的 索引注解,可以提高启动速度
public @interface SpringBootConfiguration {
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}
  • 定义上是Spring的@Configuration注解的另一种形式,可以替代@Configuration注解使用。
  • 用来指示一个类,是提供Spring引导程序的类,一个应用应该只包含一个该注解标注的类。大部分Spring引导程序,都从@SpringBootApplication中继承了这个注解
  • @indexd 5.0版本开始加入的,标识需要注入的类,提前生成索引文件META-INT/spring.components避免整个包扫描。需要增加spring-context-indexer依赖才生效。

二、@EnableAutoConfiguration注解

该注解标识开启自动配置功能,是SpringBoot最重要的注解,也是实现自动化配置的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
    
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class) //自动配置类扫描导入
public @interface EnableAutoConfiguration {

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
     *环境属性,可以在使用自动配置时重写
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
     * 配置一些被排除的自动配置类的全路径类名信息
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
     *  配置一些被排除的自动配置类的全路径类名信息
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

3.1 @AutoConfigurationPackage 自动配置包注解


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
    
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};

}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			//对主程序类所在的包及其子包进行扫描注册
            register(registry, 
                     //此处获得了注解标注的类(也就是主程序启动类),所在的包路径。
                     new PackageImports(metadata).getPackageNames().toArray(new String[0])
                    );
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}
  • @AutoConfigurationPackage 的功能是由@import注解实现的,主要功能是,如果没有声明扫描的包时,就注册被AutoConfigurationPackage 修饰的类所在的包。
  • @Import的主要功能,就是给容器中导入某个组件类。
  • @Import(AutoConfigurationPackages.Registrar.class) 就是AutoConfigurationPackages.Registrar.class这个组件类,导入到容器中。组件的具体实现,就是Registrar类中registerBeanDefinitions方法。
  • 也就是说 @AutoConfigurationPackage这个注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。

3.2 @Import(AutoConfigurationImportSelector.class) 自动配置类引入

该注解的功能是将AutoConfigurationImportSelector这个类导入到spring容器中。AutoConfigurationImportSelector可以帮助SpringBoot应用,将所有符合条件的@Configuration配置类,都加载到当前SrpingBoot应用创建并使用的IOC容器(ApplicationContext)中

3.2.1 AutoConfigurationImportSelector.class类功能

  1. AutoConfigurationImportSelector通过selectImports方法,告诉Springboot需要导入哪些组件。
  2. selectImports中的核心方法为 getAutoConfigurationEntry
  3. getAutoConfigurationEntry 的核心为 getCandidateConfigurations(annotationMetadata, attributes);
  4. getCandidateConfigurations方法包含两种读取形式,从META-INT/中的factories文件或者imports文件中读取需要自动配置注入的配置类类名
//DeferredImportSelector来处理自动配置。如果需要@EnableAutoConfiguration的自定义变体,也可以子类化这个类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	....
    //入口 AutoConfigurationImportSelector.selectImports
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //没启动自动配置就不导入
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
        //获得了所有需要自动配置的配置类信息
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
    
	/**
    * 1.AutoConfigurationImportSelector.selectImports 返回需要自动配置的类路径列表
	* 2.getAutoConfigurationEntry 返回需要自动配置的类信息
	*/
    //返回基于导入@Configuration类的AnnotationMetadata的AutoConfigurationEntry。
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}


	/**
    * 1.AutoConfigurationImportSelector.selectImports 返回需要自动配置的类路径列表
	* 2.getAutoConfigurationEntry 返回需要自动配置的类信息
	* 3.getCandidateConfigurations 从类路径的META-INF中读取自动配置的配置类全类名
	*/
    //返回应该考虑的自动配置类名。
    //默认情况该方法将使用importcandidate和getspringfactoresloaderfactoryclass()加载候选对象。
    //出于向后兼容的原因
    //它还将考虑使用getspringfactoresloaderfactoryclass()来实现springfactoresloader。
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		
        //兼容老版本 使用getspringfactoresloaderfactoryclass()来实现springfactoresloader加载
        List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		
        //默认-使用importcandidate和getspringfactoresloaderfactoryclass()加载候选对象
        ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

    

	....
}

3.2.2 获取候选配置类的方式

getCandidateConfigurations 返回了所有需要自动配置类的全限定类名,两种加载方法,加载结果后期合并

  1. 获取自动配置类方法1:ImportCandidates.load方法,加载路径META-INF/spring/full-qualified-annotation-name.imports文件中的自动配置类类名。SpringBoot从2.7开始推荐使用这种imports方式。所以此次使用的SpringBoot 2.7.4 的spring-boot-autoconfigure包就已经包含了imports文件。
  2. 获取自动配置类的方法2:从所有jar的 META-INF/spring.factories 文件中读取自动装配的配置类名。现在使用的版本 2.7.4 的
  3. spring.factories 工厂文件必须采用Properties格式,其中键是接口或抽象类的完全限定名,值是用逗号分隔的实现类名列表。例如: example.MyService = example.MyServiceImpl1 example.MyServiceImpl2 在例子。MyService是接口的名称,MyServiceImpl1和MyServiceImpl2是两个实现。
  4. spring.factories中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration类对应的那些配置类已经移动到了imports文件中了。但仍然保留了需要自动配置的监听器、处理器、过滤器等的自动装配类。
public final class ImportCandidates implements Iterable<String> {

	private static final String LOCATION = "META-INF/spring/%s.imports";

	private static final String COMMENT_START = "#";

	private final List<String> candidates;

	private ImportCandidates(List<String> candidates) {
		Assert.notNull(candidates, "'candidates' must not be null");
		this.candidates = Collections.unmodifiableList(candidates);
	}

	@Override
	public Iterator<String> iterator() {
		return this.candidates.iterator();
	}
    
	/**
    *从类路径加载导入候选对象的名称。
	*导入候选项的名称存储在名为META-INF/spring/full-qualified-annotation-name的文件中。
	*在类路径上导入。每一行都包含候选类的完整限定名。注释支持使用#字符
	*/
	public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
		String location = String.format(LOCATION, annotation.getName());
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> autoConfigurations = new ArrayList<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			autoConfigurations.addAll(readAutoConfigurations(url));
		}
		return new ImportCandidates(autoConfigurations);
	}
public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


	private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

	static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();


	private SpringFactoriesLoader() {
	}


	/**
	 *从“META-INF/spring”加载并实例化给定类型的工厂实现。,使用给定的类装入器。 
	 *返回的工厂通过AnnotationAwareOrderComparator进行排序。 
	 *如果需要自定义实例化策略,则使用loadFactoryNames获取所有注册的工厂名称。 
	 *从Spring Framework 5.3开始,如果在给定的工厂类型中发现重复的实现类名,
	 *则只实例化重复的实现类型的一个实例。
	 */
	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
    
	/**
	 *从“META-INF/spring”中加载给定类型的工厂实现的完全限定类名。
	 * 使用给定的类装入器。 从Spring Framework 5.3开始,
	 *如果为给定的工厂类型发现一个特定的实现类名不止一次,重复的实现类名将被忽略
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

三、@ComponentScan 包扫描

@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决
定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项
目主程序启动类所在包的具体位置

四、总结 springboot底层实现自动配置的步骤

  1. springboot应用启动;
  2. @SpringBootApplication起作用;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
  5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories 和 .imports文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值