SpringBoot自动装配原理

Springboot自动装配过程

什么是Springboot自动装配?
我们使用ssm开发的时候,需要写spring.xml或者配置bean,需要配置<bean/>、@Bean把类交给工厂管理。而反观springboot开发的时候,只需要配置一些简单的.properties(.yml)就行了。其实springboot就是把这些我们以前要写的<bean/>、@Bean封装成启动器(starter)导入交给工厂了。

需要了解springboot的自动装配过程,必须从如下几个相关注解开始

  • @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 {
}

@Target({ElementType.TYPE}):注解可以标记在哪;
@Retention(RetentionPolicy.RUNTIME):注解标注的类编译以什么方式保留;
@Documented:生成文档信息的时候保留注解,对类作辅助说明;
@Inherited:继承关系;
@SpringBootConfiguration:Spring Boot的配置类;
@EnableAutoConfiguration:开启自动配置功能;
@ComponentScan(包路径):扫描包;
excludeFilters:排除策略;

如:@ComponentScan(basePackages = "com.not_lsj",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.not_lsj.controller.*"})})
扫描 com.not_lsj 包,但是把controller包下所有的类都排除掉,不参与扫描
  • @EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

}

@AutoConfigurationPackage:将当前配置类所在包保存在BasePackages的Bean中,供Spring内部使用。就是注册了一个bean,这个bean用来保存当前配置类的;
@Import({AutoConfigurationImportSelector.class}):导入当前类;

  • Conditional衍生注解
    作用:条件注解,必须是@Conditional指定的条件成立,才给容器中添加组件,组件才生效。
    列举几种常见的:

@ConditionalOnBean:容器中存在指定Bean
@ConditionalOnMissingBean:容器中不存在指定Bean
@ConditionalOnClass:系统中有指定的类
@ConditionalOnMissingClass:系统中没有指定的类
@ConditionalOnProperty:系统中指定的属性是否有指定的值
@ConditionalOnWebApplication:当前是web环境
......

顺便说一下,spring创建bean的几种方式:

  1. XML解析
  2. 注解(@Configuration、@Component(及其衍生注解)、@Bean)
  3. ClassPathBeanDefinitionScanner扫描特定路径下的具有注解的Bean 进行注册
  4. @import

在此介绍一下@Import注解:作用是导入某个类(一般用于封装)

A.class是一个普通类

  1. @Import(A.class):直接导入某个类

B implements importSelector

  1. @Import(B.class):一次导入多个类
    importSelector有一个子接口 DeferredImportSelector

C implements importBeanDefinitionRegister

  1. @Import(C.class):导入一个定制的类

Springboot就是用的@Import注解实现自动配置,实现了DeferredImportSelector接口

  • DeferredImportSelector

有两大特性

  1. 延迟加载:spring源码中解析@Import注解,都是再其他注解解析完后。
    如:某个Bean在容器中通过@Bean注册了,我们需要定制同一个Bean,这时就需要延迟加载了,因为会通过@ConditionOnBean..校验当前容器是否存在这个Bean,如果存在,我们定制的就不生效。
  2. 分组:当前类导入的Bean,都是与Spring容器里面的Bean分开来的,排序只是在本组内;
    如:当前spring自己也有很多Bean要注册,然后我们通过DeferredImportSelector又导入一组Bean,这两组Bean时互补干扰的,相反如果他们没有分组,会导致容器的与我们定制的不能保证加载顺序,上诉的@ConditionOnBean..校验会存在问题;如果先加载我们定制的Bean,而到容器加载的时候,发现存在这个Bean就会报错。

实现类的方法执行顺序:
先调用getImportGroup()
有返回值:执行返回值的内容 -->process() -->selectImports(),会实现排序
无返回值:执行父类的selectImports()导入Bean

  • 部分实现的源码
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
        Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
            return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
        });
        // getAutoConfigurationEntry进行扫描具有META-INF/spring.factories文件的jar
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
        this.autoConfigurationEntries.add(autoConfigurationEntry);
        Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();

        while(var4.hasNext()) {
            String importClassName = (String)var4.next();
            this.entries.putIfAbsent(importClassName, annotationMetadata);
        }

    }

通过@Import导入一个DeferredImportSelector类型的类:AutoConfigurationImportSelector

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // 获取EnableAutoConfiguration注解属性信息
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 从META‐INF/spring.factories中获得候选的自动配置类(!!!)
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 排重
            configurations = this.removeDuplicates(configurations);
            //根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            // 根据:@EnableAutoConfiguration.exclude 
            //      @EnableAutoConfiguration.excludeName 
            //      spring.autoconfigure.exclude 进行排除
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            // 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
            configurations = this.getConfigurationClassFilter().filter(configurations);
            // 这个方法是调用实现了AutoConfigurationImportListener的bean.. 分别把候选的配置名单,和排除的配置名单传进去做扩展
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
// 最后排序并注册Bean
 public Iterable<Entry> selectImports() {}

每一个springboot的应用,都会导入一个spring-boot-autoconfigure模块,而META-INF/spring.factories文件就在这个模块下面。而校验自动装配生效的是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如图:

2b91dcce8d716cd5275b90d65472c47.png

说明:在springboot启动类内部执行run时,会执行selectImports()方法,找到对应的配置类的权限定名对应的class,加载到spring容器中。

最后看个例子:上面截图的AopAutoConfiguration

@Configuration(
    proxyBeanMethods = false // @Configuration表示是一个配置类, proxyBeanMethods = false表示当前类不是代理类(默认true)
)
@ConditionalOnProperty(  // 表示当前项目环境必须存在某种配置
    prefix = "spring.aop",
    name = {"auto"},  // 必须spring.aop.auto配置
    havingValue = "true",
    matchIfMissing = true // 如果等于false,不存在spring.aop.auto,aop不生效                     
) // 但是这里等于true表示不存在spring.aop.auto配置,就是用默认的aop
public class AopAutoConfiguration {

}

pom.xml引入xxx-starter,自动装配xxx功能,根据定义的规则校验成功之后,就是将所需的类导入spring工厂管理,完成自动装配。

  • 流程图
    springboot-自动装配.png

总结:

  1. 首先通过@Import导入一个DeferredImportSelector类型的类
  2. 扫描所有jar中的META‐INF/spring.factories
  3. 得到所有的有类路径的Bean组成List进行排序加载
  4. 最后spring就能管理这些类了
  • 自定义启动器(starter)

通过对springboot自动配置原理的了解,只要根据springboot规则,通过org.springframework.boot.autoconfigure.EnableAutoConfiguration导入,就能制定我们自定义的starter:
规则:
启动器(starter)就是空的jar文件,用来提供辅助性依赖管理。
自定义一个spring-boot-autoconfigure的配置模块,写配置类
命名:
官方:spring-boot-starter-xxx
非官方:xxx-spring-boot-starter(mybatis)

实现步骤:

构建一个管理父项目(类是微服务的父项目)
构建xxx-spring-boot-autoconfigure模块实现配置类
构建xxx-spring-boot-starter提供调用

自定义starter案例:https://gitee.com/not-lsj/spring-boot-starter.git

结尾:

  1. 本文介绍了springboot自动装配原理
  2. 模拟实现了自定义starter
  3. 某个时刻会把springboot启动原理总结发布和两篇文章的总结流程图奉上
  4. 欢迎来交流

最后的最后:第一次写总结,可能会不太好,但是会越来越努力!!!

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

推荐阅读更多精彩内容