在上一篇博客中分析了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. Web前端学习,需用了解的7大HTML知识点

    HTML是web前端开发基础,关于HTML,这里有几个很重要的知识点,在日常开发常常用到,并且在大家面试的时候也会问的,记住这7个重要知识点,助你在面试时优先录用. 1.网页结构 网页结构一般都包含文 ...

  2. 无 PowerShell.exe 执行 Empire 的几种姿势

    在实战中,Empire成为域渗透.后渗透阶段一大利器,而Empire是一个Powershell RAT,所以PowerShell必须要能运行Empire中几乎所有的启动方法都依赖于使用PowerShe ...

  3. 浏览器渗透框架BeEF使用笔记(一)

    0x00 前言 BeEF,全称The Browser Exploitation Framework,是一款针对浏览器的渗透测试工具. 用Ruby语言开发的,Kali中默认安装的一个模块,用于实现对XS ...

  4. Ubuntu 19.10 发布 | 云原生生态周报 Vol. 24

    作者 | 木苏.进超.冬岛.元毅.心水.衷源 业界要闻 1.云原生编程语言 Pulumi 1.0 pulumi ,一款中立的开源云开发平台,Pulumi 支持多语言.混合云环境.完全可扩展.初期支持 ...

  5. java 线程池那点事儿

    1.为什么要用线程池 2.常见线程池以及参数 2.1 创建线程池 2.2 线程池参数 2.3 常见线程池 3.执行流程 4.健康检查 1.为什么要用线程池 线程池提供了一种任务的提交与任务的执行解偶的 ...

  6. Javascript字符串常用方法详解

    字符串 字符串就是一个或多个排列在一起的字符,放在单引号或双引号之中. 'abc'"abc" length属性 js里的字符串类似于数组,都是一个一个字符拼凑在一起组成的,因此可以 ...

  7. 【Autofac打标签模式】Component和Autowired

    [ Autofac打标签模式]开源DI框架扩展地址: https://github.com/yuzd/Autofac.Annotation/wiki Componet标签把类型注册到DI容器 1. 把 ...

  8. NVDLA中Winograd卷积的设计

    在AI芯片:高性能卷积计算中的数据复用曾提到,基于变换域的卷积计算--譬如Winograd卷积--并不能适应算法上对卷积计算多变的需求.但Winograd卷积依旧出现在刚刚公开的ARM Ethos-N ...

  9. rem辅助式响应布局

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. MySQL数据库的安装与配置(windows)

    MySQL是目前最为流行的开放源码的数据库,是完全网络化的跨平台的关系型数据库系统,它是由瑞典MySQLAB公司开发,目前属于Oracle公司.任何人都能从Internet下载MySQL软件,而无需支 ...