浅析SpringBoot-自动装配的实现

基于Springboot 2.2.4.RELEASE、jdk8

前言

​ 最近在进行spring cloud alibaba的学习,由于cloud是建立在boot之上的,所以决定先把一知半解的boot原理弄懂,其中最重要的便是“自动装配”这个概念。boot这个巧妙的设计,让java程序员脱离了各种xml配置的苦海,那它是如何在程序启动的时候,自动装载我们需要的bean到容器里面的呢?源码里有一切我们想要的答案~~~

先导知识

​ 要想了解boot自动装配的实现,得先了解下几个注解。方便后面阅读源码。

1. @ConfigurationProperties

​ 标有此注解的类的所有属性,会自动和配置文件中的配置项进行绑定(默认从全局配置文件中获取配置值)。

​ 如配置@ConfigurationProperties(prefix="spring.redis"),则表示此类中的属性默认从配置文件中的spring.redis下对应配置的属性值获取。

2.@Import

将符合条件的的bean,注入到IoC容器中。详细可参考https://mp.weixin.qq.com/s/dNOBwMPHKdccmeJFWzzTOg

在spring4.2之前只支持导入配置类。在4.2之后支持导入普通的java类,并将其声明成一个bean。

​ Import的3种使用方式分别是:

  1. 直接导入普通java类

    @Import({User.class})

  2. 配合自定义的ImportSelector使用,只有当条件满足时才注入bean

    @Import({MyUserSelector.class})

  3. 配合ImportBeanDefinitionRegistrar使用

    在第一点中,直接把类注入到IOC容器中,只能调用类的无参构造函数,如果想对类进行个性化定制,就可以用此方式,手动将bean注册到容器中(需配合@Configuration使用)。

3.@Conditional

该注解可以实现满足一定条件时,才启用配置。

常用的一些扩展注解有:

ConditionalOnBean :容器中存在指定 Bean,则生效。

ConditionalOnMissingBean:容器中不存在指定 Bean,则生效。

ConditionalOnClass: 系统中有指定的类,则生效。

ConditionalOnMissingClass: 系统中没有指定的类,则生效。

ConditionalOnProperty: 系统中指定的属性是否有指定的值。

ConditionalOnWebApplication: 当前是web环境,则生效。

自动装配的实现

​ 自动装配在springboot中是通过@EnableAutoConfiguration注解来开启的,在启动类的注解@SpringBootApplication中就声明了这个注解。

@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 {
  //...
}

​ 这里对@Enable注解进行一下说明,在spring3.1版本就已经开始支持@Enable注解了,主要作用就是把相关组件的Bean装配到IoC容器中。在此之前,使用基于JavaConfig的形式来完成Bean的装载,则必须使用@Configuration及@Bean。而@Enable本质上是针对这两个注解的封装,所以不难发现@Enable系列的注解都会携带一个@Import注解,Spring在解析到@Import注解的时候,会根据Import导入的配置类来实现Bean的装配。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  //...
}

​ AutoConfigurationImportSelector类实现了ImportSelector接口,它只有一个selectImports的抽象方法,返回一个string数组,这个数据返回的类最终会被装配到IoC容器中。和@Configruation不同的是,ImportSelector可实现批量装配,并且还可以通过逻辑处理来选择要装配的类,也就是说可以根据上下文来决定哪些类能被IoC容器初始化。

public interface ImportSelector {

   String[] selectImports(AnnotationMetadata importingClassMetadata);

   @Nullable
   default Predicate<String> getExclusionFilter() {
      return null;
   }

}

​ 接下来看一下AutoConfigurationImportSelector.class的具体实现。先进入selectImports方法,此方法也是ImportSelector的核心方法,主要用于批量获取需要注入的配置类。

//主要看这个方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
  //这里获取自动配置的信息条目,返回最终需要的配置类信息
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

​ getAutoConfigurationEntry是其中主要的方法,用于获取实际可以注入的配置类。

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);
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

​ getCandidateConfigurations会调用SpringFactoriesLoader.loadFactoryNames去META-INF/spring.factories配置文件下获取key为EnableAutoConfiguration的配置信息回来。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//getSpringFactoriesLoaderFactoryClass()获取回来的正是EnableAutoConfiguration,在loadFactoryNames会从META-INF/spring.factories配置中加载回来的map中,找到key为EnableAutoConfiguration的配置类作为候选返回list。
  List<String> configurations = 
 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
  //默认从META-INF/spring.factories拿配置,体现了springboot的核心思想“约定优于配置”。
   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;
}

SpringFactoriesLoader.loadFactoryNames就是实际去配置文件META-INF/spring.factories获取所有配置类信息回来的方法。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
  //getOrDefault(factoryTypeName, Collections.emptyList())获取的就是key为EnableAutoConfiguration的map
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  //...
   try {
     //这里就是实际获取META-INF/spring.factories路径的地方
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      //....
      return result;
   }
  //...
}

​ 以下是源码走读的顺序图

springboot自动装配代码顺序.jpg

参考博文

https://juejin.im/post/5ce5effb6fb9a07f0b039a14#heading-11

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352