欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

程序员文章站 2022-06-29 11:09:30
前言 开心一刻 女儿: “妈妈,你这么漂亮,当年怎么嫁给了爸爸呢?” 妈妈: “当年你爸不是穷嘛!‘ 女儿: “穷你还嫁给他!” 妈妈: “那时候刚刚毕业参加工作,领导对我说,他是我的扶贫对象,我年轻理解错了,就嫁给他了!” 女儿...... @Import注解应用 应用开发中,当我们的功能模块比较 ......

前言

  开心一刻   

    女儿: “妈妈,你这么漂亮,当年怎么嫁给了爸爸呢?”
    妈妈: “当年你爸不是穷嘛!‘
    女儿: “穷你还嫁给他!”
    妈妈: “那时候刚刚毕业参加工作,领导对我说,他是我的扶贫对象,我年轻理解错了,就嫁给他了!”
    女儿......

@import注解应用

  应用开发中,当我们的功能模块比较多时,往往会按模块或类别对spring的bean配置文件进行管理,使配置文件模块化,更容易维护;spring3.0之前,对spring xml bean文件进行拆分, 例如

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
    xsi:schemalocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
    <import resource="config/user.xml"/>
    <import resource="config/role.xml"/>
    <import resource="config/permission.xml"/>
 
</beans>

  spring3.0及之后,引入了@import注解,提供与spring xml中的<import />元素等效的功能;spring4.2之前,@import只支持导入配置类(@configuration修饰的类、importselector实现类和importbeandefinitionregistrar实现类),而spring4.2及之后不仅支持导入配置类,同时也支持导入常规的java类(如普通的user类)

  示例地址:,四种都有配置,不用down下来运行,看一眼具体如何配置即可

  运行测试用例,结果如下

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

  可以看到,dog、cat、role、user、permission的实例都已经注册到了spring容器,也就是说上述讲的@import的4种方式都是能够将实例注册到spring容器的

@import注解原理

  @import何以有如此强大的功能,背后肯定有某个团队在运作,而这个团队是谁了,就是spring;spring容器肯定在某个阶段有对@import进行了处理,至于spring是在什么时候对@import进行了怎样的处理,我们来跟一跟源码;configurationclasspostprocessor实现了beandefinitionregistrypostprocessor,那么它会在spring启动的refresh阶段被应用,我们从refresh的invokebeanfactorypostprocessors方法开始

  configurationclasspostprocessor

    注意此时spring容器中的bean定义与bean实例,数量非常少,大家可以留心观察下

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

    一路跟下来,我们来到processconfigbeandefinitions方法,该方法会创建一个configurationclassparser对象,该对象会分析所有@configuration注解的配置类,产生一组configurationclass对象,然后从这组configurationclass对象中加载bean定义

  configurationclassparser

    主要是parse方法

public void parse(set<beandefinitionholder> configcandidates) {    
    this.deferredimportselectors = new linkedlist<>();

    // 通常情况下configcandidates中就一个beandefinitionholder,关联的是我们的启动类
    // 示例中是:com.lee.autoconfig.autoconfigapplication
    for (beandefinitionholder holder : configcandidates) {
        beandefinition bd = holder.getbeandefinition();
        try {
            // 被@configuration注解修饰的类会被解析为annotatedgenericbeandefinition,annotatedgenericbeandefinition实现类annotatedbeandefinition接口
            if (bd instanceof annotatedbeandefinition) {
                parse(((annotatedbeandefinition) bd).getmetadata(), holder.getbeanname());
            }
            else if (bd instanceof abstractbeandefinition && ((abstractbeandefinition) bd).hasbeanclass()) {
                parse(((abstractbeandefinition) bd).getbeanclass(), holder.getbeanname());
            }
            else {
                parse(bd.getbeanclassname(), holder.getbeanname());
            }
        }
        catch (beandefinitionstoreexception ex) {
            throw ex;
        }
        catch (throwable ex) {
            throw new beandefinitionstoreexception(
                    "failed to parse configuration class [" + bd.getbeanclassname() + "]", ex);
        }
    }

    // 处理延迟的importselector,这里本文的重点:自动配置的入口
    processdeferredimportselectors();
}

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

    从启动类(示例中是com.lee.autoconfig.autoconfigapplication)开始,递归解析配置类以及配置类的父级配置类;边跟边注意beanfactory中beandefinitionmap的变化,configurationclassparser对象有beanfactory的引用,属性名叫registry;我们可以仔细看下doprocessconfigurationclass方法

/**
 * 通过从源类中读取注解、成员和方法来构建一个完整的配置类:configurationclass
 * 注意返回值,是父级类或null(null包含两种情况,没找到父级类或之前已经处理完成)
 */
@nullable
protected final sourceclass doprocessconfigurationclass(configurationclass configclass, sourceclass sourceclass)
        throws ioexception {

    // 递归处理配置类内置的成员类
    processmemberclasses(configclass, sourceclass);

    // 处理配置类上所有@propertysource注解
    for (annotationattributes propertysource : annotationconfigutils.attributesforrepeatable(
            sourceclass.getmetadata(), propertysources.class,
            org.springframework.context.annotation.propertysource.class)) {
        if (this.environment instanceof configurableenvironment) {
            processpropertysource(propertysource);
        }
        else {
            logger.warn("ignoring @propertysource annotation on [" + sourceclass.getmetadata().getclassname() +
                    "]. reason: environment must implement configurableenvironment");
        }
    }

    // 处理配置类上所有的@componentscan注解,包括@componentscans和componentscan
    set<annotationattributes> componentscans = annotationconfigutils.attributesforrepeatable(
            sourceclass.getmetadata(), componentscans.class, componentscan.class);
    if (!componentscans.isempty() &&
            !this.conditionevaluator.shouldskip(sourceclass.getmetadata(), configurationphase.register_bean)) {
        for (annotationattributes componentscan : componentscans) {
            // 立即扫描@componentscan修饰的配置类,
            // 通常是从启动类所在的包(示例中是com.lee.autoconfig)开始扫描,扫描配置类(被@configuration修饰的类)
            set<beandefinitionholder> scannedbeandefinitions =
                    this.componentscanparser.parse(componentscan, sourceclass.getmetadata().getclassname());
            // 进一步检查通过配置类扫描得到的bean定义集,并在需要时递归解析
            for (beandefinitionholder holder : scannedbeandefinitions) {
                beandefinition bdcand = holder.getbeandefinition().getoriginatingbeandefinition();
                if (bdcand == null) {
                    bdcand = holder.getbeandefinition();
                }
                if (configurationclassutils.checkconfigurationclasscandidate(bdcand, this.metadatareaderfactory)) {
                    parse(bdcand.getbeanclassname(), holder.getbeanname());
                }
            }
        }
    }

    // 处理配置类上所有的@import注解
    // 包括@import支持的4种类型:importselector、importbeandefinitionregistrar、@configuration和普通java类
    // 普通java类会被按@configuration方式处理
    processimports(configclass, sourceclass, getimports(sourceclass), true);

    // 处理配置类上所有的@importresource注解,xml方式的bean就是其中之一
    annotationattributes importresource =
            annotationconfigutils.attributesfor(sourceclass.getmetadata(), importresource.class);
    if (importresource != null) {
        string[] resources = importresource.getstringarray("locations");
        class<? extends beandefinitionreader> readerclass = importresource.getclass("reader");
        for (string resource : resources) {
            string resolvedresource = this.environment.resolverequiredplaceholders(resource);
            configclass.addimportedresource(resolvedresource, readerclass);
        }
    }

    // 处理配置类中被@bean修饰的方法
    set<methodmetadata> beanmethods = retrievebeanmethodmetadata(sourceclass);
    for (methodmetadata methodmetadata : beanmethods) {
        configclass.addbeanmethod(new beanmethod(methodmetadata, configclass));
    }

    // 处理默认的方法或接口
    processinterfaces(configclass, sourceclass);

    // 处理父级类,如果有的话
    if (sourceclass.getmetadata().hassuperclass()) {
        string superclass = sourceclass.getmetadata().getsuperclassname();
        if (superclass != null && !superclass.startswith("java") &&
                !this.knownsuperclasses.containskey(superclass)) {
            this.knownsuperclasses.put(superclass, configclass);
            // superclass found, return its annotation metadata and recurse
            return sourceclass.getsuperclass();
        }
    }

    // no superclass -> processing is complete
    return null;
}

    上述代码中写了相关注释,有兴趣的同学可以更进一步的去跟,这里我只跟下processimports方法,因为这个与自动配置息息相关

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

    起始的configurationclass包括:1、工程中所有我们自定义的被@configuration修饰的类,示例中就只有animalconfig;2、应用的启动类,示例中是:autoconfigapplication。

    我们自定义的configurationclass一般不会包含多级父级configurationclass,例如animalconfig,就没有父级configurationclass,解析就比较简单,我们无需关注,但autoconfigapplication就不一样了,他往往会被多个注解修饰,而这些注解会牵扯出多个configurationclass,需要递归处理所有的configurationclass;上图中,我们跟到了一个比较重要的类:autoconfigurationimportselector,实例化之后封装成了deferredimportselectorholder对象,存放到了configurationclassparser的deferredimportselectors属性中

自动配置源码解析

  有人可能有这样的疑问:哪来的autoconfigurationimportselector,它有什么用? 客观莫急,我们慢慢往下看

  我们的应用启动类被@springbootapplication,它是个组合注解,详情如下

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

  相信大家都看到@import(autoconfigurationimportselector.class)了,configurationclassparser就是从此解析到的autoconfigurationimportselector,至于autoconfigurationimportselector有什么用,马上揭晓;我们回到configurationclassparser的parse方法,里面还有个很重要的方法:processdeferredimportselectors,值得我们详细跟下

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

  processdeferredimportselectors

    说的简单点,从类路径下的所有spring.facoties文件中读取全部的自动配置类(spring.factories文件中org.springframework.boot.autoconfigure.enableautoconfiguration的值),然后筛选出满足条件的配置类,封装成configurationclass,存放到configurationclassparser的configurationclasses属性中

    说的详细点,分两个方法进行说明

      selectimports方法

public string[] selectimports(annotationmetadata annotationmetadata) {
    if (!isenabled(annotationmetadata)) {
        return no_imports;
    }
    autoconfigurationmetadata autoconfigurationmetadata = autoconfigurationmetadataloader
            .loadmetadata(this.beanclassloader);
    annotationattributes attributes = getattributes(annotationmetadata);
    // 从类路径下的spring.factories文件中读取所有配置类(org.springframework.boot.autoconfigure.enableautoconfigurationd的值)
    // 得到所有配置类的全路径类名的集合 - 数组
    // 此时得到的是类名,至于该类存不存在,还需要在下面步骤中进行检验
    list<string> configurations = getcandidateconfigurations(annotationmetadata,
            attributes);
    // 去重重复的
    configurations = removeduplicates(configurations);
    // 获取需要排除的配置类,@springbootapplication exclude和excludename的值
    // 以及配置文件中spring.autoconfigure.exclude的值
    set<string> exclusions = getexclusions(annotationmetadata, attributes);
    // 验证排除的配置类是否存在 - 类路径下是否存在该类
    checkexcludedclasses(configurations, exclusions);
    // 剔除需要排除的配置类
    configurations.removeall(exclusions);
    // 进行过滤 - 通过配置类的条件注解(@conditionalonclass、@conditionalonbean等)来判断配置类是否符合条件
    configurations = filter(configurations, autoconfigurationmetadata);
    // 触发自动配置事件 - conditionevaluationreportautoconfigurationimportlistener
    fireautoconfigurationimportevents(configurations, exclusions);
    // 返回@import方式 所有满足条件的配置类
    return stringutils.tostringarray(configurations);
}

        从类路径下的所有spring.facoties文件中读取org.springframework.boot.autoconfigure.enableautoconfiguration的所有值,此时获取的是全路径类名的数组,然后进行筛选过滤,1、先去重处理,因为多个spring.factories中可能存在重复的;2、然后剔除我们配置的需要排除的类,包括@springbootapplication注解的exclude、excludename,以及配置文件中的spring.autoconfigure.exclude;3、条件过滤,过滤出满足自己条件注解的配置类。最终获取所有满足条件的自动配置类,示例中有24个。

        条件注解更详细的信息请查看:spring-boot-2.0.3源码篇 - @configuration、condition与@conditional,读取spring.facoties文件的详细信息请查看:spring-boot-2.0.3启动源码篇一 - springapplication构造方法

      processimports方法

        这个方法在解析configurationclassparser的parse方法的时候已经用到过了,只是没有做说明,它其实就是用来处理配置类上的@import注解的;上述selectimports方法解析出来的配置类,每个配置类都会经过processimports方法处理,递归处理@import注解,就与递归处理我们的启动类的@import注解一样,从而获取所有的自动配置类;springboot的自动配置就是这样实现的。

  此时还只是获取了满足条件的自动配置类,配置类中的bean定义加载还没有进行,我们回到configurationclasspostprocessor的processconfigbeandefinitions方法,其中有如下代码

// 各种方式的配置类的解析,包括springboot的自动配置 - @import、autoconfigurationimportselector
parser.parse(candidates);
parser.validate();

set<configurationclass> configclasses = new linkedhashset<>(parser.getconfigurationclasses());
configclasses.removeall(alreadyparsed);

// read the model and create bean definitions based on its content
if (this.reader == null) {
    this.reader = new configurationclassbeandefinitionreader(
            registry, this.sourceextractor, this.resourceloader, this.environment,
            this.importbeannamegenerator, parser.getimportregistry());
}
this.reader.loadbeandefinitions(configclasses);    // 将配置类中的bean定义加载到beanfactory

  至此,springboot的自动配置源码解析就完成了,有兴趣的可以更近一步的深入

总结

  1、各个方法之间的调用时序图如下,结合这个时序图看上面的内容,更好看懂

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

  2、springboot自动配置底层依赖的是springfactoriesloader和autoconfigurationimportselector;@enableautoconfiguration注解就像一个八爪鱼,抓取所有满足条件的配置类,然后读取其中的bean定义到spring容器,@enableautoconfiguration得以生效的关键组件关系图如下

springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂