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

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

程序员文章站 2022-07-02 08:01:51
在上一篇博客中分析了springBoot启动流程,大体的轮廓只是冰山一角。今天就来看一下springBoot的亮点功能:自动化装配功能。 先从@SpringBootApplication开始。在启动流程章节中,我们讲述了SpringBoot2大致的启动步骤,并进行了源码详解。但是在刷新容器这块并未展 ......

在上一篇博客中分析了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)方法:

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

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

启动类中包含@springbootapplication注解,进一步查找到@springbootconfiguration注解,然后查找到@component注解,最后会查找到@component注解:

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@documented
@component
public @interface configuration {
}

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

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

如此一来,我们的启动类就被包装成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: 导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

 1 //enableautoconfigurationimportselector的父类:autoconfigurationimportselector
 2 @override
 3 public string[] selectimports(annotationmetadata annotationmetadata) {
 4     if (!isenabled(annotationmetadata)) {
 5         return no_imports;
 6     }
 7     try {
 8         autoconfigurationmetadata autoconfigurationmetadata = autoconfigurationmetadataloader
 9             .loadmetadata(this.beanclassloader);
10         annotationattributes attributes = getattributes(annotationmetadata);
11         list<string> configurations = getcandidateconfigurations(annotationmetadata, attributes);
12         configurations = removeduplicates(configurations);
13         configurations = sort(configurations, autoconfigurationmetadata);
14         set<string> exclusions = getexclusions(annotationmetadata, attributes);
15         checkexcludedclasses(configurations, exclusions);
16         configurations.removeall(exclusions);
17         configurations = filter(configurations, autoconfigurationmetadata);
18         fireautoconfigurationimportevents(configurations, exclusions);
19         return configurations.toarray(new string[configurations.size()]);
20     }
21     catch (ioexception ex) {
22         throw new illegalstateexception(ex);
23     }
24 }

我们主要看第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这个包中,如下图

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

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

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

我们也可以将需要自动配置的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

如图所示:

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

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

原理

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

SpringBoot 源码解析 (五)----- Spring Boot的核心能力 - 自动配置源码解析

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

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

@conditionalonproperty(name = "enabled.autoconfituration", matchifmissing = true)

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