SpringBoot源码学习(一):自动装配原理分析

887 阅读5分钟

SpringBoot作为现在Java微服务开发中的中流砥柱,其开箱即用、免去xml配置等特点,高效便捷的使Java程序员进行业务开发。

每次询问SpringBoot相关技术点时,其自动装配原理也是重点关注对象。所以通过源码分析其原理对于SpringBoot学习还是非常重要的。
下面我将通过源码分析出我理解的自动装配原理过程,如有分析不对的地方希望大家给予指正。

什么是自动装配?

使用过Spring整合SSM框架的程序员,一定有过被繁杂的XML配置项弄的头疼不已。
比如在Spring.xml 里面要配置数据源、Mybatis的Bean工厂、组件扫描,在SpringMVC.xml中配置视图解析器、首页视图控制、默认处理静态资源规则、MVC注解支持,在web.xml中配置编码过滤器(CharacterEncodingFilter)、MVC前端控制器(DispatcherServlet)、文件上传过滤器、RestFul 风格过滤器等等配置。
但是在SpringBoot项目中,只需导入相关依赖,而且无需任何配置就可以通过Main方法成功启动项目。

@SpringBootApplication
public class MyAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }

}

得益于自动装配,在SpringBoot中我们不需要手动配置,只需要引入相关的starter依赖即可开箱即用。比如想要开发Web项目,只需要导入对应web的starter即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

DispatcherServlet

image.png 通过返回IOC容器,可以看到我们无需任何配置,SpringBoot在加载时就替我们注册了相应开发的组件。

如何实现的自动装配?

我们首先来观察下启动类,这里有一个核心注解@SpringBootApplication

@SpringBootApplication // 核心
public class MyAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }
}

@SpringBootApplication

...
@SpringBootConfiguration 
@EnableAutoConfiguration
@ComponentScan(···)
public @interface SpringBootApplication {...}

可以发现@SpringBootApplication 是由另外三个核心注解@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(···)组成。

@SpringBootConfiguration

...
@Configuration
public @interface SpringBootConfiguration {...}

也即@SpringBootApplication本质也是一个Configuration配置类。

@ComponentScan

包扫描注解,默认扫描当前启动类路径下所有同级以及子级路径下,被@Component(@Service,@Controller@Repository)注解标记的类。

image.png

@EnableAutoConfiguration

...
@AutoConfigurationPackage // 主启动类下所有组件注册到IOC容器中
@Import(AutoConfigurationImportSelector.class) // 加载自动装配类
public @interface EnableAutoConfiguration {}

该注解是实现自动装配的核心,其下又由两个分注解@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)组成

@AutoConfigurationPackage

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

可以看到该注解又通过@Import注解导入了一个AutoConfigurationPackages.Registrar.class类。

AutoConfigurationPackages.Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       // 核心注册方法
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); 
   }

   ...
}

通过调试registerBeanDefinitions方法,它调用register将当前层级下(主启动类)所有的组件进行批量导入注册。

当前层级

image.png

在BeanDefinition中需要注册的Bean信息

image.png

@Import(AutoConfigurationImportSelector.class)

自动装配核心功能实际就由@Import导入的 AutoConfigurationImportSelector类来实现的。

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   // 获取所有需要装配的Bean
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

观察源码发现selectImports()方法返回了一个String数组,它的作用就是获取所有符合条件的类并装载到IOC容器中,然后返回一个装配类全类名名字的String数组。

这里继续往下调试,发现是利用getAutoConfigurationEntry(annotationMetadata)方法给IoC容器中批量导入组件

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()方法我们可以获取到所有需要导入到容器中的配置类。

image.png

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 通过工厂方法加载组件
   List<String> configurations = new ArrayList<>(
         SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
   ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
   Assert.notEmpty(configurations,
         "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
               + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

然后我们会看到方法调用了Spring的工厂加载器的loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法,显然这是通过工厂加载得到所有的组件。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> 
loadSpringFactories(ClassLoader classLoader) {
    // 优先从本地缓存中寻找所有配置类
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        ...

        try {
            // 读取META-INF/spring.factories文件寻找所有配置类
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");
            
                ...
}

最后从loadSpringFactories()方法中可以看出,先去本地缓存中寻找需要加载的配置类的信息,如果缓存中没有就会从所有包下含有META-INF/spring.factories的配置文件中寻找配置类的信息。

其中在spring-boot-autoconfigure-2.4.11.jar包里面的META-INF/spring.factories文件存放着所有需要自动装配的类的信息。其数量正好对应上图configurations的144个配置项。

image.png

按需加载

虽然144个全场景配置项的自动配置启动的时候默认全部加载(xxxxAutoConfiguration)。但实际经过后续处理后只剩下23个配置项真正加载进来。很明显,SpringBoot只会加载实际你要用到的场景中的配置类。这是如何做到的了?

image.png

这里我们选取一个自动配置类,观察到每一个自动配置类都有着@Conditional或者其派生条件注解。

image.png

  • @ConditionalOnBean:当容器里有指定Bean的条件下。

  • @ConditionalOnMissingBean:当容器里没有指定Bean的情况下。

  • @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean。

  • @ConditionalOnClass:当类路径下有指定类的条件下。

  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下。

  • @ConditionalOnProperty:指定的属性是否有指定的值。

  • @ConditionalOnResource:类路径是否有指定的值。

  • @ConditionalOnExpression:基于SpEL表达式作为判断条件。

  • @ConditionalOnJava:基于Java版本作为判断条件。

  • @ConditionalOnJndi:在JNDI存在的条件下差在指定的位置。

  • @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下。

  • @ConditionalOnWebApplication:当前项目是Web项目的条件下。

RedisAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
// 检查是否有该类才会进行加载
@ConditionalOnClass({RedisOperations.class})
// 绑定默认配置信息
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }
   ...
}

所以每一个自动配置类当满足条件注解的条件时,该类才会生效进行自动装配的处理流程。

总结

SpringBoot的自动装配原理就是通过Spring的工厂加载器的loadSpringFactories()方法加载到META-INF/spring.factories中所有的自动装配类的信息去实现自动装配。再通过每一个自动装配类的条件注解进行按需加载,只加载每一个满足注解条件生效后的配置类。