目 录CONTENT

文章目录

SpringBoot默认配置原理(约定大于配置)

chenming
2021-04-17 / 0 评论 / 1 点赞 / 222 阅读 / 0 字 / 正在检测是否收录...

@SpringBootApplication

@SpringBootApplication

这个注解是SpringBoot启动类的关键注解,我们首先看看这个注解会做什么。

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

我们可以看到里面有几个子注解,首先看看@SpringBootConfiguration

@SpringBootConfiguration

,这个注解标识了启动类也是个配置类。这个很好理解,启动类的底层帮我们做了很多默认的配置,这些配置都是starter以来里面定义好的(spring.factory下),所以启动类会做一些配置的东西

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

@EnableAutoConfiguration

其次再看看@EnableAutoConfiguration,里面有@AutoConfigurationPackage和@Import()。@AutoConfigurationPackage主要是把启动类,类路径等元数据进行注册,这些注册的逻辑都在AutoConfigurationPackage的内部静态类Registrar下

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

而Registrar类里面的核心方法registerBeanDefinitions就是注册元数据的地方。

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

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }
    }

BeanDefinitionRegistry 是进行注册的注册器,AnnotationMetadata 的元数据,封装了启动类的各种信息。AutoConfigurationPackages.register方法就会将元数据进行注册,而new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()则会解析元数据获取类路径。这就是为什么SpringBoot只会扫描启动类位置及其子包下的注解。原因就是这里获取的就是启动类的包路径为初始路径。

我们再看看@Import(),@Import会注入类进入ioc容器,关键是这个类AutoConfigurationImportSelector有什么用?这个类是用来将各种starter的默认配置进行加载的,那么就清晰了,上述的Registrar是用来注册自己路径下的组件,而AutoConfigurationImportSelector是用来注册依赖的starter的组件。注册的关键方法是AutoConfigurationImportSelector下的selectImports

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());
        }
    }

最关键的是AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry的获取,Entry,说明有些信息是键值对形式被获取的,我们进去看看

    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);
            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);
        }
    }

里面获取了List configurations,那么再进入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;
    }


public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
}

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

那么就很明显了,SpringFactoriesLoader会在META-INF/spring.factories下找配置文件,里面定义了各种starter的配置类全包名。debug进去后在loadFactoryNames可以看到 Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");也就是用类加载器获取该路径下的文件。最后我们回到List configurations看看返回的列表是什么(如图所示),就是当前项目依赖的starter的配置类全包名。SpringBoot会根据这些全包名加载对应配置类进行配置。因为starter都写好了这些配置类,所以我们不需要在application.yml配置太多信息就能跑起来项目。

image

小结

小结

通过注解,SpringBoot主要加载了启动类的各种元数据并注册,这是为了进行扫描配置我们自定义的配置类。其次通过自动配置扫描了META-INF/spring.factories下的文件获取sarter默认配置好的配置类进行配置(我们不需要额外配置,这就是约定)。

1

评论区