springboot启动流程(八)ioc容器refresh过程(下篇)

所有文章

https://www.cnblogs.com/lay2017/p/11478237.html

正文

上一篇文章,我们知道了解析过程将从解析main方法所在的主类开始。在文章的最后我们稍微看了一下ConfigurationClassParser这个解析器的parse方法

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

本文将从这个parse方法继续下去,看看解析main方法所在的主类这个过程主要发生了什么。

跟进processConfigurationClass方法

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // 

    // 由main方法所在的主类开始,向超类逐层向上递归解析
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        // 这里包含了解析单个配置类的核心逻辑
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    } while (sourceClass != null);

    //
}

我们注意到,doProcessConfigurationClass方法将会完成解析的主要工作,但是又会返回一个新的sourceClass用于解析。而这个新的sourceClass会是当前上一个sourceClass的父类。所在解析过程是一个递归过程,由主类开始,向超类逐层向上递归解析处理。

继续跟进doProcessConfigurationClass方法,我们看看这个核心的解析逻辑。代码量对较多,我们只关注两个点

1)@ComponentScan注解解析,扫描并注册BeanDefinition

2)获取超类向上递归

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {
    //

    // 处理@ComponentScan注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        // 遍历@ComponentScan的属性值
        for (AnnotationAttributes componentScan : componentScans) {
            // 解析扫描
            Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            //
        }
    }

    // 

    // 判断是否有超类
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
                !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // 返回待解析的超类
            return sourceClass.getSuperClass();
        }
    }

    // 没有超类,则解析完毕
    return null;
}

首先我们main方法所在的主类是被@SpringbootApplication注解所标注的,而@SpringbootApplication组合了@ComponentScan。所谓解析主类的时候将会处理@ComponentScan注解。解析@ComponentScan的主要工作的实现由ComponentScanAnnotationParser这个解析器来完成。通常这个解析器完成之后,被扫描到的BeanDefinition将会被注册到BeanFactory当中。

doProcessConfigurationClass方法的最后一部分是从当前被解析的类元数据中获取超类,如果超类存在且需要被解析那么就当做返回值返回回去,从而被外层的方法给递归处理。

@ComponentScan注解解析

下面,我们跟进ComponentScanAnnotationParser这个解析器的parse方法,看看@ComponentScan的处理过程

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

    //

    Set<String> basePackages = new LinkedHashSet<>();
    // 从basePackages配置获取扫描路径
    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);
    }
    // 从basePackageClasses获取扫描路径
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    if (basePackages.isEmpty()) {
        // 默认添加当前被解析类的路径作为根路径
        basePackages.add(ClassUtils.getPackageName(declaringClass));
    }

    // 

    // 扫描目标路径
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}    

这里获取了一个扫描器,然后找到了待扫描的路径,最后利用扫描器去扫描路径。

跟进doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {

    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        // 扫描获取BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            //

            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 注册BeanDefinition到BeanFactory
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

我们看到,findCandidateComponents方法将会根据扫描路径获取BeanDefinition,而扫描出来的BeanDefinition将会进入注册方法registerBeanDefinition。

我们先跟进findCandidateComponents方法看看如何扫描获取

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    } else {
        // 进入
        return scanCandidateComponents(basePackage);
    }
}

再跟进scanCandidateComponents方法

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        // 拼接出搜索路径,例如:classpath*:cn/lay/springbootlearn/**/*.class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + ‘/‘ + this.resourcePattern;
        // 获取搜索路径下待处理资源
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        //
        for (Resource resource : resources) {
            //
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        // 转化成BeanDefinition
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        //
                    } else {
                        //
                    }
                } catch (Throwable ex) {
                    //
                }
            } else {
                //
            }
        }
    } catch (IOException ex) {
        //
    }
    return candidates;
}

basePackage将会被拼接成搜索路径,如:classpath*:cn/lay/springbootlearn/**/*.class。而getResources方法将会从搜索路径中获取相应的资源对象,这些资源对象并最终被读取并转化为BeanDefinition。

到这里,@ComponentScan扫描的Bean就已经成为了BeanDefinition,但是还有一步就是将BeanDefinition注册到BeanFactory当中。

我们回到doScan方法,并跟进registerBeanDefinition方法,看看注册过程

protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}

继续跟进registerBeanDefinition

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder,
        BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {

    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // 省略
}

我们看到这里直接注册到了BeanDefinitionRegistry中去了,其实就是注册到BeanFactory当中。BeanFactory的默认实现类DefaultListableBeanFactory实现了BeanDefinitionRegistry,所以DefaultListableBeanFactory即是BeanDefinition的注册位置。

跟进DefaultListableBeanFactory的registerBeanDefinition方法

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

public void registerBeanDefinition(
        String beanName,
        BeanDefinition beanDefinition) throws BeanDefinitionStoreException {

    //
    if (existingDefinition != null) {
        //
    } else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                //
            }
        } else {
            //
        }
        //
    }
    //
}

最终,也就是将BeanDefinition添加到一个key-value的集合当中,这样就完成了注册工作。

总结

到这里,ioc容器refresh过程部分就结束了。我们略过不少东西,将解析主类、解析@ComponentScan扫描Bean定义、注册到BeanFactory这个主要的流程过了一遍。当然,在这里可能还存在一个比较困惑的点。前面的文章中,我们提过几次:配置 -> BeanDefinition -> Bean这样一个过程。ioc的refresh过程却只有从配置 -> BeanDefinition这样一个过程,那么BeanDefinition -> Bean这个过程又在哪里呢?后面ioc容器注入部分将说明这部分内容。

原文地址:https://www.cnblogs.com/lay2017/p/11504047.html

时间: 2024-10-09 04:03:33

springboot启动流程(八)ioc容器refresh过程(下篇)的相关文章

springboot启动流程(六)ioc容器refresh过程(上篇)

所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 在前面的几篇文章中,我们看到Environment创建.application配置文件的加载.ApplicationContext实例对象的创建.以及主类加载成为BeanDefinition.做了这么多的准备,终于到了核心的部分,也就是ioc容器的刷新. 这里,我们不免要再次回顾一下SpringAplication的run方法 public ConfigurableApplicatio

SpringBoot启动流程分析(四):IoC容器的初始化过程

SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一):SpringApplication类初始化过程 SpringBoot启动流程分析(二):SpringApplication的run方法 SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法 SpringBoot启动流程分析(四

SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一):SpringApplication类初始化过程 SpringBoot启动流程分析(二):SpringApplication的run方法 SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法 SpringBoot启动流程分析(四

SpringBoot 启动流程

SpringBoot 启动流程 加载 resources/META-INF/spring.factories 中配置的 ApplicationContextInitializer 和 ApplicationListener. /** * 加载在框架内部使用的各种通用工厂 bean. * spring.factories 文件必须满足 Properties 文件格式,属性的 key 是接口或抽象类的全限定类名, * value 是一组由逗号分隔的实现类全类名. */ public final cl

springboot启动流程(一)构造SpringApplication实例对象

所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 启动入口 本文是springboot启动流程的第一篇,涉及的内容是SpringApplication这个对象的实例化过程.为什么从SpringApplication这个对象说起呢?我们先看一段很熟悉的代码片段 @SpringBootApplication public class SpringBootLearnApplication { public static void main(Str

springboot启动流程(九)ioc依赖注入

所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 在前面的几篇文章中,我们多次提到这么一个转化过程: Bean配置 --> BeanDefinition --> Bean对象 Bean的配置可以是xml配置,也可以是java配置.BeanDefinition配置在内存中数据对象,也是Bean的元数据.在springboot启动过程当中,refresh上下文这个步骤将会解析xml配置以及java配置,从而把Bean的配置解析成为Bea

spring的启动过程就是创建ioc容器的过程

1. spring简介 spring的最基本的功能就是创建对象及管理这些对象之间的依赖关系,实现低耦合.高内聚.还提供像通用日志记录.性能统计.安全控制.异常处理等面向切面的能力,还能帮我们管理最头疼的数据库事务,本身提供了一套简单的JDBC访问实现,提供与 第三方数据访问框架集成(如hibernate.JPA),与各种Java EE技术整合(如JavaMail.任务调度等等),提供一套自己的web层框架Spring MVC.而且还能非常简单的与第三方web框架集成.从这里我们可以认为Sprin

springboot启动流程简析

Spring Boot可以轻松创建独立的,生产级的基于Spring的应用程序,而这只需要很少的一些Spring配置.本文将从SpringBoot的启动流程角度简要的分析SpringBoot启动过程中主要做了哪些事情. 说明: springboot 2.0.6.RELEASE SpringBoot启动简要流程图 附原始大图链接 启动流程概述 启动流程从角度来看,主要分两个步骤.第一个步骤是构造一个SpringApplication应用,第二个步骤是调用它的run方法,启动应用. 1 构造Sprin

Spring之IOC容器初始化过程

Ioc容器的初始化是由refresh()方法来启动的,这个方法标志着Ioc容器的正式启动. 具体来说这个启动过程包括三个基本过程: 1.BeanDifinition的Resource定位 2.BeanDifinition的载入与解析 3.BeanDifinition在Ioc容器中的注册 需要注意的是,Spring把这三个过程分开,并使用不同的模块来完成,如使用相应的ResourceLoader.BeanDifinitionReader等模块,通过这样的实际方式,可以让用户更加灵活的对这三个过程进