SpringBoot自动装配原理详解

 更新时间:2021年03月09日 09:22:28   作者:Java伴我余生  
这篇文章主要介绍了SpringBoot自动装配原理的相关资料,帮助大家更好的理解和学习使用SpringBoot框架,感兴趣的朋友可以了解下

首先对于一个SpringBoot工程来说,最明显的标志的就是 @SpringBootApplication它标记了这是一个SpringBoot工程,所以今天的 SpringBoot自动装配原理也就是从它开始说起。

自动装配流程

首先我们来看下@SpringBootApplication 这个注解的背后又有什么玄机呢,我们按下 ctrl + 鼠标左键,轻轻的点一下,此时见证奇迹的时刻..
我们看到如下优雅的代码:

这其中有两个比较容易引起我们注意的地方,一个是@SpringBootConfiguration注解,另一个是@EnableAutoConfiguration注解;之所以说这个两个注解比较吸引我们的眼球, 不是因为它们长大的好看,而是因为其他的注解太难看了(主要是因为其他的注解我们都是比较熟悉,即使不知道他们是干什么的,可以肯定更自动装配是没有关系的)。 然后我们又伸出了邪恶的小手,开启了熟悉的操作,按下了Ctrt + 鼠标左键,瞪着色咪咪的小眼睛,瞳孔放大了百倍等待着奇迹的出现... 擦... 擦...擦...

什么也没有...
那我要你有何用,这么顶级的世界级的开源项目,怎么会让一个没用的家伙存在呢? 于是动用了上亿的脑细胞大军,经过复杂的运算,得出了一个不靠谱的结论:它可能使用来标记这是一个SpringBoot工程的配置。因为SpringBootConfiguration翻译过来就是SpringBoot的配置,于是心中又是几万只羊驼在万马奔腾,大漠飞扬。

气定神闲之后,秉承着·失败是成功之母"的信念, 熟练的左手行云流水般的按下了 Ctrl + Table 键,回到了最初的的地方。眼睛盯着 @EnableAutoConfiguration ,环顾左右,在地址栏输入了谷歌翻译, 结果显示 自动装配。我找的就是你,真是众里寻他千百度,那人却在灯火阑珊处。 熟练的按下了 Ctrl +左键,迫不及待的想要进入; 心里默默背诵起了《桃花源记》的经典诗句 ∶

林尽水源,便得一山,山有小口,仿佛若有光。便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗

此时此刻心情愉悦,有过前面的经历之后,在面对新的世界时候,我们淡定了许多。 此时大脑高速运转,没有再纠结,直捣黄龙,进入了 AutoConfigurationImportSelector.class 类,因为谷歌翻译告诉我们,这个是自动配置导入选择器。 于是我们发现了—片新天地

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


	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
    // 获取自动配置的实体
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

  // 具体用来加载自动配置类得方法
	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, );
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回最终需要的配置
		return new AutoConfigurationEntry(configurations, exclusions);
	}
}

而这个自动配置的实体 AutoConfigurationEntry里面有两个属性,configurationsexclusions

	protected static class AutoConfigurationEntry {
    // 用来存储需要的配置项
		private final List<String> configurations;
    // 用来存储排除的配置项
		private final Set<String> exclusions;

		private AutoConfigurationEntry() {
			this.configurations = Collections.emptyList();
			this.exclusions = Collections.emptySet();
		}
  }

在后面可以看到 getAutoConfigurationEntry()方法返回了一个对象 return new AutoConfigurationEntry(configurations, exclusions);这里也就是把我们需要的配置都拿到了。

那他是怎么拿到的候选的配置类呢? 我们接着看这个获取候选配置类的方法 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
进到方法后我们看到下面这个方法具体获取候选配置类的方法内容

这里我们跟着断点去走,首先进入getSpringFactoriesLoaderFactoryClass()方法

	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    // 返回的是EnableAutoConfiguration字节码对象
		return EnableAutoConfiguration.class;
	}

接着我们在进入getBeanClassLoader()方法,这里就是一个类加载器

protected ClassLoader getBeanClassLoader() {
		return this.beanClassLoader;
	}

最后我们在进入loadFactoryNames()方法,这个方法就是根据刚才的字节码文件和类加载器来找到候选的配置类。传递过来的字节码

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
    // 获取的EnableAutoConfiguration.class的权限定名
    //org.springframework.boot.autoconfigure.EnableAutoConfiguration
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

如下图:

最后通过loadSpringFactories()来获取到所有的配置类

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

这里我们要看下怎么从资源目录下 FACTORIES_RESOURCE_LOCATION 加载的。下面是加载配置文件的路径:

也就是项目启动的时候会去加载所有 META-INF 下的所有的 spring.factories 文件,我们搜一下这个这个文件,我搭建的是一个很简单的 SpringBoot 工程,它会去这几个 jar 里面找相关的配置类

但是最后自动装配的类是这个spring-boot-autoconfigure-2.4.3.RELEASE.jar

而根据EnabLeAutoConfiguration.class字节码加载的配置类就只有这118自动配置类

小结

实际上SpringBoot的自动装配原理,其实就是在项目启动的时候去加载META-INF下的 spring.factories 文件,好像也没有那么高大上。当然在启动的过程中还会有其他的配置项的加载,这里咱么直说了自动装配的加载过程。希望对大家可以有所启发。

以上就是SpringBoot自动装配原理详解的详细内容,更多关于SpringBoot自动装配原理的资料请关注脚本之家其它相关文章!

相关文章

  • CountDownLatch和Atomic原子操作类源码解析

    CountDownLatch和Atomic原子操作类源码解析

    这篇文章主要为大家介绍了CountDownLatch和Atomic原子操作类的源码解析以及理解应用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • ssm框架下web项目,web.xml配置文件的作用(详解)

    ssm框架下web项目,web.xml配置文件的作用(详解)

    下面小编就为大家带来一篇ssm框架下web项目,web.xml配置文件的作用(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • JAVA实战项目实现客户选购系统详细流程

    JAVA实战项目实现客户选购系统详细流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用Java实现一个简单的客户选购系统,大家可以在过程中查缺补漏,提升水平
    2021-10-10
  • 解决HttpServletResponse和HttpServletRequest取值的2个坑

    解决HttpServletResponse和HttpServletRequest取值的2个坑

    这篇文章主要介绍了解决HttpServletResponse和HttpServletRequest取值的2个坑问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 详解SimpleDateFormat的线程安全问题与解决方案

    详解SimpleDateFormat的线程安全问题与解决方案

    这篇文章主要介绍了SimpleDateFormat的线程安全问题与解决方案,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-03-03
  • java实现响应重定向发送post请求操作示例

    java实现响应重定向发送post请求操作示例

    这篇文章主要介绍了java实现响应重定向发送post请求操作,结合实例形式分析了java请求响应、重定向及数据处理相关操作技巧,需要的朋友可以参考下
    2020-04-04
  • Spring Lifecycle的使用小结

    Spring Lifecycle的使用小结

    这篇文章主要介绍了Spring Lifecycle的使用,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • Java基本语法笔记(菜鸟必看篇)

    Java基本语法笔记(菜鸟必看篇)

    下面小编就为大家带来一篇Java基本语法笔记(菜鸟必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • java中计算字符串长度的方法及u4E00与u9FBB的认识

    java中计算字符串长度的方法及u4E00与u9FBB的认识

    字符串采用unicode编码的方式时,计算字符串长度的方法找出UNICODE编码中的汉字的代表的范围“\u4E00” 到“\u9FBB”之间感兴趣的朋友可以参考本文,或许对你有所帮助
    2013-01-01
  • Java扩展Nginx之共享内存

    Java扩展Nginx之共享内存

    这篇文章主要介绍了Java扩展Nginx之共享内存的相关资料,需要的朋友可以参考下
    2023-07-07

最新评论