在上一篇博客中分析了springBoot启动流程,大体的轮廓只是冰山一角。今天就来看一下springBoot的亮点功能:自动化装配功能。

先从@SpringBootApplication开始。在启动流程章节中,我们讲述了SpringBoot2大致的启动步骤,并进行了源码详解。但是在刷新容器这块并未展开,refreshContext(context);简单的一行代码,背后却做了太多事情。所以为了不喧宾夺主,本篇也尽量选取和注解@SpringBootApplication有关的方法讲解。

springBoot启动类加载

首先加载springBoot启动类注入到spring容器中beanDefinitionMap中,看下prepareContext方法中的load方法:load(context, sources.toArray(new Object[0]));
跟进该方法最终会执行BeanDefinitionLoader的load方法:

private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//如果是class类型,启用注解类型
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//如果是resource类型,启用xml解析
if (source instanceof Resource) {
return load((Resource) source);
}
//如果是package类型,启用扫描包,例如:@ComponentScan
if (source instanceof Package) {
return load((Package) source);
}
//如果是字符串类型,直接加载
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

继续跟进load(Class<?> source)方法:

上述方法判断启动类中是否包含@component注解,可我们的启动类并没有该注解。继续跟进会发现,AnnotationUtils判断是否包含该注解是通过递归实现,注解上的注解若包含指定类型也是可以的。

启动类中包含@SpringBootApplication注解,进一步查找到@SpringBootConfiguration注解,然后查找到@Component注解,最后会查找到@Component注解:

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

在查找到@Component注解后,表面该对象为spring bean,然后会将其信息包装成 beanDefinitaion ,添加到容器的 beanDefinitionMap中。如下:

如此一来,我们的启动类就被包装成AnnotatedGenericBeanDefinition了,后续启动类的处理都基于该对象了。

@EnableAutoConfiguration

@SpringBootApplication注解中包含了自动配置的入口注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

我们跟进去看看@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

@AutoConfigurationPackage

  • 自动配置包注解
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

@Import(AutoConfigurationPackages.Registrar.class):默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。如下

@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
register(registry, new
PackageImport(metadata).getPackageName());
} @Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.<Object>singleton(new PackageImport(metadata));
}
}

@Import(EnableAutoConfigurationImportSelector.class)

EnableAutoConfigurationImportSelector: 导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

 //EnableAutoConfigurationImportSelector的父类:AutoConfigurationImportSelector
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}

我们主要看第11行List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);会给容器中注入众多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件。获取这些组件后,还要过滤一下这些组件,我们跟进去看看

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
//...
return configurations;
} protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
} public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//从类路径的META-INF/spring.factories中加载所有默认的自动配置类
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
//获取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值
String factoryClassNames =
properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

SpringBoot启动的时候从类路径下的 META-INF/spring.factories中获取EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中,自动配置类就会生效,最后完成自动配置工作。EnableAutoConfiguration默认在spring-boot-autoconfigure这个包中,如下图

最终有96个自动配置类被加载并注册进Spring容器中

我们也可以将需要自动配置的Bean写入这个文件

自定义starter

首先定义一个配置类模块:

@Configuration
@ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
public class MyAutoConfiguration { static {
System.out.println("myAutoConfiguration init...");
} @Bean
public SimpleBean simpleBean(){
return new SimpleBean();
} }

然后定义一个starter模块,里面无需任何代码,pom也无需任何依赖,只需在META-INF下面建一个 spring.factories文件,添加如下配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.study.startup.bean.MyAutoConfiguration

如图所示:

最后只需在启动类项目的pom中引入我们的 starter 模块即可。

原理

最终在AutoConfigurationImportSelector解析spring.factories文件:

springBoot为我们提供的配置类有180多个,但是我们不可能会全部引入。按条件注解 @Conditional或者@ConditionalOnProperty等相关注解进行判断,决定是否需要装配。

我们自定义的配置类也是以相同的逻辑进行装配,我们指定了以下注解:

@ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)

默认为 true,所以自定义的starter成功执行。

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析的更多相关文章

  1. Spring Boot之实现自动配置

    GITHUB地址:https://github.com/zhangboqing/springboot-learning 一.Spring Boot自动配置原理 自动配置功能是由@SpringBootA ...

  2. 4、Spring Boot 2.x 自动配置原理

    1.4 Spring Boot 自动配置原理 简介 spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate ...

  3. Spring Boot面试杀手锏————自动配置原理

    转:https://blog.csdn.net/u014745069/article/details/83820511 引言不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技 ...

  4. 学记:为spring boot写一个自动配置

    spring boot遵循"约定优于配置"的原则,使用annotation对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来.spring boot的神奇 ...

  5. Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置

    搞 Spring Boot 的小伙伴都知道,Spring Boot 中的配置文件有两种格式,properties 或者 yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样 ...

  6. Spring Boot框架的自动配置

    (图片来源于网络,侵删!!!) l  @RestController 因为我们例子是写一个web应用,因此写的这个注解,这个注解相当于同时添加@Controller和@ResponseBody注解 l ...

  7. SpringBoot自动配置注解原理解析

    1. SpringBoot启动主程序类: @SpringBootApplication public class DemoApplication { public static void main(S ...

  8. Spring Boot REST(二)源码分析

    Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...

  9. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo | grep tid | grep -v "daemon" tid ...

随机推荐

  1. 拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

    1.引言 老读者应该还记得我在去年国庆节前分享过一篇<技术干货:从零开始,教你设计一个百万级的消息推送系统>,虽然我在文中有贴一些伪代码,依然有些朋友希望能直接分享一些可以运行的源码.好吧 ...

  2. 渗透-N种反弹shell方法

    简介 reverse shell反弹shell或者说反向shell,就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端.reverse shell与teln ...

  3. [NOIp2009] luogu P1071 潜伏者

    翘课间操和体育课来水博客. 题目描述 CCF的题面贼长,但貌似除了背景以外,每句话都删不掉.不写啦,反正也是Ctrl C的. Solution 显然这是一道码农题. #include<cstdi ...

  4. [Luogu3797] 妖梦斩木棒

    题目背景 妖梦是住在白玉楼的半人半灵,拥有使用剑术程度的能力. 题目描述 有一天,妖梦正在练习剑术.地面上摆放了一支非常长的木棒,妖梦把它们切成了等长的n段.现在这个木棒可以看做由三种小段构成,中间的 ...

  5. opencv::基本阈值操作

    图像阈值(threshold) 阈值 是什么?简单点说是把图像分割的标尺,这个标尺是根据什么产生的,阈值产生算法?阈值类型.(Binary segmentation) 阈值类型一阈值二值化(thres ...

  6. css布局两端固定中间自适应

    第一种:采用浮动 1.1首先来看一下网上一个哥们给的代码 <body> <div class="left">左</div> <div cl ...

  7. 百万年薪python之路 -- 函数名的第一类对象及使用

    函数名是一个变量, 但它是一个特殊的变量, 与括号配合可以执行函数的变量 1.1.函数名的内存地址 def func(): print("呵呵") print(func) 结果: ...

  8. C加加学习之路 1——开始

    C++是一门古老而复杂的语言,绝不是一门可以速成的语言,学习它需要有意识的刻意练习和长时间的持续不断的磨练.而大多数人不太能耐得住寂寞,喜欢速成,所以像<21天学通C++>这种书就比较受欢 ...

  9. C#3种常见的定时器(多线程)

    总结以下三种方法,实现c#每隔一段时间执行代码: 方法一:调用线程执行方法,在方法中实现死循环,每个循环Sleep设定时间: 方法二:使用System.Timers.Timer类: 方法三:使用Sys ...

  10. Vue---mock.js 使用

    mockjs 概述 在我们的生产实际中,后端的接口往往是较晚才会出来,并且还要写接口文档,于是我们的前端的许多开发都要等到接口给我们才能进行,这样对于我们前端来说显得十分的被动,于是有没有可以制造假数 ...