《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)

@[TOC]

一、前言

我们前四篇博文,详细讨论了SpringBoot整个启动流程。博文如下:

1> 《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息
2> 《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段
3> 《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)
4> 《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段

在启动流程中涉及SpringBoot的自动装配,虽然在之前我们聊过<SpringBoot自动装配机制原理>,但其中没有聊到@EnableAutoConfiguration@Import注解是在何时被扫描的,本文就这一部分展开讨论。

注:Spring Boot版本:2.3.7.RELEASE。

二、入口

在Spring应用上下文准备阶段prepareContext()方法将应用的启动类加到Context中。

在Spring应用上下文启动阶段,会进入到refreshContext()方法,具体代码执行流程如下:
在这里插入图片描述
ServletWebServerApplicationContext的类图:
在这里插入图片描述
ServletWebServerApplicationContext间接继承自AbstractApplicationContext,所以最终会进入到AbstractApplicationContext#refresh()方法。

走到AbstractApplicationContext#refresh()方法便意味着Spring应用上下文进入Spring生命周期,Spring Boot核心特性随之启动,比如:自动装配。

三、处理自动装配

1、处理自动装配的入口

在这里插入图片描述
最终进入到PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory,List<BeanFactoryPostProcessor>)方法中,自动装配在其中实现;
在这里插入图片描述

invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory,List<BeanFactoryPostProcessor>)方法中,传入接收到的入参beanFactory类型为DefaultListableBeanFactory
在这里插入图片描述
再看DefaultListableBeanFactory的类图:
在这里插入图片描述
其实现了BeanDefinitionRegistry接口,所以会进入到if代码块:
在这里插入图片描述
在进入if代码块之后,会做两个操作:

1> 首先,遍历传入的三个BeanFactoryPostProcessor对其做分类;

分为常规后置处理器集合regularPostProcessors 和 注册处理器集合registryProcessors;
在这里插入图片描述
分类之后,regularPostProcessors有一个成员,registryProcessors中有两个成员。

final class PostProcessorRegistrationDelegate {

    public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
        ....
        
        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let the bean factory post-processors apply to them!
        // Separate between BeanDefinitionRegistryPostProcessors that implement
        // PriorityOrdered, Ordered, and the rest.
        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
        
        // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
        // 这里只有一个值:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
        String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            // internalConfigurationAnnotationProcessor实现了PriorityOrdered接口
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                // 将ConfigurationClassPostProcessor添加到currentRegistryProcessors中
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        // 对currentRegistryProcessors做一个排序
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        // 走到这里registryProcessors中有三个对象了
        // todo 核心所在
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
        
        ....
    }
}

在这里插入图片描述
接着从BeanFactory中获取到BeanDefinitionRegistryPostProcessor的实现类ConfigurationClassPostProcessor,并将其添加到registryProcessors中;此时registryProcessors中有三个成员:

  1. SharedMetadataReaderFactoryContextInitializer的静态内部类CachingMetadataReaderFactoryPostProcessor
  2. ConfigurationWarningsApplicationContextInitializer的静态内部类ConfigurationWarningsPostProcessor
  3. ConfigurationClassPostProcessor

2> 其次,执行当前注册处理器ConfigurationClassPostProcessor;

代码执行流程如下:
在这里插入图片描述
由于postProcessors中只有一个成员ConfigurationClassPostProcessor,进入到ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法。
在这里插入图片描述
ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry)方法开始真正进入到处理自动装配的核心逻辑。

2、处理自动装配内容

1)找启动类,构建ConfigurationClassParser解析器准备解析启动类

首先从DefaultLisableBeanFactory中获取所有已经注册的BeanDefinition名称;
在这里插入图片描述
candidateNames中包含了我们的启动类,此外还有6个internalXxx类;然后遍历找到启动类,将其加到configCandidates集合中。
在这里插入图片描述
找到启动类(saintSpringBootApplicatioin)之后,构建一个配置类解析器ConfigurationClassParser,其中包括ComponentScanAnnotationParser、ConditionEvaluator,分别用于包扫描和条件装配;
在这里插入图片描述
接着调用ConfigurationClassParser#parse()方法开始解析启动类进行应用程序的启动。
在这里插入图片描述

2)解析启动类

以ConfigurationClassParser#parse()方法为入口,部分代码执行流程如下:
在这里插入图片描述
其中在做条件装配时,有个点需要注意一下:ConfigurationCondition接口内部的枚举类ConfigurationPhase中有两个值PARSE_CONFIGURATIONREGISTER_BEAN,分别表示:在类解析阶段做条件装配、在类注册阶段做条件装配。

if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
}

在这里插入图片描述
1> 继续看获取SourceClass的代码逻辑:
在这里插入图片描述
其中会校验启动类上@SpringBootApplication注解的合法性,然后将启动类和其注解元数据AnnotationMetadata封装到SourceClass中返回。

如果获取不到SourceClass,则不会执行配置类(启动类)的处理。

2> 获取到SourceClass之后,处理配置类和SourceClass:
在这里插入图片描述
ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass,SourceClassPredicate<String>)方法中会处理如下内容:

  1. 如果启动类configClass被@Component的衍生注解(递归注解的父注解可以找到@Component)标注,则首先递归处理所有成员(嵌套)类:即 configClass类内部如果找到成员类,会递归调用doProcessConfigurationClass()方法处理所有成员类。
  2. 解析启动类中所有的@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean注解。

具体代码如下:

class ConfigurationClassParser {
    ....
    
    @Nullable
    protected final SourceClass doProcessConfigurationClass(
            ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
            throws IOException {

        // 启动类configClass被@Component的衍生注解(递归注解的父注解可以找到@Component)标注
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // 首先递归处理所有成员(嵌套)类:configClass类内部如果找到成员类,会递归调用doProcessConfigurationClass()方法处理所有成员类。
            processMemberClasses(configClass, sourceClass, filter);
        }

        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            //  componentScan中包含11个成员,对应于@ComponentScan中11个属性
            for (AnnotationAttributes componentScan : componentScans) {
                // 处理@ComponentScan 中的属性,返回所有派生的@Component注解标注的类,然后立即进行扫描
                // 此处会找到basePackages,其默认为启动类所在的目录
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                // TODO 实际业务中这里定义了多个派生@Component注解标注的类,这里就会循环多少次
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        // Process any @Import annotations
        // getImports()方法从启动类中获取所有的@Import注解的内容
        processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

        // Process any @ImportResource annotations
        AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);

        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") &&
                    !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }

        // No superclass -> processing is complete
        return null;
    }
    ....

}

就一个最简单、最干净的SpringBoot程序来看,其中没有@PropertySource、@ImportResource注解,平时工程中也很少使用。所以本文我们着重看@ComponentScan、@Import两个注解的处理流程(也就是我们自动装配的核心所在)。

3)解析启动类中的@ComponentScan注解

// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    //  componentScan中包含11个成员,对应于@ComponentScan中11个属性
    for (AnnotationAttributes componentScan : componentScans) {
        // 处理@ComponentScan 中的属性,返回所有派生的@Component注解标注的类,然后立即进行扫描
        // 此处会找到basePackages,其默认为启动类所在的目录
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        // TODO 实际业务中这里定义了多个派生@Component注解标注的类,这里就会循环多少次
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
        }
    }
}

首先获取启动类@SpringBootApplication注解中的11个属性,然后调用ComponentScanAnnotationParser#parse()方法处理@SpringBootApplication注解中的注解 并 设置到类路径BeanDefinition扫描器ClassPathBeanDefinitionScanner的相应属性中。

class ComponentScanAnnotationParser {
    ....

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

        Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
        boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
        scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
                BeanUtils.instantiateClass(generatorClass));

        ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
        if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
            scanner.setScopedProxyMode(scopedProxyMode);
        }
        else {
            Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
            scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
        }

        scanner.setResourcePattern(componentScan.getString("resourcePattern"));

        for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) {
            List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment,
                    this.resourceLoader, this.registry);
            for (TypeFilter typeFilter : typeFilters) {
                scanner.addIncludeFilter(typeFilter);
            }
        }
        for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) {
            List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment,
                this.resourceLoader, this.registry);
            for (TypeFilter typeFilter : typeFilters) {
                scanner.addExcludeFilter(typeFilter);
            }
        }

        boolean lazyInit = componentScan.getBoolean("lazyInit");
        if (lazyInit) {
            scanner.getBeanDefinitionDefaults().setLazyInit(true);
        }

        Set<String> basePackages = new LinkedHashSet<>();
        String[] basePackagesArray = componentScan.getStringArray("basePackages");
        for (String pkg : basePackagesArray) {
            String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            Collections.addAll(basePackages, tokenized);
        }
        for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        // 默认会走到这里
        if (basePackages.isEmpty()) {
            // 默认,basePackages为启动类所在的目录。eg:启动类为com.saint.SaintSpringBootApplication,basePackages为:com.saint
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

        scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
            @Override
            protected boolean matchClassName(String className) {
                return declaringClass.equals(className);
            }
        });
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }

}

basePackages属性为@ComponentScan注解的默认扫描包路径,如果没指定该属性,则会将启动类所在的包作为默认值 赋值basePackages属性上(以启动类SaintSpringBootApplication为例,其默认扫包路径为:com.saint)。
在这里插入图片描述
给ClassPathBeanDefinitionScanner制定完所有属性之后,会调用其doScan(String...)方法扫描basePackages目录下的所有标注了@Component衍生注解(比如:@Controller、@Service、@Repository)的类。
具体代码执行流程如下:
在这里插入图片描述
获取并注册完所有的@Component衍生类之后,在递归对这些类做解析。
在这里插入图片描述

4)解析启动类中的@Import注解

在此之前,我聊SpringBoot自动装配都是说,@EnableAutoConfiguration注解中通过@Import注解导入了AutoConfigurationImportSelector.class,@EnableAutoConfiguration注解中的@AutoConfigurationPackage中通过@Import注解导入了AutoConfigurationPackages.Registrar.class类,但我并不知道这里的@Import是在何时处理的!!这里我们就看一下针对@Import注解是怎么处理的。

processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

这里分两步,首先通过getImports()方法获取启动类中的@Import注解,然后再通过processImports()方法处理所有的@Import注解。

1> getImports()方法获取启动类中所有的@Import注解:
在这里插入图片描述
具体递归流程如下:
在这里插入图片描述
最终获取的@import注解有两个:
在这里插入图片描述

2> processImports()方法处理获取到的启动类中所有的@Import注解:

先处理AutoConfigurationPackages.Registrar.class类,再处理AutoConfigurationImportSelector类;
在这里插入图片描述
对AutoConfigurationPackages.Registrar.class类的处理比较简单,利用反射将其实例化之后,添加到启动类的importBeanDefinitionRegistrars属性中。

在这里插入图片描述
由于AutoConfigurationImportSelector实现了DeferredImportSelector接口,所以会对AutoConfigurationImportSelector进行一个处理:将AutoConfigurationImportSelector封装为DeferredImportSelectorHolder对象,然后添加到ConfigurationClassParser类的deferredImportSelectors属性中(供后面处理@Import内容)。
在这里插入图片描述

5)处理所有的自动装配类

最后回到ConfigurationClassParser#parse()方法中:
在这里插入图片描述
代码执行流程如下:
在这里插入图片描述
最终进入到AutoConfigurationImportSelector#getAutoConfigurationEntry(AnnotationMetadata)方法,这里的代码逻辑相信大家嘎嘎眼熟,在SpringBoot自动装配机制原理一文中我们聊过。

AutoConfigurationImportSelector#getAutoConfigurationEntry(AnnotationMetadata)方法源代码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 1. 获取@EnableAutoConfiguration标注类的元信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 2. 返回自动装配类的候选类名集合
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 3. 移除重复对象,因为 自动装配组件存在重复定义的情况
    configurations = removeDuplicates(configurations);
    // 4. 自动装配组件的排除名单
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 5.1. 检查自动装配Class排除集合的合法性
    checkExcludedClasses(configurations, exclusions);
    // 5.2 排除掉不需要自动装配的Class
    configurations.removeAll(exclusions);
    // 6. 进一步过滤
    configurations = getConfigurationClassFilter().filter(configurations);
    // 7. 触发自动装配的导入事件,事件包括候选的装配组件类名单和排除名单。
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

其负责拿到所有自动配置的节点,大致分为六步;

  1. 第一步,在getCandidateConfigurations()方法中利用Spring Framework工厂机制的加载器SpringFactoriesLoader,通过SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合。
  2. 第二步,利用Set不可重复性对自动装配Class集合进行去重,因为自动装配组件存在重复定义的情况;
  3. 第三步,读取当前配置类所标注的@EnableAutoConfiguration注解的属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性的值 合并为自动装配class排除集合
  4. 第四步,校验自动装配Class排除集合的合法性、并排除掉自动装配Class排除集合中的所有Class(不需要自动装配的Class)。
  5. 第五步,再次过滤后候选自动装配Class集合中不符合条件装配的Class成员;
  6. 最后一步,触发自动装配的导入事件。

phase1> getCandidateConfigurations() --> 获取自动装配类:

代码执行流程如下:
在这里插入图片描述
getSpringFactoriesLoaderFactoryClass()方法返回我们熟悉的EnableAutoConfiguration注解类;
在这里插入图片描述
紧接着,SpringFactoriesLoader.loadFactoryNames(Class<?>, ClassLoader)方法会获取所有META-INF/Spring.factories的配置文件,进而获取到所有的自动装配类;

loadFactoryNames()原理如下:

  1. 搜索指定ClassLoader下所有的META-INF/spring.fatories资源内容;
  1. 将搜索到的资源内容作为Properties文件读取,合并为一个Key为接口的全类名、Value为实现类全类名 列表的Map,作为方法的返回值;
  2. 最后从上一步返回的Map中查找并返回方法指定类型 对应的实现类全类名列表

loadFactoryNames()方法源码解释如下:

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()) {
            /**
             * 查找所有我们依赖的jar包,并找到对应有META-INF/spring.factories⽂件,然后获取⽂件中的内容
             * 
             * 第一次循环:file:/.../org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar!/META-INF/spring.factories
             * 第二次循环:file:/.../org/springframework/boot/spring-boot/2.3.7.RELEASE/spring-boot-2.3.7.RELEASE.jar!/META-INF/spring.factories
             * 第三次循环:file:/../org/springframework/boot/spring-boot-autoconfigure/2.3.7.RELEASE/spring-boot-autoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories
             */
            URL url = urls.nextElement();
            // 获取资源
            UrlResource resource = new UrlResource(url);
            // 获取资源的内容
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
  • 第一次扫描:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Orm39VX-1648878127869)(./picture/SpringBoot/SpringBoot自动装配3.png)]

  • 第二次扫描:

在这里插入图片描述
在这里插入图片描述

  • 第三次扫描:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3P2kP2p-1648878127870)(./picture/SpringBoot/SpringBoot自动装配6.png)]

phase5> getConfigurationClassFilter().filter(configurations) --> 过滤候选自动装配Class集合中不符合条件装配的Class成员:

这里分两步:

第一步:获取所有的Filter在这里插入图片描述

首先通过getConfigurationClassFilter()方法从所有META-INF/spring.factories文件中获取所有的自动装配过滤器AutoConfigurationImportFilter的实现类(一共有三个),然后实例化AutoConfigurationImportSelector类的内部类ConfigurationClassFilter,并将获取多的所有AutoConfigurationImportFilter的实现类集合赋值到ConfigurationClassFilter的filters属性中(后面会用到它做条件装配)。
在这里插入图片描述

第二步:执行条件装配

然后对获取到的所有自动装配类(最干净、最简单的SpringBoot程序有127个)执行过滤操作(条件装配)后,还剩23个自动装配类。
在这里插入图片描述
关于此处为什么127个自动装配类经过AutoConfigurationImportFilter过滤后只剩23个了,且听下回分解《SpringBoot自动装配中的条件装配》。

后面就一路返回返回返回!!!!
在这里插入图片描述

相关文章
|
1月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
36 0
|
4天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
1月前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
59 17
|
1月前
|
消息中间件 Java 数据库
解密Spring Boot:深入理解条件装配与条件注解
Spring Boot中的条件装配与条件注解提供了强大的工具,使得应用程序可以根据不同的条件动态装配Bean,从而实现灵活的配置和管理。通过合理使用这些条件注解,开发者可以根据实际需求动态调整应用的行为,提升代码的可维护性和可扩展性。希望本文能够帮助你深入理解Spring Boot中的条件装配与条件注解,在实际开发中更好地应用这些功能。
38 2
|
1月前
|
Java 容器
springboot自动配置原理
启动类@SpringbootApplication注解下,有三个关键注解 (1)@springbootConfiguration:表示启动类是一个自动配置类 (2)@CompontScan:扫描启动类所在包外的组件到容器中 (3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效
|
2月前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
121 4
|
3月前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot
|
3月前
|
Java 开发者 数据格式
【Java笔记+踩坑】SpringBoot基础4——原理篇
bean的8种加载方式,自动配置原理、自定义starter开发、SpringBoot程序启动流程解析
【Java笔记+踩坑】SpringBoot基础4——原理篇
|
5月前
|
SQL Java 数据库连接
springboot~mybatis-pagehelper原理与使用
【7月更文挑战第15天】MyBatis-PageHelper是用于MyBatis的分页插件,基于MyBatis的拦截器机制实现。它通过在SQL执行前动态修改SQL语句添加LIMIT子句以支持分页。使用时需在`pom.xml`添加依赖并配置方言等参数。示例代码: PageHelper.startPage(2, 10); List&lt;User&gt; users = userMapper.getAllUsers(); PageInfo&lt;User&gt; pageInfo = new PageInfo&lt;&gt;(users); 这使得分页查询变得简单且能获取总记录数等信息。
133 2