为什么我要写spring.factories文件?不写可以不?

在阅读spring-boot相关源码时,常常见到spring.factories文件,里面写了自动配置(AutoConfiguration)相关的类名,因此产生了一个疑问:“明明自动配置的类已经打上了@Configuration的注解,为什么还要写spring.factories文件?”

这个话题需要从@SpringBootApplication注解开始说起。

查看@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 {
    ……
}


其中比较重要的是@EnableAutoConfiguration和@ComponentScan两个注解。@ComponentScan注解的作用是扫描@SpringBootApplication所在的Application类(即spring-boot项目的入口类)所在的包(basepackage)下所有的@component注解(或拓展了@component的注解)标记的bean,并注册到spring容器中。

看到这里也许会有个疑问,在spring-boot项目中pom文件里面添加的依赖中的bean(spring-boot项目外的bean)是如何注册到spring-boot项目的spring容器中的呢?

这就需要讨论@EnableAutoConfiguration的作用。查看@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(AutoConfigurationImportSelector.class),而AutoConfigurationImportSelector.class做了什么呢?通过其源码可以看出关键的部分为,

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}


其中,getAutoConfigurationEntry方法获取了spring-boot项目中需要自动配置的项(bean),查看其源码发现,

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}


其中最重要的部分为getCandidateConfigurations方法,它获取了所有可能参与到项目的候选配置bean,与之对应的,getExclusions获取了所有不需要加载的配置bean。进一步查看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;
}


这个方法的具体实现为,读取spring-boot项目的classpath下META-INF/spring.factories的内容,这个文件常常以K/V的形式存储数据,例如:

# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.HelloAutoConfiguration,\
……


getCandidateConfigurations方法获取需要自动配置的类,除去上面讲到的需要排除(exclude)的配置类,其他类将会注册到spring-boot项目的spring容器中。

看到这里,想必已经了解@EnableAutoConfiguration注解的工作原理,回到最初的话题,“为什么要写spring.factories文件?”

结合前面提出的疑问——“在spring-boot项目中pom文件里面添加的依赖中的bean是如何注册到spring-boot项目的spring容器中的呢?”,不难得出spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器的结论。由于@ComponentScan注解只能扫描spring-boot项目包内的bean并注册到spring容器中,因此需要@EnableAutoConfiguration注解来注册项目包外的bean。而spring.factories文件,则是用来记录项目包外需要注册的bean类名。
 

在构建大型项目的时候,有时你可能会自己写一个外部依赖包(类似spring-boot-starter-xxx)。主项目添加这个外部依赖包后,这个外部依赖包里面的bean如果需要自动加入主项目的spring容器中(而不是手动添加@Bean实例化)的时候,需要使用本文的方法。如果不想使用这种方法还有其他方式吗?有的就是直接手动的添加@Bean标签即可。

你可能感兴趣的:(java,java,spring)