spring-factories文件的加载时机

2020-08-28  本文已影响0人  7d972d5e05e8

参考文章: spring.factories

先说下原理:spring.factories是spring-boot的SPI机制,采用key=value键值对的形式保存。springBoot拓展了java spi,不仅仅支持接口,还支持注解。比如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.xxx.xxx.autocfg.xxxDataSourceConfiguration

EnableAutoConfiguration就是注解。

Spring在启动的时候,会在不同生命周期的地方调用SpringFactoriesLoader . loadFactoryNames。然后在不同生命周期的地方,匹配出特定的接口或者注解,然后进行处理。(不同注解或者接口,作用于不同的生命周期。)

参考:BeanDefinitionRegistryPostProcessor -> BeanFactoryPostProcessor -> InstantiationAwareBeanPostProcessor -> BeanPostProcessor
当然我这只列出来四个spring最常用的扩展点接口。其实还有其他扩展点接口,但是由于不常用就不列出了,只需要知道它们会被spring安排的明明白白,只会出现在它们该出现的地方。我们程序员能做的仅仅是实现这些拓展点,并不能改变拓展点被执行的位置(即不能改变拓展点的生命周期位置)。

什么还不知道拓展点是啥?
拓展点就是spring为我们开发人员预留好的接口,或者注解等等。

比如:
1、org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization,这个就是拓展点。作用于bean实例化后,初始化前的生命周期
2、org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization。该扩展点,作用于bean执行完afterPropertiesSet和init-method之后的生命周期。

下面上源码:
比如:在spring启动的时候,会调用invokeBeanFactoryPostProcessors核心方法。该方法里面就拓展了加载spring-factories的地方。

具体方法在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry里面。如下:

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);

        processConfigBeanDefinitions(registry);
    }

继续追踪到org.springframework.context.annotation.ConfigurationClassParser#parse方法,如下:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }

        processDeferredImportSelectors();
    }

核心就在processDeferredImportSelectors方法。

private void processDeferredImportSelectors() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

        for (DeferredImportSelectorHolder deferredImport : deferredImports) {
            ConfigurationClass configClass = deferredImport.getConfigurationClass();
            try {
                String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
                processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
        }
    }

1、DeferredImportSelector 是 ImportSelector 的一个变种。
2、 ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector的类可以条件性地决定导入哪些配置。
3、DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是该语句被放到本函数最后一行的原因。

该方法有很重要的一句代码:

String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());

select出所有deferredImport类,即延迟import类。
进入方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports中:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //重点看!!!!!
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            configurations = sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

看下getCandidateConfigurations方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        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;
    }

是不是找到地方了,loadFactoryNames出现了。参数是:EnableAutoConfiguration,classLoader。这句话的意思就是:从所有spring-factoies加载的key-value对中,找出EnableAutoConfiguration注解的所有实现类。不管这个实现类,是在哪个jar包下,都会被加载到。如果我们想要把自己的jar打给别人,然后又想在人家应用启动的时候加载我们自己bean,那么就可以在jar包里面添加spring-factories文件,添加一个key-value:org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxx。类似文章开始那样。看下我们工程配置,如下:

image.png

通过这种方式,我们就可以在应用启动时,自动加载二方包配置的bean,无需本应用开发者配置任何代码。如果是以前,可能需要写个config类,自己手动配置很多二方包的bean。现在只要对方把spring-factoies加入它们的jar包,然后所以依赖该包的应用,都无需显示配置了。

总结:

  1. spring-factories是spring给开发人员提供的SPI
  2. spring在启动过程中的整个生命周期,会在不同位置调用不同拓展点。而这些拓展点是开发人员实现的,为了让spring能够动态加载这些拓展点,需要SPI能力
  3. 想加载二方包里面的bean,但是又不想配置。那么二方包里面的spring-factories能够满足你。它里面的自动配置类,就相当于你自己工程的xxxApplication类一样。
上一篇下一篇

猜你喜欢

热点阅读