Spring Boot 源码分析(一):自动装配

220 阅读16分钟

引入

早期的EJB笨重复杂,使用起来很不方便,很多人对此叫苦不迭。后来轻量级的Spring框架横空出世,通过编写一些简单的XML配置或注解就能快速搭建起项目环境,不用在EJB体系中苦苦挣扎,开发者的生产力得到解放。但单纯使用Spring搭建环境和开发应用仍需开发者编写很多配置,冗长的XML或一堆注解配置类总会分散开发者的一部分精力。后来Spring Boot出现了,它是对Spring的再封装,提供了几个核心特性,让开发者不用在冗长的配置和复杂的环境部署中挣扎和重复。

Spring Boot的核心特性包括约定大于配置,各种场景启动器,自动装配和嵌入式Web容器等:

  • 约定大于配置: 对日常开发中的常见场景提供了约定配置,开发者少量配置或不配置。
  • 场景启动器starter:对各种场景做了整合,把这些场景需要的依赖都收集一起并通过默认配置,开发者在maven的pom文件中添加一个starter依赖就能获得这个场景需要的所有依赖。
  • 自动装配:通过Spring原生装配方式,搭配Spring Boot提供的条件注解,可以在具体场景中自动注册所需组件,也可通过配置文件动态注册一些组件。
  • 嵌入式容器:应用开发完成后不用打成传统的war包然后放到Tomcat等Web容器中启动,可以打成单独jar包,然后在内部嵌入的Web容器中运行。

这些核心特性总结一下,就是整合了各种场景,通过提供约定配置、自动装配所需组件和提供嵌入式容器等方式,不用再像单纯使用Spring那样纠结于繁杂的配置,Spring Boot会帮我们做很多工作。 这大大减轻了开发者的工作量,生产力再次得到了解放!

当我们进入项目组,拉下代码,打开项目可能会发现一些模块,每个模块会有一个名称像xxxSpringApllication的主类,见下图。这个类里面有个main方法,方法体内会有run方法。启动main方法后,整个项目就跑起来了。

1-1.jpg

启动项目真方便,不需要开发做很多额外工作,一个main方法就解决了!但是方便的背后你知道Spring Boot背后为你做了什么吗?

  1. 你知道为什么run方法执行后项目就启动了呢?
  2. 你知道@SpringBootApplication背后为我们做了啥吗?
  3. 我们常说Spring框架的2个特性IOC和AOP,你知道Spring Boot是怎样支持这两者的吗?
  4. 工作中我们会编写properties或yaml配置文件,然后写注解配置类,你知道文件中的配置是怎样一步一步被Spring Boot获取并注入配置类中的吗?或者你可能知道Spring Boot默认的配置文件格式是properties和yaml,如果我想用json格式的文件,Spring Boot它还能做到吗?
  5. 你可能为某些场景自定义过启动器starter,但是为什么你那样做产生的jar就能被其他Spring Boot项目用起来呢?

这些背后的故事,我们可以从解析源码中获取。下面我们就开始分析Spring Boot的源码。

1. Spring的@EnableXXX注解

1.1 @EnableXXX的方式

Spring早期引入大量@EnableXXX注解,通过加上这些注解,就可以快速装配相应模块的bean到IOC容器:

  • @EnableWebMvc

  • @EnableScheduling

  • @EnableAsync

    ......

    查看对应的源码:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({DelegatingWebMvcConfiguration.class})
    public @interface EnableWebMvc {
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AsyncConfigurationSelector.class)
    public @interface EnableAsync {
    }
    

可以发现上面几个@EnableXXX注解就是简单的:自定义注解+@Import导入组件。我们也来学习一下Spring,通过一个场景来验证下这种方式能否获取我们想要的组件。一个公司大致有开发,测试,产品,项目经理这几种岗位,他们分工合作共同推动公司发展。我们用代码构建一个公司,公司里有这四种岗位,不同岗位可看作一个个组件,要将它们注入公司这个容器中。实际上,@Import可以导入普通类、配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类等,我们分别验证。请在本地跟着实现一下!

1.2 导入普通类

1.定义@EnableCompany

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({})
public @interface EnableCompany {
}

2.创建项目经理类,然后加入到@EnableCompany的@Import元注解的属性中

public class Manager {
}

3.创建配置类CompanyConfiguration,加上@EnableCompany

@Configuration
@EnableCompany
public class CompanyConfiguration {
}

4.测试类

public class CompanyApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
        Manager manager = ctx.getBean(Manager.class);
        System.out.println(manager);
    }
}

运行结果:


org.cosmos.springboot.base.a_demo.component_demo.Manager@6af93788


可以看到普通类Manager已经注入到IOC容器中了。

1.3 导入配置类

1.创建开发者类

public class Developer {
    private String qualified;

    public Developer(String qualified) {
        this.qualified = qualified;
    }

    //getter and setter ...
}

2.创建配置类并结合@Bean注册组件

@Configuration
public class DeveloperConfiguration {
    @Bean
    public Developer backer(){
        return new Developer("后端");
    }

    @Bean
    public Developer fronter(){
        return new Developer("前端");
    }
}

3.DeveloperConfiguration类加入到@EnableCompany的@Import的属性中

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
        Manager.class,
        DeveloperConfiguration.class
})
public @interface EnableCompany {
}

4.测试类

public class CompanyApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("Developer beans in the container:");
        Map<String, Developer> developers = ctx.getBeansOfType(Developer.class);
        developers.forEach((name, developer) -> System.out.println(developer));
    }
}

运行结果:


companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter Developer beans in the container: org.cosmos.springboot.base.a_demo.component_demo.Developer@6dbb137d org.cosmos.springboot.base.a_demo.component_demo.Developer@3c9d0b9d


配置类和2个开发者都注册进IOC容器了。

1.4 导入ImportSelector实现类

1.创建产品经理类ProductConfiguration

@Configuration
public class ProductConfiguration {

    @Bean
    public Product bbproduct(){
        return new Product();
    }
}

2.创建ImportSelector实现类,声明它要导入的类(覆盖selectImports方法,返回值中我们设置了产品经理类及其配置类)

public class ProductImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Product.class.getName(), ProductConfiguration.class.getName()};
    }
}

3.ProductImportSelector类加入到@EnableCompany的@Import的属性中

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
        Manager.class,
        DeveloperConfiguration.class,
        ProductImportSelector.class,
})
public @interface EnableCompany {
}

4.测试类

public class CompanyApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }
}

测试类运行结果:


companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter org.cosmos.springboot.base.a_demo.component_demo.Product org.cosmos.springboot.base.a_demo.configuration.ProductConfiguration bbproduct


ProductImportSelector指定的类都注入进IOC容器了,ProductImportSelector本身不会注入。

1.5 导入ImportBeanDefinitionRegisrtar实现类

1.创建测试者类

public class Tester {
}

2.创建ImportBeanDefinitionRegisrtar实现类, 覆盖registerBeanDefinitions方法。

public class TesterRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
        registry.registerBeanDefinition("Tester", new RootBeanDefinition(Tester.class));
    }
}

同样加入到@EnableCompany的@Import的属性中,然后运行配置类:


companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter org.cosmos.springboot.base.a_demo.component_demo.Product org.cosmos.springboot.base.a_demo.configuration.ProductConfiguration bbproduct Tester


可以看到,ImportBeanDefinitionRegisrtar实现类也成功导入了显式指明的类。

2.@Conditional

上面的案例说明了Spring的一种装配组件到容器的重要方法,Spring Boot作为对Spring的二次封装,自然吸纳了这个优秀方法。另外,Spring Boot还提供了基于Profile的装配方法,可以查找资料看一下。除此之外,Spring Boot还提供了基于@ConditionalOnXXX系列注解,这是基于某些条件的装配。我们在项目代码或Spring Boot的框架源码经常看到很多这样的注解,从这些注解中我们可以有选择性的导入我们所需的bean。

项目经理是项目的灵魂,是吗?:smile:项目经理首先对接市场或客户,提出或传递需求,然后产品经理梳理需求并画原型,后面开发者设计系统编写代码,最后测试者测试开发质量。

​ 开发者依赖项目经理的存在,没有项目经理就不会有开发者后面的事。我们先创建判断开发者是否存在的类ManagerCondition:

public class ManagerCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getBeanFactory().containsBeanDefinition(Manager.class.getName());
    }
}

然后在开发者注解配置类中加上@Condition注解,加上这个判断类:

@Configuration
public class DeveloperConfiguration {
    @Bean
    @Conditional(ManagerCondition.class)
    public Developer backer(){
        return new Developer("后端");
    }

    @Bean
    public Developer fronter(){
        return new Developer("前端");
    }
}

然后@EnableCompany的@Import只保留DeveloperConfiguration.class,然后启动测试类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
//        Manager.class,
        DeveloperConfiguration.class,
//        ProductImportSelector.class,
//        TesterRegistrar.class,
//        TesterDeferredImportSelector.class
})
public @interface EnableCompany {
}

运行结果:


companyConfiguration org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration fronter


可见IOC容器中没有Manager类,开发者对象"后端"也不会注册到容器中。@EnableCompany中取消 Manager.class的注册重新运行测试类:


companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter


"后端"重新回到了战场。所以@Conditional注解帮我们决定哪些类可以注入哪些不行。常见的ConditionalOnXXX注解有:

  • @ConditionalOnClass

  • @ConditionOnBean、@ConditionalOnMissingBean

  • @ConditionalOnProperty

    ......

    从名称基本都可以猜测到每个注解的用法。

3. SPI机制

下面学习一下SPI​​。日常开发中我们应该在代码依赖接口而不是直接依赖接口实现类,因为某些原因实现类可能要修改,但真正起作用的还是实现类,依赖了接口怎么去找到对应的实现类来完成任务呢?SPI机制设计出来就解决了这个问题。

3.1 JDK SPI

SPI可以动态的加载接口/抽象类的实现类,JDK提供了对SPI的支持。先来看看JDK原生SPI有怎样神奇的效果。

1.定义接口和实现类

package org.cosmos.springboot.base.spi.bean;

public interface Cloud {
}
package org.cosmos.springboot.base.spi.bean;

public class AliCloud implements Cloud {
}
package org.cosmos.springboot.base.spi.bean;

public class TencentCloud implements Cloud {
}

2.创建SPI文件。JDK规范了,所有定义的SPI文件要满足:

  • 位置:必须放在项目的META-INF/services目录下。
  • 文件名:必须命名为接口或抽象类的全限定名。
  • 文件内容:接口或抽象类的具体实现类全限定名,如果有多个实现类,则每行声明一个实现类的全限定名,多个实现类之间没有分割符。

项目resource目录下创建META-INF/services目录,在其中创建文件,文件名org.cosmos.springboot.base.spi.bean.Cloud,文件内容:


org.cosmos.springboot.base.spi.bean.AliCloud
org.cosmos.springboot.base.spi.bean.TencentCloud

3.创建测试类

public class JdkSPIApplication {

    public static void main(String[] args) {
        ServiceLoader<Cloud> serviceLoader = ServiceLoader.load(Cloud.class);
        serviceLoader.iterator().forEachRemaining(cloud -> {
            System.out.println(cloud);
        });
    }
}

运行结果:


org.cosmos.springboot.base.spi.bean.AliCloud@816f27d org.cosmos.springboot.base.spi.bean.TencentCloud@87aac27


通过ServiceLoader的load方法成功获得了Cloud接口声明的2个实现类!

3.2 Spring SPI

Spring的SPI相较于JDK原生SPI更强大,不只是接口或实现类,还可以是任何一个类或注解。Spring Boo多处利用了SPI机制加载自动配置类和一些特殊组件。先来验证一下效果。

1.创建SPI文件

  • 位置:必须放在项目的META-INF/services目录下。
  • 文件名:需要命名为spring.factories。这个文件其实是个properties文件。
  • 文件内容:接口或抽象类的具体实现类的全限定名作为properties的key,具体需要获取的类(不一定是全部的实现类)作为value,多个实现类 用英文逗号分割。
org.cosmos.springboot.base.spi.bean.Cloud=org.cosmos.springboot.base.spi.bean.AliCloud, org.cosmos.springboot.base.spi.bean.TencentCloud

2.测试类

public class SpringSpiApplication {
    public static void main(String[] args) {
        List<Cloud> clouds = SpringFactoriesLoader.loadFactories(Cloud.class, SpringApplication.class.getClassLoader());
        clouds.forEach(cloud -> {
            System.out.println(cloud);
        });

        System.out.println("cloudNames:");

        List<String> cloudNames = SpringFactoriesLoader.loadFactoryNames(Cloud.class, SpringApplication.class.getClassLoader());
        cloudNames.forEach(className -> {
            System.out.println(className);
        });
    }
}

运行结果:


org.cosmos.springboot.base.spi.bean.AliCloud@12bc6874 org.cosmos.springboot.base.spi.bean.TencentCloud@de0a01f cloudNames: org.cosmos.springboot.base.spi.bean.AliCloud org.cosmos.springboot.base.spi.bean.TencentCloud


Spring的SPI机制也生效了。下面进一步看看Spring到底是如何获取spring.factories中声明的类。核心是SpringFactoriesLoader.loadFactoryNames方法:

//用全局Map做缓存
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

loadFactoryNames调用了静态方法loadSpringFactories:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    	//先从缓存中取
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                //缓存找不到,直接从META-INF/spring.factories获取!
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    //检索每一个spring.factories文件
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    //以properties读取
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        //properties的key(接口等的全限定名)
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            //每个spring.factories文件的同名key对应的value,合并放到result,result是MultiValueMap
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } 
            //catch ...
        }
    }

源码表明项目启动后,先把META-INF/spring.factories中声明的所有类读出来放到缓存区中,后面直接走缓存。

4. @SpringBootApplication

点开@SpringBootApplication,它由3个注解标注:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
4.1 @ComponentScan

@ComponentScan来自Spring,放在这里意图扫描主类所在包及子包中所有组件,这个注解中excludeFilters属性是个数组,先看TypeExcludeFilter。spring文档指出,只需要上下文中注册TypeExcludeFilter子类并覆盖match方法,Spring Boot会自动找到这些子类并调用它们。

  public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {
            Iterator var3 = this.getDelegates().iterator();

            while(var3.hasNext()) {
                TypeExcludeFilter delegate = (TypeExcludeFilter)var3.next();
                //遍历所有TypeExcludeFilter并调用它的match方法
                if (delegate.match(metadataReader, metadataReaderFactory)) {
                    return true;
                }
            }
        }

        return false;
    }

再来看看AutoConfigurationExcludeFilter,见名知义,它是用来排除自动配置类的,它的关键方法match会加上2个判断。

  public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
    }
    private boolean isConfiguration(MetadataReader metadataReader) {
        //1.判断是否是配置类:是否被@Configuration标注
        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
    }
   private boolean isAutoConfiguration(MetadataReader metadataReader) {
       //2.判断是否是自动装配类:用Sping SPI机制加载所有配置类后从中判断
        return this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
    }

    protected List<String> getAutoConfigurations() {
        if (this.autoConfigurations == null) {
            //Spring SPI
            this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
        }

        return this.autoConfigurations;
    }
4.2 @SpringBootConfiguration

@SpringBootConfiguration,很简单,用来标注这是一个配置类。

@Configuration
public @interface SpringBootConfiguration {}
4.3 @EnableAutoConfiguration

经过前面的准备,就可以探索@EnableAutoConfiguration这个非常重要的注解了。这个核心注解的作用是:启用Spring Boot的自动装配,根据导入的依赖和上下文合理加载默认配置。点开源码可以看到,它有2个注解标注:

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
4.3.1 @AutoConfigurationPackage

@EnableAutoConfiguration是个组合注解,先来研究@AutoConfigurationPackage注解。

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
        String[] basePackages() default {};
		Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage又用@Import导入了一个类,这个类是抽象类AutoConfigurationPackages的静态内部类Registrar。Spring Boot会导入Registrar类,这个类肯定起了重要作用。

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new 		                        AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

返回来看AutoConfigurationPackages类,他有3个静态内部类Registrar、PackageImports和BasePackages。

Registrar实现了ImportBeanDefinitionRegistrar,说明他有导入组件到IOC容器的作用。重点关注它重写的registerBeanDefinitions方法,它调用了AutoConfigurationPackages的register方法。这个方法的第一个参数AnnotationMetadata,指的是@AutoConfigurationPackage所在根包;第二个参数的获取较长,是调用AutoConfigurationPackages的静态内部类PackageImports的getPackageNames方法的返回值。先看一下PackageImports:

private static final class PackageImports {
        private final List<String> packageNames;

        PackageImports(AnnotationMetadata metadata) {
            //获取@AutoConfigurationPackage的属性
            AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
            List<String> packageNames = new ArrayList();
            String[] var4 = attributes.getStringArray("basePackages");
            int var5 = var4.length;

            int var6;
            for(var6 = 0; var6 < var5; ++var6) {
                String basePackage = var4[var6];
                //获取basePackages属性值
                packageNames.add(basePackage);
            }

            Class[] var8 = attributes.getClassArray("basePackageClasses");
            var5 = var8.length;

            for(var6 = 0; var6 < var5; ++var6) {
                Class<?> basePackageClass = var8[var6];
                 //获取basePackageClasses属性值
                packageNames.add(basePackageClass.getPackage().getName());
            }

            if (packageNames.isEmpty()) {
                //通过上面没获取到,直接取Spring Boot主类所在包
                packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
            }

            this.packageNames = Collections.unmodifiableList(packageNames);
        }

        List<String> getPackageNames() {
            return this.packageNames;
        }
    }

再来看AutoConfigurationPackages类的register方法:

private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        //BeanDefinitionRegistry可以向IOC容器注册BeanDefinition
        //容器中有AutoConfigurationPackages,则取出来将上面获取的包名字符串数组加到BasePackages构造器参数中
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            //容器中没有,就构造BeanDefinition,并将上面得到的包名放到BasePackages构造器参数中,然后注册到容器
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }
static final class BasePackages {
        private final List<String> packages;

    	//BasePackages的构造器参数会放到属性packages
        BasePackages(String... names) {
            List<String> packages = new ArrayList();
            String[] var3 = names;
            int var4 = names.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String name = var3[var5];
                if (StringUtils.hasText(name)) {
                    packages.add(name);
                }
            }

            this.packages = packages;
        }
}

所以register方法把@AutoConfigurationPackage的basePackages属性和basePackageClasses属性指定的包下的类注册到容器了。

4.3.2 AutoConfigurationImportSelector

接下来看看@EnableAutoConfiguration的第二个注解,用@Import导入了AutoConfigurationImportSelector,它实现了DeferredImportSelector,但比ImportSelector执行得晚些,这个特点可以用来在其他类导入后做些补充性的工作。Spring Boot的自动装配也是基于约定大于配置,项目中已有的配置自动装配了,没有的配置需要补充进来,而DeferredImportSelector就适合做补充工作。先来看看DeferredImportSelector:

public interface DeferredImportSelector extends ImportSelector {
    @Nullable
	default Class<? extends Group> getImportGroup() {
		return null;
	}

	interface Group {
		void process(AnnotationMetadata metadata, DeferredImportSelector selector);
		Iterable<Entry> selectImports();

		class Entry {

			private final AnnotationMetadata metadata;

			private final String importClassName;

			public Entry(AnnotationMetadata metadata, String importClassName) {
				this.metadata = metadata;
				this.importClassName = importClassName;
			}
            //...
        }
        //...
    }

}

DeferredImportSelector接口设计了一个内部接口Group,为了把不同的DeferredImportSelector实现类根据一些条件分成不同的组,就抽象出这个接口。Group接口里又加了内部类Entry和一个重要方法process。具体到AutoConfigurationImportSelector来看:

public class AutoConfigurationImportSelector implements DeferredImportSelector{
    
    	//1. 重写了DeferredImportSelector的getImportGroup方法
    	public Class<? extends Group> getImportGroup() {
        	return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
    	}
    
    //2.AutoConfigurationGroup
    private static class AutoConfigurationGroup implements Group {
        //覆盖的process方法是加载所有自动配置类的入口!
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            //内部类的process方法调用了内部类外的方法getAutoConfigurationEntry
            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);
            }

        }
    }
    
    	//3.重写了ImportSelector的selectImports方法,这里会导入组件!
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
        	if (!this.isEnabled(annotationMetadata)) {
            	return NO_IMPORTS;
        	} else {
            	AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    
}

1. 先看AutoConfigurationGroup

属性autoConfigurationEntries将要封装自动装配的组件:

private static class AutoConfigurationGroup implements Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        private final Map<String, AnnotationMetadata> entries = new LinkedHashMap();
        private final List<AutoConfigurationImportSelector.AutoConfigurationEntry> autoConfigurationEntries = new ArrayList();
        //...
}

而AutoConfigurationEntry定义了要导入和不导入的组件名集合:

    protected static class AutoConfigurationEntry {
        private final List<String> configurations;
        private final Set<String> exclusions;
		//...
    }

process方法是加载所有自动配置类的入口!它调用getAutoConfigurationEntry方法,参数annotationMetadata指的是@EnableAutoConfiguration。方法返回的正是上面说的AutoConfigurationEntry对象,它分别定义了属性表示要导入容器中和不要导入的组件集合,所以我猜想getAutoConfigurationEntry方法要做的就是获取所有自动装配类,然后按照某些方法过滤出需要导入的和不要到导入的,最后封装到AutoConfigurationEntry对象返回,源码正是做了这些工作:

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata 	 			annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //获取注解属性
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //获取所有自动配置类!
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //去重
            configurations = this.removeDuplicates(configurations);
            //获取显式指定要排除的配置类
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            //去除操作
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

getCandidateConfigurations利用Spring SPI加载了所有自动配置类:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        //再次看到Spring SPI的身影!
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        return configurations;
    }

获取指定的那些要排除的配置类:

    protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        Set<String> excluded = new LinkedHashSet();
        excluded.addAll(this.asList(attributes, "exclude"));
        excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
        excluded.addAll(this.getExcludeAutoConfigurationsProperty());
        return excluded;
    }

	//获取Spring Boot全局配置spring.autoconfigure.exclude的值
    protected List<String> getExcludeAutoConfigurationsProperty() {
        Environment environment = this.getEnvironment();
        if (environment == null) {
            return Collections.emptyList();
        } else if (environment instanceof ConfigurableEnvironment) {
            Binder binder = Binder.get(environment);
            return (List)binder.bind("spring.autoconfigure.exclude", String[].class).map(Arrays::asList).orElse(Collections.emptyList());
        } else {
            String[] excludes = (String[])environment.getProperty("spring.autoconfigure.exclude", String[].class);
            return excludes != null ? Arrays.asList(excludes) : Collections.emptyList();
        }
    }

process方法最后是遍历那些需要要导入的自动配置类,然后放到AutoConfigurationGroup的Map类型属性entries中。

2. selectImports

AutoConfigurationImportSelector的selectImports方法指明要导入到容器中的bean,方法也是上面分析过的getAutoConfigurationEntry方法。

总结一下,@EnableAutoConfiguration注解通过组合的2个注解完成了自动装配,利用@AutoConfigurationPackage完成指定包路径下(如果没指定,默认主启动类所在包及其子包)所有bean的注册,利用@Import和AutoConfigurationImportSelector配合SPI导入所有需要导入的bean。

5. 总结

至此,Spring Boot自动装配的源码分析了一下。本质就是通过采用Spring的@Import注解方式、@ConditionOnXXX系列注解、Spring SPI机制这三种方法实现了自动装配。源码设计的精巧有趣,跟一遍你会更好的利用Spring Boot这笔资源。