Spring Boot - 原理深刻 - 自动配置

2.2.2 自动配置

​ 可以在咱们添加jar包依赖时,自动为咱们进行配置一下配置,咱们能够不须要配置或者少许配置就能运行编写的项目。java

问题:Spring Boot究竟是如何进行自动配置的,都把那些组件进行了自动配置?web

2.2.2.1 @SpringBootApplication

Spring Boot 应用启动的入口是@SpringBootApplication注解标注类的main方法,spring

@SpringBootApplication可以扫描Spring组件而且自动配置Spring Boot markdown

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

@SpringBootApplication注解类框架

// 注解的适用范围:类、接口、枚举
@Target({ElementType.TYPE})
// 注解的生命周期:运行时
@Retention(RetentionPolicy.RUNTIME)
// 标明注解可标注在javadoc中
@Documented
// 标明注解能够被子类继承
@Inherited
// 标明该类为配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
// 包扫描器
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

从上面能够看出,@SpringBootApplication注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个核心注解组成。ide

2.2.2.1.1 @SpringBootConfiguration

@SpringBootConfiguration 注解标明其类为Spring Boot配置类spring-boot

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 配置到IOC容器
@Configuration
public @interface SpringBootConfiguration {
}

从上述能够看出,@SpringBootConfiguration注解类主要注解为@Configuration注解,该注解由Spring框架提供,表示当前类为一个配置类,而且能够被组件扫描器扫描。工具

2.2.2.1.2 @EnableAutoConfiguration

@EnableAutoConfiguration注解表示为自动配置类,该注解是Spring Boot最重要的注解,也是实现自动配置的注解。this

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 自动配置扫描导入
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

从源码能够发现,@EnableAutoConfiguration注解为一个组合注解,其做用就是借助@Import注解导入特定场景须要向IOC注册的Bean,而且加载到IOC容器。@AutoConfigurationPackage就是借助@Import来搜集全部符合自动配置条件的Bean定义,而且加载到IOC容器中。url

  • @AutoConfigurationPackage

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // 导入Registrar中注册的组件
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }

    从上述源码中能够看出@AutoConfigurationPackage注解的功能是有@Import注解实现的。@Import它是Spring框架底层注解,它的做用就是给容器导入某个组件类

    Registrar类源码

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
              // 将主程序类所在的包以及全部子包下的组件扫描到Spring容器
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    
    }

    从上述能够看出,@AutoConfigurationPackage注解的主要做用就是将主程序类所在的包以及全部子包下的组件加载到IOC容器中。

    所以:在定义项目包目录时,要求定义的包结构必须规范,项目主程序启动类要放在最外层的根目录位置,而后在根目录的位置内部创建子包和类进行业务开发,这样才能保证定义的类才能被组件扫描器扫描。

  • @Import({AutoConfigurationImportSelector.class})

    将AutoConfigurationImportSelector类导入到Spring容器中。

    AutoConfigurationImportSelector能够帮助 Spring Boot 应用将全部符合条件@Configuration的配置都导入到当前Spring Boot建立并使用的IOC容器(ApplicationContext)中。

    // 自动配置的过程
    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
      Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                   () -> String.format("Only %s implementations are supported, got %s",
                                       AutoConfigurationImportSelector.class.getSimpleName(),
                                       deferredImportSelector.getClass().getName()));
      // 获取自动配置的配置类
      AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
      this.autoConfigurationEntries.add(autoConfigurationEntry);
      for (String importClassName : autoConfigurationEntry.getConfigurations()) {
          this.entries.putIfAbsent(importClassName, annotationMetadata);
      }
    }
    
    // 获取自动配置元信息
    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
      if (this.autoConfigurationMetadata == null) {
          // 加载自动配置元信息,须要传入beanClassLoader这个类加载器
          this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      }
      return this.autoConfigurationMetadata;
    }
    
    // 获取自动配置的配置类
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
          // 从META-INF/spring.factories配置文件中将对于的自动配置类获取到
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

深刻AutoConfigurationMetadataLoader.loadMetadata()方法

// 文件中须要加载的配置类的类路径
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // 读取spring-boot-autoconfigure-2.1.14.RELEASE.jar中spring-autoconfigure-metadata.properties的信息生成URL
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

深刻AutoConfigurationImportSelector.getCandidateConfigurations() 方法

这个方法有一个重要的loadFactoryNames方法,这个方法让SpringFactoriesLoader去加载一些组件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 这个方法须要两个参数,getSpringFactoriesLoaderFactoryClass()、getBeanClassLoader()
    // getSpringFactoriesLoaderFactoryClass() 返回的:EnableAutoConfiguration.class
    // getBeanClassLoader() 返回的:beanClassLoader类加载器
    List<String> 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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}

继续深刻loadFactoryNames()方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // 获取出的健
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            // 若是类加载器不为空,这加载类路径下的META-INF/spring.factories,将其中设置的配置类的类路径信息封装为Enumeration对象
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources("META-INF/spring.factories") :
                    ClassLoader.getSystemResources("META-INF/spring.factories"));
            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 factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

会去读取一个 spring.factories 的文件,读取不到会报错

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

它实际上是去加载一个外部的文件,而这个文件是在

Spring Boot - 原理深刻 - 自动配置

Spring Boot - 原理深刻 - 自动配置

@EnableAutoConfiguration 注解就是从classpath中搜寻META-INF/spring.factories配置文件,并将其org.springframework.boot.autoconfigure.EnableAutoConfiguration对于的配置经过反射实例化对应的标注了@Configuration的JavaConfig配置类,而且加载到IOC容器中。

以web项目为例,在项目中加入了web环境依赖启动器,对应的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration自动配置就会生效,打开自动配置就会发现,在配置类中经过全注解的方式对 Spring MVC 运行环境所须要环境进行了默认配置,包括前缀、后缀、试图解析器、MVC校验器等。

总结

Spring Boot 底层自动配置的步骤:

1)Spring Boot 应用启动

2)@SpringBootApplication 注解起做用

3)@EnableAutoConfiguration 注解起做用

4)@AutoConfigurationPackage 注解起做用

​ @AutoConfigurationPackage 这个注解主要做用就是@Import({AutoConfigurationPackages.Registrar.class}),它经过Registrar类导入容器中,而Registrar的做用就是将扫描主配置类的包以及子包,并将对应的组件导入IOC容器中。

5)@Import(AutoConfigurationImportSelector.class)

​ @Import(AutoConfigurationImportSelector.class) 它将 AutoConfigurationImportSelector 类导入容器中,AutoConfigurationImportSelector 类的做用是经过getAutoConfigurationEntry()方法执行的过程当中,会使用内部工具类SpringFactoriesLoader,查找classpath上全部的jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给Spring Factory加载器进行一系列的容器建立过程。

2.2.2.1.3 @ComponentScan

@ComponentScan 注解具体扫描包的路径,由Spring Boot主程序所在包的位置决定。在扫描的过程当中由@AutoConfigurationPackage注解进行解析,从而获得Spring Boot主程序类所在包的具体位置

相关文章
相关标签/搜索