加载中...

SpringBoot的自动装配


环境说明

本文是基于SpringBoot 2.5.2版本

从入口说起

@SpringBootApplication
public class StaffingSystemApplication {

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

可以看到,SpringBoot项目中主要就是这个@SpringBootApplication注解,这个注解做了什么呢?我们继续往下看

@SpringBootApplication注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   .......
}

1、@SpringBootConfiguration:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
   ......
}

可以看到,这个注解主要就是一个Configuration,用来标明SpringBootApplication是一个配置类

2、@ComponentScan显然就是一个包扫描的配置,用于扫描注册bean,默认会扫描启动类所在的包下的类,并默认排除TypeExcludeFilter和AutoConfigurationExcludeFilter两种自定义的bean类

3、自动装配的重点在这个@EnableAutoConfiguration注解上,核心代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ......
}

可以看到,这个注解上又有两个关键注解@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class),首先@AutoConfigurationPackage的核心代码又如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

这个AutoConfigurationPackages.Registrar.class是一个静态类,在源码的注释上有一段话:

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */

说明这个类主要是用来将@SpringBootApplication所在的包及其子包下的所有bean扫面注册到容器中

所以还有一个关键的注解:@Import(AutoConfigurationImportSelector.class),这个AutoConfigurationImportSelector类的继承体系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

AutoConfigurationImportSelector实现了ImportSelector接口,这个接口的主要作用是:

/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 * @return the class names, or an empty array if none
	 */

用中文理解大致就是指:

获取所有符合条件的bean类的全限定类名,这些类需要被加载到 IoC 容器中

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

因此,我们要重点看一下这个getAutoConfigurationEntry方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    //判断配置文件中是否禁用了自动装配,默认spring.boot.enableautoconfiguration=true,
    //可在 application.properties 或 application.yml 中设置
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
    //获取注解中的属性
    //debug下发现是获取@EnableAutoConfiguration注解中的 exclude 和 excludeName
		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);
	}

上述getAutoConfigurationEntry方法主要有以下几个关键点:

1、在(*)处,getCandidateConfigurations方法内部的核心代码为:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		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;
	}

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ......
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        ......

            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
            }
        ......
}

可见,自动装配是通过读取META-INF/spring.factories文件来得到可能要加载的类的,所有的starter底下的这个文件都会被读取到

(后面两处代码也不少,这里就不放代码了,就说说具体的思路)

2、在(**)处,通过之前得到的exclude 和 excludeName,以及spring.autoconfigure.exclude配置的内容,移除一些不需要加载到容器的类

3、在(***)处再次筛选,getConfigurationClassFilter()方法得到一系列AutoConfigurationImportFilter,这个filter接口的实现类有OnBeanConditionOnClassCondition等,而注解@ConditionalOnBean、@ConditionalOnClass等分别引用了这两个类,这样用这两个注解标识的类将会在容器内有指定bean、类路径有指定class的情况下才会注入到容器中,类似的注解其实还有很多,如@ConditionalOnMissingBean、@ConditionalOnMissingClass、@ConditionalOnProperty等,都是基于上述的原理

简而言之,就是@ConditionalOnXXX中需要满足指定的条件才能让该类注入到容器

自动配置总结

SpringBoot自动配置主要是通过@EnableAutoConfiguration注解实现的,通过导入ImportSelector接口的实现类AutoConfigurationImportSelector,加载META-INF/spring.factories文件下的类,并通过exclude筛选掉不需要加载的类,以及用@Conditional注解按需加载配置类。

因此也可以了解到,如果要自己实现一个starter自动注入,就需要在META-INF/spring.factories文件写入对应的类


文章作者: DestiNation
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DestiNation !
  目录