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

Posted lay2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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容器注入部分将说明这部分内容。

 

以上是关于springboot启动流程ioc容器refresh过程(下篇)的主要内容,如果未能解决你的问题,请参考以下文章

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

SpringBoot:Spring容器的启动过程

头秃系列,二十三张图带你从源码分析Spring Boot 启动流程~

springboot启动流程ioc依赖注入

spring学习总结002 --- IOC容器启动源码(简易版)

Spring源码剖析-IOC启动流程