美文网首页
spring-factories文件的加载时机

spring-factories文件的加载时机

作者: 7d972d5e05e8 | 来源:发表于2020-08-28 14:50 被阅读0次

    参考文章: 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类一样。

    相关文章

      网友评论

          本文标题:spring-factories文件的加载时机

          本文链接:https://www.haomeiwen.com/subject/hujlsktx.html