SpringBoot自动装配-源码分析
1. 简介
通过源码探究SpringBoot的自动装配功能。
2. 核心代码
2.1 启动类
我们都知道SpringBoot项目创建好后,会自动生成一个当前模块的启动类。如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestApplication {
   public static void main(String[] args) {
      SpringApplication.run(TestApplication.class, args);
   }
}
2.2 @SpringBootApplication
在启动类中有个很重要的注解@SpringBootApplication,在该注解中除了元注解,就是@SpringBootConfiguration
、@EnableAutoConfiguration、@ComponentScan
- @SpringBootConfiguration:标识了当前类为配置类
 - @ComponentScan:配置类的组件扫描
 - @EnableAutoConfiguration:激活自动装配
 
@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 {
	/**
	 * 排除特定的自动配置类,以便它们永远不会被应用
	 */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  Class<?>[] exclude() default {};
  /**
	 * 排除特定的自动配置类名称,以便它们永远不会被
	 */
  @AliasFor(annotation = EnableAutoConfiguration.class)
  String[] excludeName() default {};
  /**
   * 用于扫描带注释组件的基本包。
   */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  String[] scanBasePackages() default {};
  /**
   * 用于指定要扫描带注释组件的包。将扫描指定的每个类的包。
   */
  @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  Class<?>[] scanBasePackageClasses() default {};
  ...
}
2.3 @EnableAutoConfiguration
这里我们重点看@EnableAutoConfiguration注解。
在该注解中我们看到了熟悉的@Import注解,并且该注解指定导入了AutoConfigurationImportSelector.class
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  Class<?>[] exclude() default {};
  String[] excludeName() default {};
}
2.4 AutoConfigurationImportSelector
我们进入到AutoConfigurationImportSelector.class,看到当前类继承自DeferredImportSelector接口,而通过查看DeferredImportSelector源码 public interface DeferredImportSelector extends ImportSelector {}得知,DeferredImportSelector继承自ImportSelector接口。因此我们大概得知SpringBoot默认装载了ImportSelector::selectImports()方法返回的全限类名数组。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  /**
   * 重写ImportSelector接口中的selectImports方法
   * <p>
   *    该方法返回的数组<全限类名> 都将被装载到IOC容器
   */
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    // 将符合注入IOC条件的Bean类信息返回
		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, exclusions);
    // 从候选配置集合中排除需要排除的项
		configurations.removeAll(exclusions);
    // 获取在spring.factories中注册的过滤器,并执行filter方法,返回符合注册条件的元素
		configurations = getConfigurationClassFilter().filter(configurations);
    // 触发自动配置导入事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回自动配置和排除项信息
		return new AutoConfigurationEntry(configurations, exclusions);
	}
  /**
   * 获取属性
   */
  protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
		String name = getAnnotationClass().getName();
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
		Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
				+ " annotated with " + ClassUtils.getShortName(name) + "?");
		return attributes;
	}
  /**
   * 获取候选的配置信息
   */
  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
    // 这个就很重要了,从这里大概可以判断出 配置信息是从META-INF/spring.factories这个文件中获取到的
		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;
	}
}
2.5 SpringFactoriesLoader
为了验证配置信息是不是从META-INF/spring.factories获取的,我们继续跟踪源码SpringFactoriesLoader::loadFactoryNames()
public final class SpringFactoriesLoader {
	/**
	 * 工厂资源位置
	 *
	 * <p>
	 *     可以存在于多个Jar文件中
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
	/**
	 * 加载工厂名称
	 *
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
    // 当前上下文中 factoryTypeName = EnableAutoConfiguration注解的全限类名
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}
  /**
   * 加载spring工厂
   */
	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 {
      // 获取 META-INF/spring.factories 枚举信息
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
        // spring.factories 文件地址
				URL url = urls.nextElement();
        // 获取resource信息
				UrlResource resource = new UrlResource(url);
        // 加载配置文件中的配置信息
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        // 遍历配置信息放入全局的Map缓存中
				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;
	}
}
在这里为了更方便的查看loadSpringFactories中各步骤是用来干嘛的,特意添加debug截图如下:

2.6 spring.factories
spring-boot-autoconfigure下的META-INF/spring.factories文件信息

从上图中我们能看出spring.factories 中指定了很多常用中间件的auto configure文件信息。
2.7 RedisAutoConfiguration
我们仅查看我们比较熟悉的redis中间件的autoconfiguration文件信息
从RedisAutoConfiguration源码中我们能看出在文件中使用很多的@Conditional注解来实现注入符合条件的SpringBean
// 标识为配置类
@Configuration(proxyBeanMethods = false)
// 当存在RedisOperations.class时注入当前类
@ConditionalOnClass(RedisOperations.class)
// 激活RedisProperties属性文件
@EnableConfigurationProperties(RedisProperties.class)
// 导入客户端配置类
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
  @Bean
  // 当 当前环境中没有redisTemplate Bean时注入当前Bean
  @ConditionalOnMissingBean(name = "redisTemplate")
  /*
   * 当指定RedisConnectionFactory类已存在于 BeanFactory 中,并且可以确定单个候选项才会匹配成功。
   * 或者 BeanFactory 存在多个 RedisConnectionFactory 实例,但是有一个 primary 候选项被指定(通常在类上使用 @Primary 		 * 注解),也会匹配成功
   */
  @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
3. 小结
至此我们大概了解了SpringBoot是如何实现自动装配的。
- 项目启动
 - 通过启动类上的
@SpringBootApplication注解加载@EnableAutoConfiguration注解 - 通过
@EnableAutoConfiguration加载@Import(AutoConfigurationImportSelector.class)执行AutoConfigurationImportSelector导入选择器 - 在
AutoConfigurationImportSelector中执行selectImports()方法 AutoConfigurationImportSelector::selectImports()通过加载ClassPath下的META-INF/spring.factories文件来动态的注入*AutoConfiguration类- *AutoConfiguration类中通过使用
@Conditional注解及其派生注解实现了Bean的灵活装载。 
SpringBoot自动装配-源码分析的更多相关文章
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
		
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
 - SpringBoot启动代码和自动装配源码分析
		
 随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三 ...
 - 原创001 | 搭上SpringBoot自动注入源码分析专车
		
前言 如果这是你第二次看到师长的文章,说明你在觊觎我的美色!O(∩_∩)O哈哈~ 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 本系列为SpringBoot深度源码专车系列,第一篇发车 ...
 - SpringBoot自动装配源码解析
		
序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...
 - SpringBoot自动装配源码
		
前几天,面试的时候被问到了SpringBoot的自动装配的原理.趁着五一的假期,就来整理一下这个流程. 我这里使用的是idea创建的最简单的SpringBoot项目. 我们都知道,main方法是jav ...
 - SpringBoot自动配置源码调试
		
之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...
 - Spring Boot 自动配置 源码分析
		
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
 - springboot整合mybatis源码分析
		
springboot整合mybatis源码分析 本文主要讲述mybatis在springboot中是如何被加载执行的,由于涉及的内容会比较多,所以这次只会对调用关系及关键代码点进行讲解,为了避免文章太 ...
 - tomcat源码--springboot整合tomcat源码分析
		
1.测试代码,一个简单的springboot web项目:地址:https://gitee.com/yangxioahui/demo_mybatis.git 一:tomcat的主要架构:1.如果我们下 ...
 
随机推荐
- Python3中最常用的5种线程锁你会用吗
			
前言 本章节将继续围绕threading模块讲解,基本上是纯理论偏多. 对于日常开发者来讲很少会使用到本章节的内容,但是对框架作者等是必备知识,同时也是高频的面试常见问题. 官方文档 线程安全 线程安 ...
 - vsftpd.conf配置文件详解
			
1.默认配置: 1>允许匿名用户和本地用户登陆. anonymous_enable=YES local_enable=YES 2>匿名用户使用的登陆名为ftp或anonymous,口令为空 ...
 - Linux云计算-03_必备基础命令
			
Linux系统启动默认为字符界面,一般不会启动图形界面,所以对命令行的熟练程度能更加方便.高效的管理Linux系统. 本章介绍Linux系统必备命令各项参数及功能场景,Linux常见命令包括:cd.l ...
 - Linux使用shell脚本监控
			
(1)性能监控脚本 performance.sh #!/bin/bash #-------------------------------------------------------------- ...
 - redis-cluster集群安装(windows)
			
在此先奉上安装包(链接:https://pan.baidu.com/s/1QHYQPkYPuiRWhdj9APbjnw 提取码:jv8x ) 1. 安装ruby 下载 rubyinstaller-2. ...
 - SpringMVC的注解关键词解释
			
SpringMVC的注解关键词解释 @Controller控制器定义 和Struts1一样,Spring的Controller是Singleton的.这就意味着会被多个请求线程共享.因此,我们将控制器 ...
 - 15 shell for循环
			
除了 while 循环和 until 循环,Shell 脚本中还有for 循环,for 循环有两种使用形式:C语言风格的for循环与Python语言风格的for in循环,两种形式的for循环用法对比 ...
 - mac  sudo: /etc/sudoers is world writable
			
今天误操作修改了/etc/sudoers的权限,将它的权限改成了777,结果就导致执行所有sudo的命令都报错. sudo: /etc/sudoers is world writable sudo: ...
 - 2012年第三届蓝桥杯C/C++程序设计本科B组省赛题目 海盗比酒量 结果填空
			
** 一.题目 ** 海盗比酒量 有一群海盗(不多于20人),在船上比拼酒量.过程如下:打开一瓶酒,所有在场的人平分喝下,有几个人倒下了.再打开一瓶酒平分,又有倒下的,再次重复- 直到开了第4瓶酒,坐 ...
 - jar\war\SpringBoot加载包内外资源的方式,告别FileNotFoundException吧
			
工作中常常会用到文件加载,然后又经常忘记,印象不深,没有系统性研究过,从最初的war包项目到现在的springboot项目,从加载外部文件到加载自身jar包内文件,也发生了许多变化,这里开一贴,作为自 ...