美文网首页
Springboot的自动装配

Springboot的自动装配

作者: 大杯冰摩卡 | 来源:发表于2020-03-15 20:07 被阅读0次

    熟悉Springboot的同学一定在主启动类上见过@ SpringBootApplication这个注解,如果我们注释掉@ SpringBootApplication这个注解会发生什么事呢?

    org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:156) ~[spring-boot-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    ···
    

    Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean
    由于ServletWebServerFactory没有注入导致无法启动IOC容器,那么我们就来看看@ SpringBootApplication到底做了哪些事?


    1.SpringBootApplication

    /**
     * Indicates a {@link Configuration configuration} class that declares one or more
     * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
     * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
     * annotation that is equivalent to declaring {@code @Configuration},
     * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
     *
     * @author Phillip Webb
     * @author Stephane Nicoll
     * @since 1.2.0
     */
    @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 {
    

    由文档注释可见,它是来自 SpringBoot1.2.0,其实在 SpringBoot1.1 及以前的版本,在启动类上标注的注解应该是三个:@Configuration + @EnableAutoConfiguration +@ComponentScan,只不过从1.2以后 SpringBoot 帮我们整合起来了。


    2.SpringBootConfiguration

    /**
     * Indicates that a class provides Spring Boot application
     * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
     * standard {@code @Configuration} annotation so that configuration can be found
     * automatically (for example in tests).
     * <p>
     * Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and
     * most idiomatic Spring Boot applications will inherit it from
     * {@code @SpringBootApplication}.
     *
     * @author Phillip Webb
     * @since 1.4.0
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    
    }
    

    从文档注释以及它的声明上可以看出,它被 @Configuration 标注,说明它实际上是标注配置类的,而且是标注主启动类的。被 @Configuration标注的类,会被 Spring 的IOC容器认定为配置类。相当于一个 applicationContext.xml 的配置文件。
    此外,@SpringBootConfiguration还有一个作用,我们用IDEA的find Usages功能可以看到他还与Springboot的test包有关,没错。 getOrFindConfigurationClasses这个方法就是Spring测试框架搜索主启动类用的,这里就不过多阐述了。


    3.ComponentScan

    这个注解,学过Spring的同学应该对它不会陌生,就是扫描注解,它可以指定包扫描的根路径,让 Spring 来扫描指定包及子包下的组件。不过在上面的声明中有显式的指定了两个过滤条件:

    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    3.1TypeExcludeFilter

    /**
     * Provides exclusion {@link TypeFilter TypeFilters} that are loaded from the
     * {@link BeanFactory} and automatically applied to {@code SpringBootApplication}
     * scanning. Can also be used directly with {@code @ComponentScan} as follows:
     * <pre class="code">
     * &#064;ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
     * </pre>
     * <p>
     * Implementations should provide a subclass registered with {@link BeanFactory} and
     * override the {@link #match(MetadataReader, MetadataReaderFactory)} method. They should
     * also implement a valid {@link #hashCode() hashCode} and {@link #equals(Object) equals}
     * methods so that they can be used as part of Spring test's application context caches.
     * <p>
     * Note that {@code TypeExcludeFilters} are initialized very early in the application
     * lifecycle, they should generally not have dependencies on any other beans. They are
     * primarily used internally to support {@code spring-boot-test}.
     *
     * @author Phillip Webb
     * @since 1.4.0
     */
    

    通过文档注释我们可以看到TypeExcludeFilter提供从BeanFactory加载并自动应用于SpringBootApplication扫描的排除TypeFilter。 也可以直接与@ComponentScan一起使用,如下所示:ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
    实现应提供一个向 BeanFactory 注册的子类,并重写 match(MetadataReader, MetadataReaderFactory) 方法。它们还应该实现一个有效的 hashCode 和 equals 方法,以便可以将它们用作Spring测试的应用程序上下文缓存的一部分。
    这个Filter的核心方法是 match 方法,它实现了过滤的判断逻辑:

    @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                throws IOException {
                //从BeanFactory中获取所有类型为 TypeExcludeFilter 的组件,去执行自定义的过滤方法。
            if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
                Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory)
                        .getBeansOfType(TypeExcludeFilter.class).values();
                for (TypeExcludeFilter delegate : delegates) {
                    if (delegate.match(metadataReader, metadataReaderFactory)) {
                        return true;
                    }
                }
            }
            return false;
        }
    

    由此可见,TypeExcludeFilter 的作用是做扩展的组件过滤。

    3.2AutoConfigurationExcludeFilter

    @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                throws IOException {
            return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
        }
    
        private boolean isConfiguration(MetadataReader metadataReader) {
            return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
        }
    
        private boolean isAutoConfiguration(MetadataReader metadataReader) {
            return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
        }
    
        protected List<String> getAutoConfigurations() {
            if (this.autoConfigurations == null) {
                this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
                        this.beanClassLoader);
            }
            return this.autoConfigurations;
        }
    

    这里的 match 方法要判断两个部分:是否是一个配置类,是否是一个自动配置类,关于SpringFactoriesLoader.loadFactoryNames我们后面会详细讲解。
    注意,TypeExcludeFiltersAutoConfigurationExcludeFilter在应用程序生命周期的很早就初始化了,它们通常不应该依赖于任何其他bean。它们主要在内部用于支持 spring-boot-test 。


    4.核心EnableAutoConfiguration

    老规矩,我们还是来看下文档注释:

    /**
     * Enable auto-configuration of the Spring Application Context, attempting to guess and
     * configure beans that you are likely to need. Auto-configuration classes are usually
     * applied based on your classpath and what beans you have defined. For example, if you
     * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
     * {@link TomcatServletWebServerFactory} (unless you have defined your own
     * {@link ServletWebServerFactory} bean).
       启用Spring-ApplicationContext的自动配置,并且会尝试猜测和配置您可能需要的Bean。
       通常根据您的类路径和定义的Bean来应用自动配置类。
       例如,如果您的类路径上有 tomcat-embedded.jar,则可能需要 TomcatServletWebServerFactory (除非自己已经定义了 ServletWebServerFactory 的Bean)
     * <p>
     * When using {@link SpringBootApplication}, the auto-configuration of the context is
     * automatically enabled and adding this annotation has therefore no additional effect.
       使用 @SpringBootApplication 时,将自动启用上下文的自动配置,因此再添加该注解不会产生任何其他影响。
     * <p>
     * Auto-configuration tries to be as intelligent as possible and will back-away as you
     * define more of your own configuration. You can always manually {@link #exclude()} any
     * configuration that you never want to apply (use {@link #excludeName()} if you don't
     * have access to them). You can also exclude them via the
     * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
     * after user-defined beans have been registered.
       自动配置会尝试尽可能地智能化,并且在您定义更多自定义配置时会自动退出(被覆盖)。您始终可以手动排除掉任何您不想应用的配置(如果您无法访问它 
       们,请使用 excludeName() 方法),您也可以通过 spring.autoconfigure.exclude 属性排除它们。自动配置始终在注册用户自定义的Bean之后应用。
     * <p>
     * The package of the class that is annotated with {@code @EnableAutoConfiguration},
     * usually via {@code @SpringBootApplication}, has specific significance and is often used
     * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
     * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
     * not using {@code @SpringBootApplication}) in a root package so that all sub-packages
     * and classes can be searched.
       通常被 @EnableAutoConfiguration 标注的类(如 @SpringBootApplication)的包具有特定的意义,通常被用作“默认值”。例如,在扫描@Entity类时将使用 
       它。通常建议您将 @EnableAutoConfiguration(如果您未使用 @SpringBootApplication)放在根包中,以便可以搜索所有包及子包下的类。
     * <p>
     * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
     * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
     * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
     * often using {@link ConditionalOnClass @ConditionalOnClass} and
     * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
        自动配置类也是常规的Spring配置类。它们使用 SpringFactoriesLoader 机制定位(针对此类)。通常自动配置类也是 @Conditional Bean(最经常的情况下    
        是使用 @ConditionalOnClass 和 @ConditionalOnMissingBean 标注)。
     *
     */
    

    @EnableAutoConfiguration也是一个组合注解

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

    4.1AutoConfigurationPackage

    /**
     * Indicates that the package containing the annotated class should be registered with
     * {@link AutoConfigurationPackages}.
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    }
    

    文档翻译表示包含该注解的类所在的包应该在 AutoConfigurationPackages 中注册。
    网上将这个@AutoConfigurationPackage注解解释成自动配置包,咱从一开始学 SpringBoot 就知道一件事:主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们。由此可知是它起的作用。
    它的实现原理是在注解上标注了 @Import,导入了一个 AutoConfigurationPackages.Registrar
    这里我们聊一下@Import,它是干嘛的呢?
    在Spring框架中,关于配置项我们不可能全部写在一个类里面,这个时候就要使用@Import,总结下来它的作用就是和xml配置的 <import />标签作用一样,允许通过它引入 @Configuration 注解的类 (java config), 引入ImportSelector接口的实现(这个比较重要, 因为要通过它去判定要引入哪些@Configuration) 和ImportBeanDefinitionRegistrar接口的实现,也包括 @Component注解的普通类

    4.1.1AutoConfigurationPackages.Registrar

    /**
         * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
         * configuration.
                用于保存导入的配置类所在的根包。
         */
        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                register(registry, new PackageImport(metadata).getPackageName());
            }
    
            @Override
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new PackageImport(metadata));
            }
    
        }
    

    Registrar实现了ImportBeanDefinitionRegistrar 接口,向IOC容器里手动注册组件。我们来看下Registrar重写的registerBeanDefinitions方法:
    先看下传参new PackageImport(metadata).getPackageName()
    它实例化的 PackageImport 对象的构造方法:

    PackageImport(AnnotationMetadata metadata) {
                this.packageName = ClassUtils.getPackageName(metadata.getClassName());
            }
    

    它获取了metadata 的所在包名。这里的metadata是什么呢?
    翻看 ImportBeanDefinitionRegistrar接口的文档注释:

    public interface ImportBeanDefinitionRegistrar {
    
        /**
         * Register bean definitions as necessary based on the given annotation metadata of
         * the importing {@code @Configuration} class.
         * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
         * registered here, due to lifecycle constraints related to {@code @Configuration}
         * class processing.
         * @param importingClassMetadata annotation metadata of the importing class
         * @param registry current bean definition registry
         */
        void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
    
    }
    

    注意 importingClassMetadata 的参数说明:导入类的注解元数据
    它实际代表的是被 @Import 标记的类的信息。
    通过Debug我们也可以得到:


    实际上new PackageImport(metadata).getPackageName()是为了获取Springboot主启动类的包名,拿来干什么?我们在回到register方法中
    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
            if (registry.containsBeanDefinition(BEAN)) {
                BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
                ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
                constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
            }
            else {
                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                beanDefinition.setBeanClass(BasePackages.class);
                beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
                beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                registry.registerBeanDefinition(BEAN, beanDefinition);
            }
        }
    

    通过代码我们可以得知,register会判断当前容器里面是否包含了AutoConfigurationPackages,如果有就把拿传入的包名设置到一个 basePackage里面,如果没有就新建一个GenericBeanDefinition,追加上basePackage注册到容器里面。很明显这里的basePackage就是根包,这么做也就是为了拿到主启动类所在包及子包下的组件。
    补充:
    通过debug我们可以得到在Web项目里,register()的执行顺序

      SpringApplication.run()
          => refreshContext()
            => EmbeddedWebApplicationContext.refresh()
              => AbstractApplicationContext.invokeBeanFactoryPostProcessors()
                => PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
                  => ConfigurationClassPostProcessor.processConfigBeanDefinitions()
                    => ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
                      => loadBeanDefinitionsFromRegistrars()
                        => AutoConfigurationPackages内部类Registrar.registerBeanDefinitions()
                          => AutoConfigurationPackages.register()
    

    4.2@Import(AutoConfigurationImportSelector.class)

    前面已经聊了@Import的用法,这里实际上是导入了一个 ImportSelector来向容器中导入组件。
    导入的组件是:AutoConfigurationImportSelector

    /**
     * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
     * auto-configuration}. This class can also be subclassed if a custom variant of
     * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
    DeferredImportSelector 处理自动配置。如果需要自定义扩展 @EnableAutoConfiguration,则也可以编写该类的子类。
     *
     * @author Phillip Webb
     * @author Andy Wilkinson
     * @author Stephane Nicoll
     * @author Madhura Bhave
     * @since 1.3.0
     * @see EnableAutoConfiguration
     */
    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
            ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    

    文档里面专门提到了DeferredImportSelector,这是个什么东西我们还要来看下:

    4.2.1DeferredImportSelector

    /**
     * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
     * have been processed. This type of selector can be particularly useful when the selected
     * imports are {@code @Conditional}.
        ImportSelector 的一种扩展,在处理完所有 @Configuration 类型的Bean之后运行。当所选导入为 @Conditional 时,这种类型的选择器特别有用。
     * <p>Implementations can also extend the {@link org.springframework.core.Ordered}
     * interface or use the {@link org.springframework.core.annotation.Order} annotation to
     * indicate a precedence against other {@link DeferredImportSelector DeferredImportSelectors}.
        实现类还可以扩展 Ordered 接口,或使用 @Order 注解来指示相对于其他 DeferredImportSelector 的优先级。
     * <p>Implementations may also provide an {@link #getImportGroup() import group} which
     * can provide additional sorting and filtering logic across different selectors.
        实现类也可以提供导入组,该导入组可以提供跨不同选择器的其他排序和筛选逻辑。
     */
    public interface DeferredImportSelector extends ImportSelector {
    

    由此我们可以知道,DeferredImportSelector 的执行时机,是在 @Configuration 注解中的其他逻辑被处理完毕之后(包括对 @ImportResource@Bean 这些注解的处理)再执行,换句话说,DeferredImportSelector的执行时机比 ImportSelector更晚。

    4.2.2selectImport

    我们再回到AutoConfigurationImportSelector,它的核心还是重写的selectImport方法。

    @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(autoConfigurationMetadata,annotationMetadata)中:

    /**
         * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
         * of the importing {@link Configuration @Configuration} class.
         * @param autoConfigurationMetadata the auto-configuration metadata
         * @param annotationMetadata the annotation metadata of the configuration class
         * @return the auto-configurations that should be imported
         */
        protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
                    // 加载候选的自动配置类
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    

    这个方法里有一个非常关键的集合,configurations是加载自动配置类的集合,通过getCandidateConfigurations方法来获取:

    /**
         * Return the auto-configuration class names that should be considered. By default
         * this method will load candidates using {@link SpringFactoriesLoader} with
         * {@link #getSpringFactoriesLoaderFactoryClass()}.
         * @param metadata the source metadata
         * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
         * attributes}
         * @return a list of candidate configurations
         */
        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;
        }
    
    /**
         * Return the class used by {@link SpringFactoriesLoader} to load configuration
         * candidates.
         * @return the factory class
         */
        protected Class<?> getSpringFactoriesLoaderFactoryClass() {
            return EnableAutoConfiguration.class;
        }
    

    这个方法又调用了 SpringFactoriesLoader.loadFactoryNames 方法,传入的Class就是 @EnableAutoConfiguration

    4.2.3SpringFactoriesLoader.loadFactoryNames

    SpringFactoriesLoader实际上是SpringFramework3.2 就已经有了的类,它是一个框架内部内部使用的通用工厂加载机制。
    SpringFramework 利用 SpringFactoriesLoader 都是调用 loadFactoryNames 方法:

    /**
         * Load the fully qualified class names of factory implementations of the
         * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
         * class loader.
    使用给定的类加载器从 META-INF/spring.factories 中加载给定类型的工厂实现的全限定类名。
         */
    
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    

    来看方法实现,第一行代码获取的是要被加载的接口/抽象类的全限定名,下面的 return 分为两部分:loadSpringFactoriesgetOrDefaultgetOrDefault 方法很明显是Map中的方法,不再解释,主要来详细看 loadSpringFactories 方法。

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    

    我们分开来看

    4.2.3.1 获取本地缓存

      MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
    

    进入方法后先从本地缓存中根据当前的类加载器获取是否有一个类型为 MultiValueMap<String, String> 的值, MultiValueMap<String, String>实际上就是一个Map<K, List<V>>

    4.2.3.2 加载spring.factories

         Enumeration<URL> urls = (classLoader != null ?
                     classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
    

    这部分动作就是获取当前 classpath 下所有jar包中有的 spring.factories 文件,并将它们加载到内存中。
    代码中使用 classLoader 去加载了指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION ,而这个常量指定的路径实际是:META-INF/spring.factories

    /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    

    这个文件在 spring-boot-autoconfiguration 包下可以找到。

    spring.factories
    spring.factories是一个接口名和实现类名key-vlaue的集合,实现类的配置按照","符号分割开,这么设计的好处:不再局限于接口-实现类的模式,key可以随意定义!
    拿到spring.factories这个文件,以 Properties 的形式加载,并取出org.springframework.boot.autoconfigure.EnableAutoConfiguration 指定的所有自动配置类(是一个很大的字符串,里面都是自动配置类的全限定类名),装配到IOC容器中,自动配置类就会通过ImportSelector@Import 的机制被创建出来,之后就生效了。
    这也就解释了为什么 即便没有任何配置文件,SpringBoot的Web应用都能正常运行。

    4.2.3.2 缓存到本地

     while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
    

    它拿到每一个文件,并用 Properties 方式加载文件,之后把这个文件中每一组键值对都加载出来,放入 MultiValueMap 中。
    StringUtils.commaDelimitedListToStringArray会将大字符串拆成一个一个的全限定类名。
    整理完后,整个result放入cache中。下一次再加载时就无需再次加载 spring.factories 文件了。


    总结

    • @SpringBootApplication 是一个组合注解。包含@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
    • @ComponentScan 中的 exclude 属性会将主启动类、自动配置类屏蔽掉。
    • @Configuration 可标注配置类,@SpringBootConfiguration 并没有对其做实质性扩展。
    • SpringFramework 提供了模式注解、@EnableXXX + @Import 的组合手动装配。
    • @SpringBootApplication 标注的主启动类所在包会被视为扫描包的根包。
    • AutoConfigurationImportSelector 配合 SpringFactoriesLoader 可加载 “META-INF/spring.factories” 中配置的 @EnableAutoConfiguration 对应的自动配置类。
    • DeferredImportSelector 的执行时机比 ImportSelector 更晚。

    相关文章

      网友评论

          本文标题:Springboot的自动装配

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