springboot自动装配原理浅析

在梳理自动配置原理之前我们要怀着这样两个疑问,才能更好的理解自动配置流程原理

  • 为什么我们不用配置包扫描,但是我们的组件依旧可以被注册?
  • 为什么我们一启动springboot项目,它会帮我们自动生成那么多组件?

1、SpringBoot主程序启动类

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

@SpringBootApplication修饰的就是springboot主程序启动类,每次我们直接直接启动这个启动类,SpringBoot就启动成功了,并且帮我们配置了好多自动配置类。

一切原因都是因为 @SpringBootApplication 这个注解,我们来查看一下该注解。

2、@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 {

这里只进行解释自动配置的流程原理,不看元注解,其中除了元注解,这三个注解@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan就显得很重要了,当然这三个注解中也是有轻有重的,先简单说明三个注解的作用,然后再详细说明

  1. @SpringBootConfiguration:表示被改注解修饰的类是一个配置类,当然,因为注解是具有传递性的,也就意味着,被@SpringBootApplication注解修饰的类是一个配置类
  2. @EnableAutoConfiguration:开启自动配置的注解,这正是自动配置的精华所在
  3. @ComponentScan:包扫描注解,源码之中,这是一个 @ComponentScan注解的高级使用方式,这里并不影响我们理解自动配置的流程,所以不做解释。

由此我们可以看到如果我们需要理解自动配置原理,那么这其中最重要的注解就是@EnableAutoConfiguration

3、@EnableAutoConfiguration

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

同样的,我们不需要关注元注解,我们只需要看@AutoConfigurationPackage和@Import注解。
同样的,我们先简单说明这两个注解的作用,然后再详细解释

  1. @AutoConfigurationPackage:自动配置包注解,用于给包结构下的所有组件进行注册,详细查看这个注解就能明白,为什么我们不需要配置包扫描就可以注册组件了
  2. @Import:导入自动配置组件

4、@AutoConfigurationPackage

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

可以看到,其实这个注解就是导入了一个这个类型的AutoConfigurationPackages.Registrar.class组件,我们来看这个组件的作用是什么

AutoConfigurationPackages.Registrar.class组件

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

	}

其实他就是批量注册了,主配置文件同级以及子集的组件,这个@Import(AutoConfigurationPackages.Registrar.class)就是@import注解的一个高级用法,作用就是批量注册。
根据debug查看可以看到其实new PackageImports(metadata).getPackageNames()就是获取的当前被@SpringBootApplication注解修饰的类所在的包,然后它会根据这个包按照,前面说的,“注册同级及其自己的文件的组件”的规则进行注册。
在这里插入图片描述(根据上图可以看到:bean包、config包、controller包中的注解都可以被扫描到)
所以这时候我们就清晰的明白我们前面最开始提出的第一个疑问了:为什么我们不用自己配置包扫描还能自动被注册,正是因为这个@Import(AutoConfigurationPackages.Registrar.class)注解

5、@Import(AutoConfigurationImportSelector.class

这个注解同样是@Import注解的高级用法:批量注册。
那么它是如何个批量注册法呢,我们查看这个AutoConfigurationImportSelector

AutoConfigurationImportSelector.class组件

根据@import的高级用法——批量注册,这个类实现的最高父接口使用idea的关系树查看,可以看到,这个是使用实现ImportSelector接口的批量注册的方式
在这里插入图片描述ImportSelector这接口中的String[] selectImports(AnnotationMetadata importingClassMetadata);方法就是用于批量注册的方法,这个方法返回的数组就是我们要批量注册的组件的全类名,然后我们在AutoConfigurationImportSelector类中找到这个方法,如下

selectImports(AnnotationMetadata annotationMetadata)方法

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

简单看一下返回的值是根据autoConfigurationEntry.getConfigurations()进行转为字符串数组的,所以我们需要看一下autoConfigurationEntry 这个类使用getConfigurations()获取的Configurations是什么,记住我们要看的是什么,不然会因为看不懂源码而迷失自己。
所以我们需要查看getAutoConfigurationEntry(annotationMetadata)这个方法

getAutoConfigurationEntry(annotationMetadata)方法

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

我们可以看到,getAutoConfigurationEntry(annotationMetadata)方法中最终返回的new AutoConfigurationEntry(configurations, exclusions)中就传入了configurations,而这个configurations,就是我们前面获取的configurations

看这个行代码List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);见名知意,获取的就是候选配置,而候选配置都有什么,我们需要进一步查看

同样需要明确我们进入getCandidateConfigurations(annotationMetadata, attributes)这个方法要查看的是什么:getCandidateConfigurations(annotationMetadata, attributes)获取到的候选配置是什么?

getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)方法

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

我们看到返回的List<String> configurations是根据SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()这个方法获取的,我们继续深入查看这个方法

SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()方法

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

依旧是明确我们是来干什么的:查看SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());获取的List<String>到底是什么配置

所以我们需要在进入到loadSpringFactories(classLoader)查看

loadSpringFactories(classLoader)方法

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

		try {
			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()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这段源码太长了,容易使大家产生误会,所以不过多解释。只言简意赅的解释这个方法中的作用这个方法就是用于加载所有位于META-INF/spring.factories目录下的文件,debug查看可知
在这里插入图片描述其实这个目录下的文件有很多,但是最主要的是spring自动配置jar中的这个文件中,如下图
在这里插入图片描述它会去将# Auto Config中的内容加载为一个List<String>数组
在这里插入图片描述
所以回答上一个问题就是:
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());获取的List<String>到底是我们位于META-INF/spring.factories目录下的spring.factories文件中需要自动配置的全限定类名


然后我们回到最开始的需要看一下autoConfigurationEntry 这个类使用getConfigurations()获取的Configurations是什么这个问题,这个类就是用于加载,自动配置jar的META-INF/spring.factories目录下的spring.factories文件中的全限定类名的
debug验证
在这里插入图片描述
然后最最最重要的一步就是springboot 的精髓按需生效,实现按需生效的注解就是@Conditional这一系列注解
,如下图
在这里插入图片描述这里不做一一解释了,这个注解的作用就是满足条件才生效


总结

由此springboot的自动配置就再次分析了一边,这一边显然比第一遍要更加清晰了,总结如下

  1. SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
  2. @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件。
  3. 可以查看public String[] selectImports(AnnotationMetadata annotationMetadata)方法的内容。
  4. 通过protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)获取候选的配置,这个是扫描所有jar包类路径下"META-INF/spring.factories";
  5. 然后把扫描到的这些文件包装成Properties对象。
  6. 从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中。
  7. 整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中。
  8. 每一个这样XXAutoConfiguration类都是容器中的一个组件都加入到容器中,用他们来做自动配置。每一个自动配置类进行自动配置功能,以HttpEncodingAutoConfiguration为例解释自动配置原理。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
  1. 根据当前不同的条件判断,决定这个配置是否生效。

参考文档如下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值