zoukankan      html  css  js  c++  java
  • 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容器注入部分将说明这部分内容。

  • 相关阅读:
    IIS浏览显示目录
    图解NuGet的安装和使用
    未能找到类型或命名空间名称“DbContext”
    IIS报错:未将对象引用设置到对象的实例
    最新11位手机号正则表达式
    Sql Server连表查询字段为null
    sql server 表连接
    2019用卡提额攻略
    win10,7 80端口被占用的检测和解决方法
    SAP之RFC_READ_TABLE
  • 原文地址:https://www.cnblogs.com/lay2017/p/11504047.html
Copyright © 2011-2022 走看看