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是如何实现自动装配的。

  1. 项目启动
  2. 通过启动类上的@SpringBootApplication注解加载@EnableAutoConfiguration注解
  3. 通过@EnableAutoConfiguration加载@Import(AutoConfigurationImportSelector.class)执行AutoConfigurationImportSelector导入选择器
  4. AutoConfigurationImportSelector中执行selectImports()方法
  5. AutoConfigurationImportSelector::selectImports()通过加载ClassPath下的META-INF/spring.factories文件来动态的注入*AutoConfiguration类
  6. *AutoConfiguration类中通过使用@Conditional注解及其派生注解实现了Bean的灵活装载。

SpringBoot自动装配-源码分析的更多相关文章

  1. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  2. SpringBoot启动代码和自动装配源码分析

    ​ 随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三 ...

  3. 原创001 | 搭上SpringBoot自动注入源码分析专车

    前言 如果这是你第二次看到师长的文章,说明你在觊觎我的美色!O(∩_∩)O哈哈~ 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 本系列为SpringBoot深度源码专车系列,第一篇发车 ...

  4. SpringBoot自动装配源码解析

    序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...

  5. SpringBoot自动装配源码

    前几天,面试的时候被问到了SpringBoot的自动装配的原理.趁着五一的假期,就来整理一下这个流程. 我这里使用的是idea创建的最简单的SpringBoot项目. 我们都知道,main方法是jav ...

  6. SpringBoot自动配置源码调试

    之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...

  7. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  8. springboot整合mybatis源码分析

    springboot整合mybatis源码分析 本文主要讲述mybatis在springboot中是如何被加载执行的,由于涉及的内容会比较多,所以这次只会对调用关系及关键代码点进行讲解,为了避免文章太 ...

  9. tomcat源码--springboot整合tomcat源码分析

    1.测试代码,一个简单的springboot web项目:地址:https://gitee.com/yangxioahui/demo_mybatis.git 一:tomcat的主要架构:1.如果我们下 ...

随机推荐

  1. Mysql优化(出自官方文档) - 第六篇

    Mysql优化(出自官方文档) - 第六篇 目录 Mysql优化(出自官方文档) - 第六篇 Optimizing Subqueries, Derived Tables, View Reference ...

  2. Room-数据持久化存储(入门)

    @ 目录 一.简单使用 1.Entity 2.Dao 3.DataBase 4.使用 二.参数解析 1.Entity 2.Dao 3.查询方式 总结 # 前言 官方简介: Room 持久性库在 SQL ...

  3. 一次 RocketMQ 顺序消费延迟的问题定位

    一次 RocketMQ 顺序消费延迟的问题定位 问题背景与现象 昨晚收到了应用报警,发现线上某个业务消费消息延迟了 54s 多(从消息发送到MQ 到被消费的间隔): 2021-06-30T23:12: ...

  4. R-聚类

    一.定义:将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类 二.距离:欧几里得度量(euclidean metric)也称欧氏距离       绝对值距离(manhattan)   ...

  5. Redis的flushall/flushdb误操作

    Redis的flushall/flushdb命令可以做数据清除,对于Redis的开发和运维人员有一定帮助,然而一旦误操作,它的破坏性也是很明显的.怎么才能快速恢复数据,让损失达到最小呢? 假设进行fl ...

  6. ABP Framework:移除 EF Core Migrations 项目,统一数据上下文

    原文:Unifying DbContexts for EF Core / Removing the EF Core Migrations Project 目录 导读:软件开发的一切都需要平衡 动机 警 ...

  7. HDU 4292 Food 多源多汇入门题

    Food 有F种食物和D种饮料,每种食物或饮料只能供有限次,且每个人只享用一种食物和一种饮料.现在有n个人,每个人都有自己喜欢的食物种类列表和饮料种类列表,问最多能使几个人同时享用到自己喜欢的食物和饮 ...

  8. 【Azure 应用服务】Azure Function App 执行PowerShell指令[Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt]错误

    问题描述 使用PowerShell脚本执行获取Azure订阅列表的指令(Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt).在本地 ...

  9. MIT6.828 Preemptive Multitasking(上)

    Lab4 Preemptive Multitasking(上) PartA : 多处理器支持和协作多任务 在实验的这部分中,我们首先拓展jos使其运行在多处理器系统上,然后实现jos内核一些系统功能调 ...

  10. linux学习之路第八天(linux文件权限详解)

    建议和我上一篇博客一起通读,效果更加 1.权限的基本介绍 通过一张图片解决疑惑(重点) rwx权限详解 rwx作用到文件 1)[r]代表可读(read) :可以读取,查看 2)[w]代表可写(writ ...