扫码关注微信公众号

回复“面试手册”,获取本站PDF版

回复“简历”,获取高质量简历模板

回复“加群”,加入程序员交流群

回复“电子书”,获取程序员类电子书

当前位置: Java > Spring高频面试题 > 36.SpringBoot自动配置原理

这是一个面试高频题

Springboot相比于Spring最大的区别就是简化了各种配置,只需要一行@SpringBootApplication注解,项目就能启动起来,非常方便。那么Springboot是如何实现自动配置的呢?

简单来说就是@SpringBootApplication注解会对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@Condition注解指定的条件时,便在依赖的支持下进行实例化,注册到Spring容器中。

spring.factories
spring.factories

下面看看具体是怎么实现的?点开@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}
)}
)
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 {};
}

可以很容易看到@EnableAutoConfiguration注解的名字和自动配置是非常相近的,这也是Springboot实现自动配置的关键,点开@EnableAutoConfiguration注解,如下

@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 {};
}

这里需要重点关注的是@Import注解,点开如下,截取两个比较重要的方法

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            //加载自动配置的元信息
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, 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);
            //获得注解中被exclude和excludeName排除的类的集合
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
           	//从候选配置类中去除掉被排除的类
            configurations.removeAll(exclusions);
            //根据条件过滤
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            //返回符合条件的自动配置类的全限定名数组
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

从上述代码可以看到符合条件的自动配置类也是经过很多条件筛选出来的,看到获取配置类的方法是getCandidateConfigurations,打开如下

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

这时又看到了loadFactoryNames方法,继续打开,

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

    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 {
                //获取配置类的url
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    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();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

终于看到了获取spring.factories的代码,上述代码主要就是通过SpringFactoriesLoader加载器加载META-INF/spring.factories文件,首先通过这个文件获取到每个配置类的url,再通过这些url将它们封装成Properties对象,最后解析内容存于Map<String,List<String>>

回到前面,就是通过getCandidateConfigurations方法调用loadFactoryNames方法,loadFactoryNames方法调用loadFactoryNames方法方法,获取到了原始的配置类,然后再经过去重过滤等操作通过selectImports()方法返回一个自动配置类的全限定名数组。

因为前有个过滤配置类的地方,如下

所以还有一些可以配置过滤条件的注解,如下

  • @ConditionalOnJava,使用指定的 Java 版本时生效
  • @ConditionalOnNotWebApplication,当前应用不是 web 应用时生效
  • @ConditionalOnWebApplication,当前应用是 web 应用时生效
  • @ConditionalOnBean,容器中存在指定的 Bean 时生效
  • @ConditionalOnResource,类路径下存在指定的资源文件时生效
  • @ConditionalOnExpression, 满足指定的 SpEL 表达式时生效
  • @ConditionalOnProperty,系统中指定属性存在指定的值时生效
  • @ConditionalOnClass,存在指定的类时生效
  • @ConditionalOnSingleCandidate ,当指定的bean在容器中只有一个,或者存在多个时但指定了首选的bean

点击面试手册,获取本站面试手册PDF完整版