springboot笔记-2-.核心的上下文以及配置扫描解析(下)

前言

   上文说了springboot是如何发现并保存我们需要注册的bean,其最重要的就是依靠一个特殊的BeanFactoryProcessor-》ConfigurationClassPostProcessor,本文则主要来讲一下其详细的加载过程

 

正文

  这儿先回顾下上文ConfigurationClassPostProcessor解析时最主要的一段代码

//构建解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    //第一次解析时传入的类  这儿一般为主类
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        //解析出主启动类所扫描出的所有需要注册到spring容器的类(包括我们使用@Import 或者@ImportResource 等引入的配置类)
        parser.parse(candidates);
        parser.validate();
        //得到解析出的所有的类
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        //对这些解析到的类再进行处理  例如@Configuration类中可能有各种@Bean注解的方法需要处理
        this.reader.loadBeanDefinitions(configClasses);
        ../省略
    }
    while (!candidates.isEmpty());

 

1.从parse启动类开始寻找

   

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    //延迟引入的配置集合    
    this.deferredImportSelectors = new LinkedList<>();

    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            //主类我们直接进入第一个方法
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        ../省略
    }
    //这个时候开始加载上述延迟引入的配置集合
    processDeferredImportSelectors();
}

  这儿主要分两步,第一步就是拿着我们的启动类就开始解析,第二步就是对那些延缓加载的配置进行加载(例如我们@Import引入的配置)

  那我们还是先看下如何解析的启动类,因为需要延缓加载的配置也是在这个方法里找到的。

 

2.重载的parse方法

  最终这个parse会走到如下的方法

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    //判断是否应该跳过,如果为true  则跳过加载
    //这儿主要是根据@Conditional类的注解来判定的
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }
    //判断是否已经存在被加载的配置类中
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    //说明已经被加载过
    if (existingClass != null) {
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                existingClass.mergeImportedBy(configClass);
            }
            // Otherwise ignore new imported config class; existing non-imported class overrides it.
            return;
        }
        else {
            // Explicit bean definition found, probably replacing an import.
            // Let's remove the old one and go with the new one.
            this.configurationClasses.remove(configClass);
            this.knownSuperclasses.values().removeIf(configClass::equals);
        }
    }

    // 解析,configurationClass  如果其有父类  那么继续循环其父类
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    //每个解析的类最终都会放入这个configurationClasses
    this.configurationClasses.put(configClass, configClass);
}

  第一节中有讲到我们在制作可拔插组件的时候如何让系统判断只有依赖都被加载的时候插件才生效呢,其实上述方法中的第一行代码就已经给出了答案,我们在需要加载的配置类上使用@Conditional类的注解,具体的注解有如下,一般可以顾名思义,可根据需求使用

springboot笔记-2-.核心的上下文以及配置扫描解析(下)

 

 

 

 2.1 核心的doProcessConfigurationClass

  

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

    // 1.处理内部类  然后会递归到processConfigurationClass方法重新执行
    processMemberClasses(configClass, sourceClass);

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

    // 3.处理 @Componentscan 注解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            //解析出每个componentscan所指明的解析位置下的所有需要解析的配置类
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 如果componentscan下找到了configuration类,注意这儿的Configuration类不止是带有@Configuration注解的类  (会打上full标签)
            //还有@Import  @Component  @ImportResources @ComponentScan
            //或者其有@Bean注解的方法   (剩下的打上little标签)  
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                //检查是否为ConfigurationClass  并为其打上标签 
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // 4.处理 @Import 
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // 5.处理 @ImportResource 
    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);
        }
    }

    // 6.处理该加载类的所有@Bean注解的方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 7.如果其实现了接口 ,那么执行接口中的default方法
    processInterfaces(configClass, sourceClass);

    // 8.如果有父类则递归读取父类
    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();
        }
    }

    // 没有父类直接返回
    return null;
}

  这个方法看着虽然多,但是步骤清晰

  1.查看该配置类是否有内部类,如果有为configurationClass的子类则返回本文第二节步骤 重新执行

  2.处理propertySource注解

  3.然后处理我们最为常见的@ComponentScan注解,将解析出来的所有需要配置的类依旧返回第二节步骤重新执行

  4.然后处理Import注解,将所有解析出来的需要配置的类放入deferredImportSelectors这个集合中,后面再进行处理(springboot可拔插配置的关键)

  5..处理@ImportResource注解,即我们常用的引入xml配置文件的注解,并将其解析出来的文件地址放入该配置类的importedResources

  6.处理配置类中,有@Bean注解的方法,并将解析出来的方法信息放入该配置类的beanMethods

  7.执行配置类接口的default方法

  8.如果其父类为配置类,返回第二节步骤重新执行

  

  可以看出,springboot核心的解析步骤就是这样,如果有内部类或者通过配置解析出来的配置类或者父类,那么就递归第二节中的返回进行解析,确保所有类都要被加载到。然后对于@Bean和@ImportResource注解则将解析出来的东西放入该配置类的容器中。对于@Import注解解析出来的结果则是放入了特定的deferredImportSelectors中,而这个也就是我们第一节中最后的那个方法主要处理的东西,当然这个放后面说。

  这儿流程较为简单,所以我主要分析下有两个地方,第一个是如何解析的@ComponentScan,第二个是如何解析的@Import

 

2.2 @ComponentScan扫描

  熟悉springboot的同学应该知道这个注解的值关乎着我们哪些路径的bean能够注册到spring容器中,我们现在就来看下系统是如何对其进行解析的呢,我们从如下的代码开始入手

Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

  这儿需要注意@ComponentScan注解是组合在@SpringBootApplication中的

springboot笔记-2-.核心的上下文以及配置扫描解析(下)

 

 

 

 

 2.1中的方法通过这儿实现了对@ComponentScan的解析,所以我们来看这个方法。

//注意 第一次进来 declaringClass为我们主启动类
//
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    //根据@ComponentScan的useDefaultFilters值来构建一个ClassPathBeanDefinitionScanner
    //默认为true  如果为true 会注册一些默认的注解过滤器,例如@Component @ManagedBean 和 @Named
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    //类名生成器,默认为BeanNameGenerator接口
    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    //为scanner设置类名生成器,默认为AnnotationBeanNameGenerator  如果不填则为类名且首字母小写
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
            BeanUtils.instantiateClass(generatorClass));
    //scopedProxy默认为DEFAULT
    ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
    if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
        scanner.setScopedProxyMode(scopedProxyMode);
    }
    else {
        //scopeResolver默认为AnnotationScopeMetadataResolver
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }
    //设置资源匹配器  默认为**/*.class   即所有class
    scanner.setResourcePattern(componentScan.getString("resourcePattern"));
    //找到所有的设置的filter 默认为空
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addIncludeFilter(typeFilter);
        }
    }
    //找到所有排除的filter ,springboot默认有TypeExcludeFilter和AutoConfigurationExcludeFilter
    for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
        for (TypeFilter typeFilter : typeFiltersFor(filter)) {
            scanner.addExcludeFilter(typeFilter);
        }
    }
    //是否懒加载 默认为false
    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);
    }
    //如果有basePackageClasses则添加  默认为null
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
        basePackages.add(ClassUtils.getPackageName(clazz));
    }
    //如果上述的全部为空  则添加声明类的所在包  
    //这也就是为何@SpringbootApplication默认只解析其所在的包下面的所有的类,其上级包没法解决
    if (basePackages.isEmpty()) {
        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));
}

  这个方法很长,但内容很简单,无非就是挨个解析@ComponentScan的值并将其设置到ClassPathBeanDefinitionScanner中,然后调用scanner的解析方法,这儿有两点需要注意

   1.默认的includeFilter中加入了@Component注解,这个非常重要

   2.如果注解中没有找到任何可用的basePackage,那么会默认使用传入类的package作为basePackage,而这儿第一次进来的一般为主启动类,所以@springbootApplication之所以默认是解析其类所在的包下所有的类的缘由就在于此。

  然后我们接着看doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //做下判空
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //创建返回set
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    //迭代每个basePackage
    for (String basePackage : basePackages) {
        //找到basePackage下所有的配置类
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        //处理这次找到的所有配置类
        for (BeanDefinition candidate : candidates) {
        //获取其Scope信息,也就是分析其@Scope注解 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); //设置其scope candidate.setScope(scopeMetadata.getScopeName()); //这儿会获取名字,如果没有默认为其类名且首字母小写 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //如果配置类的BeanDefinition继承了AbstractBeanDefinition if (candidate instanceof AbstractBeanDefinition) { //则为这个candidate根据刚才初始化时构建的beanDefinitionDefaults来设置BeanDefinition一些属性, //例如是否懒加载,自动装配模型等 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //如果配置类 的BeanDefinition实现了AnnotatedBeanDefinition接口 //例如有@Lazy @Primary @DependsOn @Role @Description等注解 if (candidate instanceof AnnotatedBeanDefinition) { //那么就对其每个注解进行特定的处理 例如Lazy设置懒加载 例如Primary设置该类为主要类(例如一个接口有多个实现时,某个实现类加上这个就可以直接使用@Autowire注解不会报错) AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //检查一下该配置类是否在registray中已存在。为true则代表无冲突,否则会报异常 //例如两个类名一样且都使用@Component注解没有指定姓名就会报错这儿 if (checkCandidate(beanName, candidate)) { //如果没问题则构造一个BeanDefinitionHolder BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //这儿是根据scope信息来判定是否需要需要生成代理 默认不生成 //如果要生成可以为配置类加上@Scope(proxyMode = ScopedProxyMode.DEFAULT) 即可//只要不为ScopedProxyMode.NO(默认属性)即可 // definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //将该配置类注入到spring的registry中 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

  这个方法主要就是根据basePackage找到所有的配置类并创建BeanDefinition返回,然后为BeanDefinition设置一些必要的属性,最后根据特定的注解做一些特殊的判断即可。最后这些配置类代表的BeanDefinitionHolder 会被添加到spring的regitry中,在后面实例化的时候将会有很大的作用。

那我们还是接着看下basePackage下的Bean是如何被找到的,我们接着看findCandidateComponents方法。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        //找到解析的路径匹配规则  resourcePattern默认为**/*.class   即所有的class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        //根据路径去循环递归查找路径所包含的每一个class文件        
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        for (Resource resource : resources) {
            //循环处理每个可读的class文件
            if (resource.isReadable()) {
                try {
                    //读取文件的元数据
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    //如果文件在includeFilter中  且没有包含在excludeFilters中 则说明是我们需要加入到容器中的配置类
                    if (isCandidateComponent(metadataReader)) {
                        //新建ScannedGenericBeanDefinition  (注意其实现了AnnotatedBeanDefinition接口,且集成了AbstractBeanDefinition类)
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                    ..//代码略
    //返回
    return candidates;
}

  这个方法就很简单了,系统解析了路径,并找到路径下所有的class文件,那么spring怎么知道哪些是需要加载配置的呢,这时在上面着重让大家记住的includeFilter就起作用了,由于其加入了@Component注解的支持,我们看这个isCandidateComponent方法,

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

  看了这个方法相信大家就了然了,只有includeFilter为true才会返回true,这儿还有点需要注意的是isConditionMatch,这个处理的是@Conditional系列的注解,用来判定是否满足加载配置的条件。如果不满足则不必加载,会返回false。

  小结

  到此,是不是相信大家对于springboot是如何加载我们项目中所有需要依赖的bean以及配置已经有了一个大概理解了呢,我们可以概括一下。

  spring创建应用上下文后,其重要的BeanPostProcessor  即ConfigurationClassPostProcessor会根据我们的sourceClass 也就是主启动类来开始加载,然后会解析各种用到的注解以及内部类配置,内部@Bean方法等,而当其遇到@ComponentScan时则会根据我们的@ComponnetScan,也就是组合在@SpringbootApplication中的注解各种信息来加载出其匹配路径下的所有文件,然后对其进行逐个加载。

  

  好了,到这儿@ComponentScan的解析就完成了,那么为何我们要再讲解一下@Import解析呢。因为其实我们的@ComponentScan只能扫描到我们项目下面的路径,那对于我们引入的各种依赖包呢?例如引入mybatisPlus的springboot启动包后,我们并没有对其配置进行专门引入,而@ComponentScan也不可能扫描到他, springboot那么多自动化配置我们不可能说自己去挨个引入。这个任务其实就是交给了@Import注解分析来处理

 

2.3@Import注解分析

  让我们继续回到2.1节最后那8个步骤的第四步,代码位置及ConfigurationClassParser.doProcessConfigurationClass方法中处理@Import注解的那一步

// Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);

  注意这个getImports即是找到我们当前解析的类,也就是sourceClass的所有Import类的类,例如解析我们的主配置类,也就是使用了@SpringbootApplication注解时,就会引入的两个类(本系列第一篇开头有讲到)

springboot笔记-2-.核心的上下文以及配置扫描解析(下)springboot笔记-2-.核心的上下文以及配置扫描解析(下)

 

 

 

  也就是AutoConfigurationImportSelector(注意实现了DeferredImportSelector接口)  和AutoConfigurationPackages.Registrar.class(注意实现了ImportBeanDefinitionRegistrar接口),

 然后我们正式开始看processImports方法

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        //为空自然返回
        if (importCandidates.isEmpty()) {
            return;
        }
        //检查下循环引用的问题
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            //将当前的configClass推入栈importStack
            this.importStack.push(configClass);
            try {
                //迭代每个selector
                for (SourceClass candidate : importCandidates) {
                    //如果为实现了ImportSelector接口   之前所说的AutoConfigurationImportSelector正好满足
                    //这儿会执行该selector的selectImports方法
                    if (candidate.isAssignable(ImportSelector.class)) {
                        //实例化
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        //如果实现了前置处理 (即aware)则执行下其前置处理方法
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            //如果该selector实现了DeferredImportSelector接口且deferredImportSelectors容器不为null
                            //则将该selector包装好放入deferredImportSelectors(延缓加载容器)中   延缓加载
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            //如果延缓容器为null或者不为DeferredImportSelector类型  也就是非延缓加载
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            //那么就直接递归重新加载
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    //如过实现了ImportBeanDefinitionRegistrar  之前所说的AutoConfigurationPackages.Registrar正好满足
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        //实例化
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        //如果实现了前置处理 (即aware)则执行下其前置处理方法
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        //将其加入到该sourceClass的importBeanDefinitionRegistrars容器中        
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        //否则递归到最开始处理配置的方法进行解析
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            ../代码略
            finally {
                //出栈
                this.importStack.pop();
            }
        }
    }

  该类可以看出,对传入的importSouces分为了三类处理,

  1.第一类为实现了ImportSelector接口的,执行完其前置方法(如果实现了前置接口)后,该类又分为两种情况处理,第一种则是如果为延缓加载selector且延缓加载容器不为空,则将其加入延缓加载队列。否则就执行执行其selectImport方法,将配置类查出并递归执行。

  2.第二类为实现了ImportBeanDefinitionRegistrar接口的,执行完其前置方法(如果实现了前置接口)后,将其加入sourceClass的importBeanDefinitionRegistrars容器中

  3.第三类为上面两种以外的情况,直接递归到本文第2节所讲的步骤。

  上面第一类中第二种情况的selectImport方法是查找配置的关键,但由于第一种加载到延缓容器的情况在下一节就会使用到这个方法就会讲到,所以我们下一节一起讲。

 

3. 处理延缓加载

  我们把目光回到本文第一节中所讲的方法processConfigurationClass中

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<>();
//1.根据configClass解析其配置
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
//2.处理延缓容器
        processDeferredImportSelectors();
    }

  第一步我们也就处理完成了,到目前已经成功的把我们主启动类所发现的所有需要加载的配置类已经成功加载。第二步就是处理延缓加载容器

我们看延缓加载容器的处理代码

private void processDeferredImportSelectors() {
    //为空直接返回
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    if (deferredImports == null) {
        return;
    }
    //排个序
    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
    Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
    //迭代每个import
    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
        Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();、
        //根据import的group分组
        DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
                (group == null ? deferredImport : group),
                (key) -> new DeferredImportSelectorGrouping(createGroup(group)));
        grouping.add(deferredImport);
        //设置引入该import的configurationClass  例如AutoConfigurationImportSelector则为主启动类
        configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getConfigurationClass());
    }
    //分组进行处理
    for (DeferredImportSelectorGrouping grouping : groupings.values()) {
        grouping.getImports().forEach((entry) -> {
            //获取到引入import的configurationClass
            ConfigurationClass configurationClass = configurationClasses.get(
                    entry.getMetadata());
            try {
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            ../略
        });
    }
}

  这个方法就是一个对所有的import进行排序分组等。最后循环这些selector 将值重新调入processImports方法(防止我们加载的配置类也是selector)。这儿的代码可能很多人容易不理解,尤其是最后一循环,其实这儿我们注意这个groupings 所对应的map,这个map的values是DeferredImportSelectorGrouping,而这个DeferredImportSelectorGrouping里面则用了deferredImports集合装入了我们属于这个分组的所有的selector,所以我们在最后的分组循环体重进行拆解。

  首先迭代时的值为DeferredImportSelectorGrouping,也就是上述的装入了属于该组的selector的容器。然后程序执行了其getImport方法,那我们则看下这个方法。

public Iterable<Group.Entry> getImports() {
            //迭代该容器中的所有selector
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                //处理每个selector
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            //返回group中的selectImports的返回值
            return this.group.selectImports();
        }

  该类其实看着意图很明显,迭代处理了每个selector,然后返回了group(注意这儿的group为默认为DefaultDeferredImportSelectorGroup)中的selectImports方法的返回值,可以预见,肯定是迭代中处理中往这个group的容器中加入了数据,然后返回。我们看下具体的处理是否和料想的一样。

我们接着看

private static class DefaultDeferredImportSelectorGroup implements Group {
        //selector查找到的返回值的容器
        private final List<Entry> imports = new ArrayList<>();
        //迭代执行selector的查找方法
        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            //查找
            for (String importClassName : selector.selectImports(metadata)) {
                this.imports.add(new Entry(metadata, importClassName));
            }
        }
        //返回查找值
        @Override
        public Iterable<Entry> selectImports() {
            return this.imports;
        }
    }

  看来果真和我们料想一致啊。。这儿会执行每个selector的selecImports方法,我们就以2.3节中说道的由@SpringbootConfiguration的组合注解@AutoConfigurationImportSelector为例来说明这个方法吧,我们直接进入到这个方法。

 

3.1 自动化配置的关键

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    //检查下环境变量中spring.boot.enableautoconfiguration是否为true,如果不为true则不会执行
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    //加载元数据
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    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 StringUtils.toStringArray(configurations);
}

 

  这儿主要两个步骤比较重要,1.检查环境是否满足自动化配置,这儿主要查看的环境变量spring.boot.enableautoconfiguration。如果不专门设置的话默认为true,也就是说我们如果不想让spring自动化配置生效,可以在jvm启动参数上添加
-Dspring.boot.enableautoconfiguration=false 即可
  第二个关键的步骤就是getCandidateConfigurations,这个方法会获取到所有配置,我们继续看。
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;
    }

  这段代码相信如果有看过本系列的第一篇文章相信会很眼熟,在讲第一篇的时候谈到spring实现自动发现配置的关键便是其会解析所有依赖下的META-INF/spring.factories文件,然后获取到所有的配置。到这儿@Import这个注解的功能算是终于明白了,而Springboot如何实现自动发现和配置也搞清楚了,正是因为@SpringbootApplication这个注解中引入了AutoConfigurationImportSelector这个selector,所以才能实现其自动化发现并配置。

spring的spring-boot-autoconfigure-xxx.jar依赖中则提供了很多的自动化配置,当然我们也可以自己编写,关于这些本系列第一篇就讲解,有兴趣的可以看看。

 到此我们的依赖就成功返回给了spring解析器。我们再次回到那个循环处理的方法中。

  
for (DeferredImportSelectorGrouping grouping : groupings.values()) {
            grouping.getImports().forEach((entry) -> {
                ConfigurationClass configurationClass = configurationClasses.get(
                        entry.getMetadata());
                try {
                    processImports(configurationClass, asSourceClass(configurationClass),
                            asSourceClasses(entry.getImportClassName()), false);
                }
            
            });
        }

  这个getImports得到的便是该组所有的selector所查找到的所有配置类,然后foreach每个配置类去重复执行我们已经分析过的processImports方法即可。

  到此,对@Import分析就算完成了

   小结

  @Import注解的主要作用便是让我们有了自定义扩展配置的功能,而其中的AutoConfigurationImportSelector自动查找加载所有依赖下的META-INF/spring.factories文件更是Springboot实现自动发现并配置的关键。

 

4.loadBeanDefinitions  扫尾工作

  回到本文开头,我们算是终于执行完了ConfigurationClassParser.parse方法,但是紧跟其后的还有ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法需要我们仔细分析

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);

  记住这儿我们解析到的所有的需要加载的类都已经放在了这个configClasses中

  我们看下这个load方法

    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
        for (ConfigurationClass configClass : configurationModel) {
            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }
    }

  代码还是少的,主要是new了一个TrackedConditionEvaluator,然后迭代处理。这个TrackedConditionEvaluator 主要用来处理@Condinal系列的注解。即用来再次评估判断是否应该跳过。例如有个配置类上有@ConditionalOnMissingClass,可能在一开始加载到他的时候没有这个missingClass,但是后面全部加载后,这个类又被加载进去了。但是我们的配置类已经注册到系统中了,所以需要再次校验。

  我们接着看方法

private void loadBeanDefinitionsForConfigurationClass(
            ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
        //再次评估是否应该不加载
        if (trackedConditionEvaluator.shouldSkip(configClass)) {
            String beanName = configClass.getBeanName();
            //如果不应该被加载但是又被加载进去了  那么直接移除
            if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
                this.registry.removeBeanDefinition(beanName);
            }
            this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
            return;
        }
        //如果是被其他配置类以import方式加载的例如@Import注解,或者内部类的方式
        if (configClass.isImported()) {
            //那么要将其的BeanDefinition注册到spring中
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }
        //处理配置类中的@Bean的注解  并将其BeanDefinition注册到spring中
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }
        //处理@ImportResource加载的配置类 常用来加载xml文件,并将其BeanDefinition注册到spring中
        loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
        //处理@ImportBeanDefinitionRegistrars加载的类,并将其BeanDefinition注册到spring中
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

    1.首先做了下验证,如果不需要加载却已经加载了就移除。

    2.如果本身是被Import进来的,那么需要将其加入到spring的容器中。

    3.处理配置类中的@Bean注解,记住我们在2.1节中最后的那个8个步骤中第6个步骤中说道配置类中所有@Bean方法都将会加载到这个配置类的BeanMethods容器中

    4.处理@ImportResource加载的类,即2.1节中最后的那8个步骤中的第5个步骤,说道会将@ImportResource的资源加载到这个配置类的importedResources容器中

    5.最后处理ImportBeanDefinitionRegistrars,即2.3分析@Import注解中分析中processImports方法里,如果import为ImportBeanDefinitionRegistrar则会加载到这个配置类的importBeanDefinitionRegistrars容器中。

  

  这儿逻辑基本相似,主要说下加载@Bean,因为我们一般用的比较多,加载的原理基本都类似。我们看下loadBeanDefinitionsForBeanMethod方法

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
        //@Bean方法所在的配置类
        ConfigurationClass configClass = beanMethod.getConfigurationClass();
        //方法元数据
        MethodMetadata metadata = beanMethod.getMetadata();
        //方法名
        String methodName = metadata.getMethodName();

        //判断该方法是否应该不加载  即@Condional类的注解
        //如果跳过的会在配置类中记载
        if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
            configClass.skippedBeanMethods.add(methodName);
            return;
        }
        if (configClass.skippedBeanMethods.contains(methodName)) {
            return;
        }
        //得到这个@Bean注解的元数据
        AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
        Assert.state(bean != null, "No @Bean annotation attributes");

        // 得到name属性值,
        List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
        //有多个的话就用第一个 没设置就为方法名
        String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

        // Register aliases even when overridden
        for (String alias : names) {
            //为方法设置别名
            this.registry.registerAlias(beanName, alias);
        }

        // 如果已经被加载就报异常
        if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
            if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
                throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
                        beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
                        "' *es with bean name for containing configuration class; please make those names unique!");
            }
            return;
        }
        //新建一个BeanDefinition 并设置必要属性
        ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
        beanDef.setResource(configClass.getResource());
        beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
        
        if (metadata.isStatic()) {
            // static @Bean method
            beanDef.setBeanClassName(configClass.getMetadata().getClassName());
            beanDef.setFactoryMethodName(methodName);
        }
        else {
            // instance @Bean method
            beanDef.setFactoryBeanName(configClass.getBeanName());
            beanDef.setUniqueFactoryMethodName(methodName);
        }
        beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
        beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

        AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

        Autowire autowire = bean.getEnum("autowire");
        if (autowire.isAutowire()) {
            beanDef.setAutowireMode(autowire.value());
        }
        //初始化方法
        String initMethodName = bean.getString("initMethod");
        if (StringUtils.hasText(initMethodName)) {
            beanDef.setInitMethodName(initMethodName);
        }
        //设置销毁时方法
        String destroyMethodName = bean.getString("destroyMethod");
        beanDef.setDestroyMethodName(destroyMethodName);

        // 设置scope属性
        ScopedProxyMode proxyMode = ScopedProxyMode.NO;
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
        if (attributes != null) {
            beanDef.setScope(attributes.getString("value"));
            proxyMode = attributes.getEnum("proxyMode");
            if (proxyMode == ScopedProxyMode.DEFAULT) {
                proxyMode = ScopedProxyMode.NO;
            }
        }

        // 如果scope不为ScopedProxyMode.NO 则返回代理
        BeanDefinition beanDefToRegister = beanDef;
        if (proxyMode != ScopedProxyMode.NO) {
            BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
                    new BeanDefinitionHolder(beanDef, beanName), this.registry,
                    proxyMode == ScopedProxyMode.TARGET_CLASS);
            beanDefToRegister = new ConfigurationClassBeanDefinition(
                    (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
        }

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
                    configClass.getMetadata().getClassName(), beanName));
        }
        //注册到spring
        this.registry.registerBeanDefinition(beanName, beanDefToRegister);
    }

  其实也就是判断了下是否应该加载,然后为其构建BeanDefinition注册到spring中即可。

  好了,到此springboot如何加载系统中的配置类就讲完了。

 

总结

  我们主要分析了ConfigurationClassParser.parse方法 以及ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法。

  ConfigurationClassParser.parse则主要是根据我们传入的主启动类进行解析,然后分析其@PropertySource,@ComponentScans,@Import,@ImportResource,@Bean注解。而ComponentScans注解则会默认加载启动类所在包下所有的需要加载的class文件,然后递归进行解析。对于@Import加载的类则分为了两种,如果为DeferredImportSelector则加载到延时容器,如果为ImportBeanDefinitionRegistrar类型则防止到配置类指定容器中,@ImportResource和@Bean注解解析后的内容也会放到配置类专门的容器中。最后在解析完成后该方法会开始处理延时容器中的selector,而其中的AutoConfigurationImportSelector更是spring自动配置的核心。

  ConfigurationClassBeanDefinitionReader.loadBeanDefinitions则是对配置条件进行再次核实防止出错,然后开始加载之前放置到配置类容器中的各个容器资源。

  到此spring就已经完成了对所有需要加载的类的BeanDefinition的定义,但需要注意此时类并没有被实例化,真正的实例化会在下一篇中说明。

 

上一篇:Mapstruct入门及使用


下一篇:简化mapstruct代码: mapstruct-spring-plus