SpringBoot 自动装配原理

SpringBoot 自动装配原理

SpringBoot 基本特性

  • AutoConfiguration 自动装配

  • Starter

  • Actuator

  • SpringBoot CLI

我们为什么会使用SpringBoot?

让我们回想一下,在springboot没有出现之前,我们通常使用SSM(Spring、SpringMVC、Mybatis)架构搭建应用程序,用过Spring框架的同学知道,我们通常称为“万能胶”,因为Spring社区擅长继承各类框架;既然擅长集成各类框架,哪就免不了一些配置,在没有Annotation出现之前,全部都是xml配置,回想一下,当时我们搭建一个框架需要多少xml配置?是不是想想都头疼;在Annotation出现之后变减少了一些xml。

我们为什么会使用SpringBoot呢?当然是因为使用起来简洁、方便呀,什么简单?配置简单,拿过来一个starter,运行即可,无需额外的配置便可启动运行。

那么SpringBoot是如何做到这么简单的配置呢?让我们来看接下来的解答。

约定优于配置

在软件开发领域当中,我们或多或少听说过这个概念,其实SpringBoot就是这一概念的主要体现。

  • maven 的目录结构

    • 默认resources文件夹存放配置文件

    • 默认打包方式为jar

  • 提供开箱即用starter

  • 默认配上文件application.properties/yml

  • 等等...

基本注解(介绍注解的主要目的是为了以后分析自动装配原理做铺垫)

SpringBoot 作为微服务的一个实现框架,其实并没有什么新的技术产生,都是依赖于Spring中原有的技术来封装的。

让我们从@SpringBootApplication注解入手来分析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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {}

@SpringBootApplication 注解是一个复合注解,我们主要分析@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration 注解。

@ComponentScan

这个注解打架接触的最多,相当于在xml中配置context:component-scan,它的作用就是扫描指定路径下需要自动装配的类。

标识需要自动装配类的注解为:@Component、 @Repository、@Service、@Controller。

@ComponentScan 默认会扫描当前package下所有标注相关注解的类,将其注入到Spring的IoC中。

@SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {}

@SpringBootConfiguration 注解其实是@Configuration注解的一个封装,@Configuration注解我们应该不会陌生,他是基于javaConfig形式的基于SpringIoC容器的配置类的一种注解。

@Configuration 注解的作用是声明一个javaConfig配置类,而配置类中的任何一个@Bean的方法,它的返回值都会作为Bean的定义注册到SpringIoc中,方法名默认就是这个bean的id。

@EnableAutoConfiguration

@EnableAutoConfiguration 注解其实也不是什么新的注解,我们应该经常看到过以@Enable开头的注解,这里不详细解释了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

@EnableAutoConfiguration 这个复合注解中有两个主要的注解,@AutoConfigurationPackage、@Import注解,这里我们先简单解释一下这两个注解作用,稍后结合SpringBoot自动装配的原理进行解释。

@Import 注解的作用就是将其他位置的javaConfig配置类导入到当前环境下,进行加载相应的Bean。

@AutoConfigurationPackage 注解下其实也是使用@Import注解。

@Import注解 和 @AutoConfiguration注解虽然在功能上作用是一样的,但是是不同的两种方式加载Bean到IoC容器中。

深入分析SpringBoot 自动装配原理

通过前面的分析我们知道,SpringBoot自动装配机制主要发生在@EnableAutoConfiguration这个注解上,那么下面我们将详细分析下这个注解。

Selector 装配方式 和 Register 装配方式

SpringBoot 中有两种自动装配的方式,一个是实现 ImportSelector接口,一个是实现 ImportBeanDefinitionRegistrar接口,我们这里只介绍ImportSelector方式,ImportBeanDefinitionRegistrar方式请读者自行查阅。

AutoConfigurationImportSelector 类(实现ImportSelector接口方式)

首先我们先看下AutoConfigurationImportSelector类结构关系和源码


image
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    }
    // 加载元数据,获取需要过滤调类的一些信息
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
      .loadMetadata(this.beanClassLoader);
    // 加载类路径下 META-INF/spring.factories 文件和排除不需要的配置类
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
    // 返回所有的配置类名称的数组
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
}

上面的源码就是自动配置的所有逻辑,首先通过注解获取配置注解上的元数据,例如:exclude 属性;其次通过getAutoConfigurationEntry()方法加载类路径下所有META-INFO文件夹下的spring.factories文件并进行解析,处理;最后将处理好的配置类名称以数组的形式返回。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
    // 获取注解上的元数据
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取所有META-INFO/spring.factories配置文件
        List configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
    // 获取所有需要排除的配置类
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
    // 移除需要排除的配置类
        configurations.removeAll(exclusions);
    // 过滤掉不符合condition的配置类
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

上述的代码逻辑非常的清晰,这里就不多做介绍了,我们看下getCandidateConfigurations()这个方法。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

这段代码就是通过SpringFactoriesLoader的loadFactoryNames()加载所有的spring.factories文件。那么spring.factories文件和我们的自动装配有什么关系呢?让我们看下面的图片

image

上图是spring.factories文件的一部分,其中key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的有好多配置类的全路径,SpringBoot就是根据约定大于配置的原则,将配置类写到 key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value中,然后通过SpringFactoriesLoader.loadFactoryNames()方法进行加载,代码如下。

// 加载META-INF/spring.factories 文件的逻辑
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

// 返回EnableAutoConfiguration类型的Class
protected Class getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

// 根据class类型加载配置文件中的信息
public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

// 具体读取配置文件的信息
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

到此为止SpringBoot自动装配的逻辑结束,接下来就是Spring容器进行刷新后同构后置处理器进行调用后注册到SpringIoC容器中。

你可能感兴趣的:(SpringBoot 自动装配原理)