专栏首页源码之路逐行阅读Spring5.X源码(七)扫描和注册神器 ConfigurationClassPostProcessor ,学此类者,胜过学九阳神功!胆小勿入!

逐行阅读Spring5.X源码(七)扫描和注册神器 ConfigurationClassPostProcessor ,学此类者,胜过学九阳神功!胆小勿入!

在本专题中讲解了BeanFactoryPostProcessor及子子类BeanDefinitionRegistryPostProcessor,我们讲过spring初始化过程中会默认注册几个后置处理器,在refresh()正式启动spring容器过程中会调用默认注册的后置处理器,其中有一个后置处理器叫ConfigurationClassPostProcessor,在sping中扮演着最最最重要的角色,本篇文章我们详细讲解该后置处理器。

问题

  1. @Configuration注解的作用是什么,Spring是如何解析加了@Configuration注解的类?
  2. Spring在什么时候对@ComponentScan、@ComponentScans注解进行了解析?
  3. Spring什么时候解析了@Import注解,如何解析的?
  4. Spring什么时候解析了@Bean注解?

作用

ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。 完成扫描啊!你说重要不重要!!!

注册时机

虽然之前讲过ConfigurationClassPostProcessor在哪里注册的,但为保证本篇博文的完整性,笔者简单谈下这个问题,我们启动一个spring:

@ComponentScan("com")
public class Config {


}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
    }
}

new AnnotationConfigApplicationContext();会调用构造函数:

    public AnnotationConfigApplicationContext() {
AnnotatedBeanDefinitionReader
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

跟进new AnnotatedBeanDefinitionReader(this);的构造函数

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
        /**
         * registerAnnotationConfigProcessors
         * 根据名字顾名思义就是->注册注解配置的的处理器
         * 也就是这个方法里面会注册一些用于处理注解的处理器
         */
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);就是注册默认的后置处理器,这个方法跟到底,一直到registerAnnotationConfigProcessors这个方法,这个方法里面就是真正的注册各种内置的后置处理器,我们找到这段代码:

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

这段代码就是注册ConfigurationAnnotationProcessor后置处理器哒。最后一行代码中的registerPostProcessor方法完成向bean工厂注册功能:

private static BeanDefinitionHolder registerPostProcessor(
            BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
        /**
         * registry就是AnnotationApplicationContext
         * 这里是调用父类GenericApplicationContext中的registerBeanDefinition方法
         * 调用beanFactory将spring默认的BeanDefinition注册进去
         */
        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(beanName, definition);
        return new BeanDefinitionHolder(definition, beanName);
    }

调用时机

context.refresh();完成spring中的启动过程,根据去找到这行代码invokeBeanFactoryPostProcessors(beanFactory),调用所有注册的beanFactory后置处理器,继续跟进去:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        //getBeanFactoryPostProcessors获取后置处理器
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

        // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
        // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    }

跟进第一行代码invokeBeanFactoryPostProcessors,这个方法实现了spring后置处理器的调用,包括spring内置的和程序员提供的,这个调用顺序在(五)中详细讨论了,这里我们只讨论ConfigurationClassPostProcessor调用时机。 往下找到下面的代码:

    //查询BeanDefinitionRegistryPostProcessor的实现类
            String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    //添加到数组中,后续进行遍历回调
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    //存储BeanDefinitionRegistryPostProcessor实现类的名字
                    processedBeans.add(ppName);
                }
            }
          //对BeanDefinitionRegistryPostProcessor设置调用顺序
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            //执行BeanDefinitionRegistryPostProcessor
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();

这段代码就是完成ConfigurationClassPostProcessor后置处理器的调用!首先,查找实现BeanDefinitionRegistryPostProcessor接口的bean,这里其实就有一个,就是ConfigurationClassPostProcessor,然后判断是否还实现了PriorityOrdered接口,ConfigurationClassPostProcessor确实实现了这个接口,然后放到集合中,在下面的invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);方法里完成ConfigurationClassPostProcessor的调用,我们看下这个方法:

    private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        }
    }

没错,调用ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法,后续代码继续调用postProcessBeanFactory方法。Spring在启动过程中,最先调用的是ConfigurationClassPostProcessor后置处理器,改处理器通过以上两个方法完成扫描和注册。

类继承关系

ConfigurationClassPostProcessor实现了 BeanDefinitionRegistryPostProcessor接口,而 BeanDefinitionRegistryPostProcessor接口继承了 BeanFactoryPostProcessor接口,所以ConfigurationClassPostProcessor中需要重写 postProcessBeanDefinitionRegistry()方法和postProcessBeanFactory() 方法。而ConfigurationClassPostProcessor类的作用就是通过这两个方法去实现的。

ConfigurationClassPostProcessor类继承关系

核心方法

上文讲过,spring启动过程中先调用ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry(registry)方法,再调用postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法,我们来详细讨论这两个方法完成的功能。

postProcessBeanDefinitionRegistry

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        // 一个工厂的后置处理器只会执行一次
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
        //跟进
        processConfigBeanDefinitions(registry);
    }

核心逻辑在processConfigBeanDefinition()方法中,这个方法完成了扫描注册,特别难,笔者看了好几遍,非常的核心,读者务必多读几遍,大家先大体浏览下。看完后我带大家详细分析下,很难,做好心理准备吧。

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        /**
         * registry就是DefaultListableBeanFactory,DefaultListableBeanFactory是bean工厂拉,之前的博文讲的太多拉
         * 获取注册的所有beanName
         */
        String[] candidateNames = registry.getBeanDefinitionNames();
        /**
         * 循环处理所有BeanDefinition,找到配置类
         *      比如下面配置类,找到后spring就知道从哪里扫描啦
         *          @ComponentScan("com")
         *          public class Config { }
         */
        for (String beanName : candidateNames) {
            // 根据beanName获得BeanDefinition
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);

            /*
             *  BeanDefinition中有这么一个变量 Map<String, Object> attributes = new LinkedHashMap<>();
             *  用来存储属性值,如果该attributes中存在configurationClass这个键,且对应的值是full或者lite
             *  意味着已经处理过了,直接跳过
             *  后面处理BeanDefinition时,会给bd设置一个属性(key为configurationClass,value为full或者lite)
             */
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            // checkConfigurationClassCandidate()会判断一个是否是一个配置类,并为BeanDefinition设置属性为lite或者full。
            // 在这儿为BeanDefinition设置lite和full属性值是为了后面在使用
            // 如果加了@Configuration,那么对应的BeanDefinition为full;
            // 如果加了@Bean,@Component,@ComponentScan,@Import,@ImportResource这些注解,则为lite。
            //lite和full均表示这个BeanDefinition对应的类是一个配置类
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                //添加到集合中,后面统一处理,BeanDefinitionHolder就是对BeanDefinition和名字简单封装而已
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        //如果没有配置类,就返回拉,啥也不执行了
        if (configCandidates.isEmpty()) {
            return;
        }

        //将配置类进行排序
        //我们可以提供多个配置类,谈后在配置类上添加@Order注解决定配置类的先后调用顺序
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });
        /**
         * 判断是否有自定义的beanName生成器,没有话就用默认的
         * 因为后面会扫描出所有加入到spring容器中calss类,然后把这些class
         * 解析成BeanDefinition类,此时需要利用BeanNameGenerator为这些BeanDefinition生成beanName
         * 默认是类名首字母小写
         */
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
        /**
         * 实例化ConfigurationClassParser 为了解析各个配置类(带@Configuration注解的类)
         * 初始化ConfigurationClassParser的一些属性
         */
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        /**
         * 实例化两个set
         * candidates用于将之前加入的configCandidates去重
         * alreadyParsed用于判断是否处理过了
         */
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            // 开始扫描/注册包下的类
            // 在此处会解析配置类上的注解(ComponentScan扫描出的类,@Import注册的类,以及@Bean方法定义的类)
            // 注意:这一步只会将加了@Configuration注解以及通过@ComponentScan注解扫描的类才会加入到BeanDefinitionMap中
            // 通过其他注解(例如@Import、@Bean)的方式,在parse()方法这一步并不会将其解析为BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass类
            // 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中实现的
            parser.parse(candidates);
            parser.validate();
            /**
             * 获取在扫描时put进去的configurationClasses
             */
            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());
            }
            //在这里统一处理,没有注册的进行注册
            // 将上一步parser解析出的ConfigurationClass类加载成BeanDefinition
            // 实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean注解的方法
            // 因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            candidates.clear();
            // 这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition
            // 实际上就是看配置类(例如AppConfig类会向BeanDefinitionMap中添加bean)
            // 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length
            // 这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析
            // 这里的AppConfig类向容器中添加的bean,实际上在parser.parse()这一步已经全部被解析了
            // 所以为什么还需要做这个判断,目前没看懂,似乎没有任何意义。
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                // 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }

        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op
            // for a shared cache since it'll be cleared by the ApplicationContext.
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

方法伊始,我们拿到容器中所有的BeanDefinition的名字,包括系统内置的后置处理器及我们提供的Config配置类

获取系统所有BeanDefinition的名字

紧接着,循环遍历这些BeanDefinition,过滤掉spring内置的后置处理器,留下我们提供的配置类,解析配置类,完成扫描和注册,这个配置类只能解析使用一次。那么spring如何过滤出我们提供的配置类以及如何保证只解析使用一次呢?

保证使用一次很简单,只要第一次解析使用完成后添加一个标志即可。BeanDefinition维护一个变量, Map<String, Object> attributes = new LinkedHashMap<>();用来存储属性值,如果该attributes中存在configurationClass这个键,且对应的值是full或者lite意味着已经处理过了,直接跳过。下面的源码就是判断是否有该键值的:

    private static final String CONFIGURATION_CLASS_FULL = "full";
    private static final String CONFIGURATION_CLASS_LITE = "lite";

    public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
        return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
    }
public static boolean isLiteConfigurationClass(BeanDefinition beanDef) {
        return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
    }

常量值full和lite表示什么意思下文会讲到。 最关键的是如何筛选出我们的配置类?答案就是根据类型。文章开头,我们通过代码往spring中添加我们的配置类context.register(Config.class);,我们跟进这行代码看spring是如何把Config转成BeanDefinition的,一直跟到doRegisterBean方法:

         //beanClass就是Config.class
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
                       ... 中间省略...
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

由此可见Config被转成了AnnotatedGenericBeanDefinition类型,还记得之前讲过的BeanDefinition继承图吗,笔者不厌其烦的再次祭出BeanDefinition的继承图:

BeanDefinition继承图

AnnotatedGenericBeanDefinition在最底层右面第二个的位置。上文中的“注册时机”我们能看到,spring内置的后置处理器都是RootBeanDefinition类型的。由此可见AnnotatedGenericBeanDefinition和RootBeanDefinition没有半毛钱关系,我们自然通过instanceof类型匹配关键字过滤出我们的配置类了!分析过滤的源码:

else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }

跟进checkConfigurationClassCandidate这行代码,这行代码的意思是找到我们的配置类并获取到他的注解信息。如果注解包含了@Configuration,则设置属性键值对configurationClass=full,否则判断注解是否包含@Component、@ComponentScan、@Import、@ImportResource、有@Bean注解的方法其中之一,如果是则设置键值对configurationClass=lite,如果都不是则返回false,说明Config不是配置类。看源码:

public static boolean checkConfigurationClassCandidate(
            BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        //获取class名字,这里是com.config.Config
        String className = beanDef.getBeanClassName();

        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }
        AnnotationMetadata metadata;
        //判断BeanDefinition是否是AnnotatedBeanDefinition类型,AnnotatedGenericBeanDefinition实现了AnnotatedBeanDefinition
        //我们的配置类Config对应的BeanDefinition确实就是AnnotatedBeanDefinition类型
        if (beanDef instanceof AnnotatedBeanDefinition &&
                className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
            //获取配置类的注解信息
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        }
        //如果BeanDefinition是AbstractBeanDefinition并且包装了业务类,RootBeanDefinition继承了如果BeanDefinition是AbstractBeanDefinition
        //spring内置的后置处理器确实是AbstractBeanDefinition类型的
        else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            // Check already loaded Class if present.. since we possibly can't even load the class file for this Class.
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            //拿到注解信息
            metadata = new StandardAnnotationMetadata(beanClass, true);
        }
        else {
            try {//既不是AnnotatedBeanDefinition类型也不是AbstractBeanDefinition类型,或者是AbstractBeanDefinition类型但没有包装业务类
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
                metadata = metadataReader.getAnnotationMetadata();
            }
            catch (IOException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not find class file for introspecting configuration annotations: " +
                            className, ex);
                }
                return false;
            }
        }
        //判断注解中是否包含了@Configuration注解
        if (isFullConfigurationCandidate(metadata)) {
            //设置属性,相当于beanDef.setAttribute("configurationClass","full")
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        //判断注解中是否包含了@Component、ComponentScan、Import、ImportResource其中之一,或者是否有@Bean注解的方法。
        else if (isLiteConfigurationCandidate(metadata)) {
            //设置属性,相当于beanDef.setAttribute("configurationClass","lite")
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
            //实际上,如果你打断点仔细运行,你会发现,sring内置后置处理器都没有上面那些注解
            return false;
        }

        //如果有@Order注解,拿到注解的value值,并将值设置到属性中,后面执行会根据这个属性值的大小进行先后调用
        Integer order = getOrder(metadata);
        if (order != null) {
            beanDef.setAttribute(ORDER_ATTRIBUTE, order);
        }
        return true;
    }

由此可知,所谓的配置类就是要么含有@Configuration注解,要么含有@Component、@ComponentScan、@Import、@ImportResource其中之一,要么类中含有@Bean注解的方法。注意其中@Component包括他的子注解@Service、@Controller、@Repository。spring后续根据这些注解完成扫描和注册。 实际上上面的代码运行结束后,只有我们的配置类Config对应的BeanDefinition完成了属性值的设置,且返回true,最后添加到集合configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));。spring内置的后置处理器都没有上面所说的注解所以返回false,自然不会添加到集合中。源码中我也解释清楚了,希望读者仔细阅读思考,有疑问打在留言区一起探讨,我会一一回复。 继续往下看源码:

if (configCandidates.isEmpty()) {
            return;
        }

如果,我们没有提供配置类,直接返回,后续也不会扫描和注册。实际运用过程中,我们不可能让spring不扫描,难不成让我们手动封装BeanDefinition,可以,试问开发组有几个懂BeanDefinition的?所以这行代码永远不会执行。 继续看源码:

    configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

上面checkConfigurationClassCandidate代码中的最后处理了@Order注解,将他的值放到了属性中,在这里,如果你有多个配置类,进行排序,后面会按照顺序进行处理。打个比方:

@ComponentScan("com")
@Order(1)
public class Config {
}

@Service
@Order(2)
public class Config2 {
}

public class SpringTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class);
        context.register(Config2.class);
        context.refresh();
    }
}

配置类排序

OK,继续看源码:

SingletonBeanRegistry sbr = null;
        /**
         * 由于当前传入的是DefaultListableBeanFactory是SingletonBeanRegistry的子类
         */
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            /**
             * 判断是否有自定义的beanName生成器
             */
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                // 获取spring默认的beanName生成器,这里为空
                if (generator != null) {
                    /**
                     * componentScanBeanNameGenerator与importBeanNameGenerator定义时就赋值了new AnnotationBeanNameGenerator()
                     * 如果spring有默认的beanName生成器,则重新赋值
                     */
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

说实话,笔者不是很关心这几行源码,大概意思就是如果你提供了自定义的名字生成器那就用你的,否则spring用自己默认的名字生成器,我知道是Class类名首字母小写。读者可研究下名字生成器类AnnotationBeanNameGenerator,很简单。它实现了BeanNameGenerator接口,这个接口只有一个String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);方法,你可以实现这个接口生成自定义名字生成器。打个比方:

//自定义名字生成器
public class MyNameGenerator implements BeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return definition.getBeanClassName()+"源码之路";
    }
}

public class SpringTest {

    public static void main(String[] args) {


        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(Config.class);
        //创建一个名字生成器类
        MyNameGenerator myNameGenerator = new MyNameGenerator();
//通过上面的源码可知,必须以单例的方式注册的bean工厂中
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,myNameGenerator);
        context.refresh();
        System.out.println();
    }
}

调试结果:

自定义名字生成器

在读此篇博客之前,如果你要生成一个自定义名字生成器你怎么做?查百度对吧,百度没有呢?spring源码重要吗?不重要,工作中会用spring就行,我关注业务就行,谁会关注源码?谁会关注?你的技术领导会,你们的CTO会,因为他是建筑师,而你是建筑工人。我没有吹嘘我自己,我只是强调spring源码对你的整个职业生涯起着至关重要的作用。 不啰嗦,继续往下看:

if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }

标准资源环境,什么鬼,暂不解释,以后专门写一篇博客讲解环境抽象。

上面我们拿到了配置类集合configCandidates,下一步就是解析处理这些配置类。

      ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Spring的工具类ConfigurationClassParser用于分析@Configuration注解的配置类,产生一组ConfigurationClass对象。下文会用到。

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());

实例化两个set,,candidates用于将之前加入的configCandidates去重, alreadyParsed用于判断是否处理过了。 代码再往下就是开始循环do while 循环解析处理candidates配置类了。

            //解释配置类                 
            parser.parse(candidates);

跟进这个方法:

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //遍历分析配置类
        for (BeanDefinitionHolder holder : configCandidates) {
            //得到配置类对应的BeanDefinition
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                // 这里根据Bean定义的不同类型走不同的分支,但是最终都会调用到方法
                // processConfigurationClass(ConfigurationClass configClass)
                // 判断是否是AnnotatedBeanDefinition类型,上文分析过了
                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);
            }
        }
        // 执行找到的 DeferredImportSelector 
        //  DeferredImportSelector 是 ImportSelector 的一个变种。
        // ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector
        // 的类可以条件性地决定导入哪些配置。
        // DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是
        // 该语句被放到本函数最后一行的原因。
        this.deferredImportSelectorHandler.process();
    }

先关注里面的parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());方法,参数有两个,一个是注解元数据,一个bean的名称,继续跟进:

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

首先将注解元数据和bean名称封装成ConfigurationClass,这个类翻译成中午就是“配置类”。继续跟进: processConfigurationClass中的第一行就是检查当前解析的配置bean是否包含Conditional注解,如果不包含则不需要跳过,如果包含了则进行match方法得到匹配结果,match方法返回true则跳过不解析,返回false不跳过。什么意思呢?我们继续打比方: 先生成一个业务类

public class Person {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

再创建一个配置类:

@Configuration
public class BeanConfig {
    @Bean(name = "bill")
    public Person person1(){
        return new Person("Bill Gates",62);
    }
    @Bean("linus")
    public Person person2(){
        return new Person("Linus",48);
    }
 }

测试:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();

        Map<String, Person> map = context.getBeansOfType(Person.class);
        System.out.println(map);
    }
}
打印结果:
{bill=Person{name='Bill Gates', age=62}, linus=Person{name='Linus', age=48}}

问题来了,如果我想根据当前操作系统来注入Person实例,windows下注入bill,linux下注入linus,怎么实现呢?这就需要我们用到@Conditional注解了。 首先,创建一个WindowsCondition类:

public class WindowsCondition  implements Condition {
    /**
     * @param conditionContext:判断条件能使用的上下文环境
     * @param annotatedTypeMetadata:注解所在位置的注释信息
     * */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        //获取bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (property.contains("Windows")){
            return true;
        }
        return false;
    }
}

接着,创建LinuxCondition类:

public class LinuxCondition  implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        Environment environment = conditionContext.getEnvironment();

        String property = environment.getProperty("os.name");
        if (property.contains("Linux")){
            return true;
        }
        return false;
    }
}

接着就是使用这两个类了,因为此注解可以标注在方法上和类上,所以分开测试:

  1. 修改BeanConfig:
@Configuration
public class BeanConfig {
    //只有一个类时,大括号可以省略
    //如果WindowsCondition的实现方法返回true,则注入这个bean
    @Conditional({WindowsCondition.class})
    @Bean(name = "bill")
    public Person person1(){
        return new Person("Bill Gates",62);
    }

    //如果LinuxCondition的实现方法返回true,则注入这个bean
    @Conditional({LinuxCondition.class})
    @Bean("linus")
    public Person person2(){
        return new Person("Linus",48);
    }
 }

打印结果:{bill=Person{name='Bill Gates', age=62}},注意一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。

  1. 标注在类上: 一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。我们试一下,将BeanConfig改写,这时,如果WindowsCondition返回true,则两个Person实例将被注入:
@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
    //只有一个类时,大括号可以省略
    //如果WindowsCondition的实现方法返回true,则注入这个bean
//  @Conditional({WindowsCondition.class})
    @Bean(name = "bill")
    public Person person1(){
        return new Person("Bill Gates",62);
    }

    //如果LinuxCondition的实现方法返回true,则注入这个bean
//  @Conditional({LinuxCondition.class})
    @Bean("linus")
    public Person person2(){
        return new Person("Linus",48);
    }
 }

@Conditional注解传入的是一个Class数组,存在多种条件类的情况。这种情况貌似判断难度加深了,测试一波,新增新的条件类,实现的matches返回false(这种写死返回false的方法纯属测试用,没有实际意义O(∩_∩)O)

public class ObstinateCondition  implements Condition{
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
            return false;
    }
}

BeanConfig修改一下:

@Conditional({WindowsCondition.class,ObstinateCondition.class})
@Configuration
public class BeanConfig {
 
    @Bean(name = "bill")
    public Person person1(){
        return new Person("Bill Gates",62);
    }
 
    @Bean("linus")
    public Person person2(){
        return new Person("Linus",48);
    }
}

请读者自行测试,这个例子就是说明processConfigurationClass中的第一行代码的作用,如果不符合条件注册bean到IOC,否则不继续执行。

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

读到这里大家都知道,我们的配置类只有Config.java,我们在这个配置类上加一个Condition条件:

@Order(1)
@ComponentScan("com")
@Conditional({LinuxCondition.class})
public class Config {
}

讲道理,此时,上文中的判断为true,代码直接return,也就是说你这个配置类无效,spring不会继续解析你这个配置类了。但实际上,不会rerurn。为什么?spring启动过程中,我们是这样注册配置类的context.register(Config.class);,这个register源码之前讲过,一直跟到doRegisterBean中,有这么两行代码:

//@Conditional装配条件判断是否需要跳过注册
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
            return;
        }

也就是说,我们在注册Config配置类的时候spring已经对Condition条件进行判断了,不会生成Config对应的BeanDefinition了,那怎么办?看:

//      context.register(Config.class);
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(Config.class);
        context.registerBeanDefinition("config",abd);

不让spring做判断,手动注册BeanDefinition,打断点调试:

image.png

好了,shouldSkip这行代码讲清楚了吧,全网没有我讲这么细的,你可以尽情搜百度,能找到我讲这么细的给我留言,我个你发红包。我说了,这部分内容很难,你能坚持看到这里而且你能看懂,最低阿里P6级别。 不吹了,继续看源码:

// 第一次进入的时候, configurationClasses size = 0,existingClass 肯定为 null
        //在这里处理Configuration重复import
        //如果同一个配置类被处理两次,两次都属于被import的则合并导入类,返回。如果配置类不是被导入的,则移除旧使用新的配置类
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    //如果要处理的配置类configClass在已经分析处理的配置类记录中已存在,
                    //合并二者的importedBy属性
                    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);
            }
        }

        //递归地处理配置类及其超类层次结构。sourceClass包含了calss文件,后面获取父类用
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            // 真正的做解析
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);

第一次调用existingClass 肯定为空,难道还有第二次调用?是的,@Import注解就是引入,先不管这个,代码会跳过判断,执行SourceClass sourceClass = asSourceClass(configClass);,从当前配置类configClass开始向上沿着类继承结构逐层执行doProcessConfigurationClass,直到遇到的父类是由Java提供的类结束循环。然后就是真正的解析sourceClass = doProcessConfigurationClass(configClass, sourceClass);,循环处理配置类configClass直到sourceClass变为null,doProcessConfigurationClass的返回值是其参数configClass的父类,如果该父类是由Java提供的类或者已经处理过,返回null。doProcessConfigurationClass()会对一个配置类执行真正的处理: 1. 一个配置类的成员类(配置类内嵌套定义的类)也可能适配类,先遍历这些成员配置类,调用processConfigurationClass处理它们; 2. 处理配置类上的注解@PropertySources,@PropertySource 3. 处理配置类上的注解@ComponentScans,@ComponentScan 4. 处理配置类上的注解@Import 5. 处理配置类上的注解@ImportResource 6. 处理配置类中每个带有@Bean注解的方法 7. 处理配置类所实现接口的缺省方法 8. 检查父类是否需要处理,如果父类需要处理返回父类,否则返回null

返回父类表示当前配置类处理尚未完成,调用者processConfigurationClass会继续处理其父类;返回null才表示该配置类的处理完成。从这里可以推断一旦一个配置类被processConfigurationClass处理完成,表示其自身,内部嵌套类,各个实现接口以及各级父类都被处理完成。

上面的步骤我们挨个分析,进入doProcessConfigurationClass源码,首先处理的就是内部类:

// @Configuration 继承了 @Component
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // 递归处理内部类,因为内部类也可能是一个配置类
            //配置类上有@Configuration注解,该注解继承 @Component,if 判断为true,
            // 调用processMemberClasses方法,递归解析配置类中的内部类。
            processMemberClasses(configClass, sourceClass);
        }

processMemberClasses方法就是处理内部类,为什么要处理内部类?因为内部类也可能是一个配置类,打个比方:

@Order(1)
@Configuration
@ComponentScan("com.anno")
public class Config {
    @Order(1)
    @Configuration
    @ComponentScan("com.scan")
    class InnerClass{
    }
    @Order(2)
    @Configuration
    @ComponentScan("com.condition")
    class InnerClass2{
    }
}

进入到processMemberClasses方法里面:

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
        //找到内部类,因为内部类也可能是一个配置类
        Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
        if (!memberClasses.isEmpty()) {
            List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
            for (SourceClass memberClass : memberClasses) {
                //判断内部类是不是配置类,上文讲过了如何判断是不是配置类
                if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
                        !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                    candidates.add(memberClass);
                }
            }
            //对配置类进行那个排序,上文也讲过了
            OrderComparator.sort(candidates);
            for (SourceClass candidate : candidates) {
                if (this.importStack.contains(configClass)) {
                    //1)出现配置类循环导入,直接报错
                    this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
                }
                else {
                    //将配置类入栈
                    this.importStack.push(configClass);
                    try {
                        //还是调用processConfigurationClass方法,也许内部类还有内部类
                        //其实,工作中我们不会内部类嵌套内部类再嵌套内部类再嵌套。。。
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                    finally {
                        //解析完出栈
                        this.importStack.pop();
                    }
                }
            }
        }
    }

注释解释的很清楚,就是判断内部类是否是配置类,上文已经讲过如何判断是否是内部类了,如果是配置类还是要调用processConfigurationClass方法来处理,这是一个递归的过程,一直到没有内部配置类为止。

配置类内部类处理完,紧接着就是处理配置类上的注解@PropertySources,@PropertySource,如果配置类上有@PropertySource注解,则解析加载properties文件,并将属性添加到Spring上下文中。源码:

    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");
            }
        }

这段源码,小编没有分析过,我就不误人子弟了,但是这段代码的执行结果就是将我们自定义的资源文件添加到spring上下文环境中,我们通过一个例子来说明: 首先在resources中添加一个资源文件demo.properties:

demo.name=this is my properties

资源文件

修改以下我们的配置类:

@Order(1)
@Configuration
@ComponentScan("com")
@PropertySource({"classpath:demo.properties"})
public class Config {
    @Value("${demo.name}")
    private String name;
    public String getName() {
        return name;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
        System.out.println(context.getBean(Config.class).getName());
    }
}
打印结果:
demo.name=this is my properties

spring已经将配置文件加入到系统环境中了,在任何地方都可以通过@Value("${demo.name}")方式进行调用拉!


OK,下一步处理配置类上的注解@ComponentScans,@ComponentScan。 第一步先获取@ComponentScans@ComponentScan注解:

Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);

然后,开始循环遍历扫描注解,拿到注解里的值进行扫描注册:

// 备注:这个方法虽然有返回值,但是其实内部都已经把Bean定义信息加入到工厂里面去了
Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

费这么大劲儿,终于要开始扫描了。进入parse方法,第一行代码创建一个ClassPathBeanDefinitionScanner对象,这个东西我们在《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》《逐行阅读Spring5.X源码(番外篇)自定义扫描器, Mybatis是如何利用spring完成Mapper扫描的》讲过。

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

我们知道,spring启动过程中会在构造方法中生成一个ClassPathBeanDefinitionScanner对象:

image.png

手动扫描的时候就是利用这个对象完成扫描的:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册配置类
        context.register(Config.class);
        context.refresh();
        context.scan("com");
    }
}


@Override
    public void scan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        this.scanner.scan(basePackages);
    }

但是spring内部扫描的时候确又生成了这个对象,两个地方调用的构造函数不同,spring内部生成的这个扫描对象功能更丰富。我想spring是想给程序员提供扫描的功能,但是又不想给你更多的权限吧。 OK,下一行代码:

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

BeanName的生成器,我们可以单独制定。若不指定(大部分情况下都不指定),那就是默认的AnnotationBeanNameGenerator,它的处理方式是:类名首字母小写。这个上面也讲过啦。

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));
        }

这两个属性和scope代理相关的,这里略过,使用较少。

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

控制去扫描哪些.clsss文件的模版。一般不设置 默认值为:*/.class 全扫嘛

for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addIncludeFilter(typeFilter);
            }
        }
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addExcludeFilter(typeFilter);
            }
        }

之前讲过,就是扫描过滤器,如果你注解上写了过滤器,就复制到ClassPathBeanDefinitionScanner扫描器中。

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

扫描的Bean,支持懒加载啦,默认为false。

        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);
        }

Spring在此处有强大的容错处理。虽然他是支持数组的,但是它这里也容错处理,支持 , ; 换行等的符号分隔处理,并且,并且更强大的地方在于:它支持${...}这种占位符的形式,非常的强大。我们可以动态的进行扫包了~~~~~厉害了我的哥。比如

@ComponentScan(value = "com,net" )

会根据逗号解析成com包和net包放到数组中。

if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

basePackageClasses有时候也挺好使的。它可以指定class,然后这个class所在的包(含子包)都会被扫描,打个比方:

@Order(1)
@Configuration
@PropertySource({"classpath:demo.properties"})
@ComponentScan(value = "com,net",basePackageClasses = Person.class)
public class Config {
    @Value("${demo.name}")
    private String name;

    public String getName() {
        return name;
    }
}

我们的Person类在com.scan.condition包下,调试结果:

image.png

if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

如果我们没有指定此值,它会取当前配置类所在的包 ,是不是很熟悉?SpringBoot就是这么干的,第一次接触过springboot的时候,我就很奇怪为啥要把启动类放在根目录下。

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

这个行代码的意思增加一个排他过滤器,配置类本身不需要添加到容器中,他只是用作解析。 最后一行代码scanner.doScan(StringUtils.toStringArray(basePackages));完成扫描,将符合规则的类添加到IOC容器中,在这里不深入分析了,前面讲了,我们在《逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器》详细讨论了,建议读者好好阅读这篇文章。 parse扫描解析讲完了,我们跳出parse方法继续往下看。

    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());
                    }
                }

parse扫描注册完成后,将生成的BeanDefinition返回,还需要把每个Bean检测一遍。因为Scan出来的Bean,还有可能是@Configuration的,因此需要再次交给parse一遍,防止疏漏,换句话说,扫描后的业务类中还有配置类,我们还需要处理这些配置类。getOriginatingBeanDefinition返回原始的 BeanDefinition,没有的话返回 null,链式调用该方法,最终可获取到由用户定义的 BeanDefinition。

注解@ComponentScans,@ComponentScan讲完了,下一步看注解@Import的处理。

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

processImports方法负责对@Import注解进行解析。configClass是配置类,sourceClass又是通过configClass创建的,getImports(sourceClass)从sourceClass获取所有的@Import注解信息,然后调用ConfigurationClassParser#processImports。 在应用中,有时没有把某个类注入到IOC容器中,但在运用的时候需要获取该类对应的bean,此时就需要用到@Import注解。spring首先把import进来的类当作配置类处理,然后递归处理该配置类。继续打比方: 首先创建一个业务类,且不要放到扫描包下,是不会扫描到IOC容器中的:

public class User {

}

然后import到配置类上

@Order(1)
@Configuration
@PropertySource({"classpath:demo.properties"})
@ComponentScan(value = "com,net",basePackageClasses = Person.class)
@Import(User.class)
public class Config {
    @Value("${demo.name}")
    private String name;

    public String getName() {
        return name;
    }
}

我们看下processImports的源码怎么处理注解@Import的

if (importCandidates.isEmpty()) {
            return;
        }

这行代码的意思是如果没有@Import注解,就返回不做任何处理。

if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }

这行判断是解决循环引用的,暂不关心,以后会详细将循环引用问题。第一次的话执行else语句,然后拿出@Import注解中的所有类进行循环处理,这里只有User。if语句中有三种处理情况:

  1. 实现ImportSelector接口
  2. 实现ImportBeanDefinitionRegistrar接口
  3. 既没有实现ImportSelector接口也没有实现ImportBeanDefinitionRegistrar接口 先看第一种 实现ImportSelector接口。 再看第二种实现ImportBeanDefinitionRegistrar接口。 最后看既没有实现ImportSelector接口也没有实现ImportBeanDefinitionRegistrar接口。

ImportSelector接口:ImportSelector接口只定义了一个selectImports(),用于指定需要注册为bean的Class名称。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。来看一个简单的示例,假设现在有一个接口HelloService,需要把所有它的实现类都定义为bean,而且它的实现类上是没有加Spring默认会扫描的注解的,比如@Component、@Service等。 首先让上文的User类实现ImportSelector接口,然后在方法中返回User2权限定名,这样spring就会将User2添加到容器中。

public class User2 {
}

public class User  implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {User2.class.getName()};
    }
}

我们看源码实现:

//如果实现了ImportSelector接口
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
    Class<?> candidateClass = candidate.loadClass();
// 反射创建这个类的实例对象
    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
//是否有实现相关Aware接口,如果有,这调用相关方法
    ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
// 延迟加载的ImportSelector
    if (selector instanceof DeferredImportSelector) {
//  延迟加载的ImportSelector先放到List中,延迟加载
    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
    }else {
// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
    processImports(configClass, currentSourceClass, importSourceClasses, false);
    }
}

代码很简单,相信大家能看懂,但是有这么行代码

ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);

判断是否有实现相关Aware接口,如果有,这调用相关方法。Aware,是感应和感知的意思。当bean实现了对应的Aware接口时,BeanFactory会在生产bean时根据它所实现的Aware接口,给bean注入对应的属性,从而让bean获取外界的信息。Aware是顶级接口,它有很多子接口:

Aware系列接口

如果我们被Import的User类实现了Aware接口,就会在上面invokeAwareMethods源码内调用实现该接口的方法,但是在此处不是所有有Aware接口都可以,invokeAwareMethods源码:

    public static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
            ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {

        if (parserStrategyBean instanceof Aware) {
            if (parserStrategyBean instanceof BeanClassLoaderAware) {
                ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
                        ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
                if (classLoader != null) {
                    ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
                }
            }
            if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
                ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
            }
            if (parserStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
            }
            if (parserStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
            }
        }
    }

只有BeanClassLoaderAware、BeanFactoryAware 、EnvironmentAware、ResourceLoaderAware接口才能被调用,其他Aware接口啥时候调用以后再说。举个例子:

public class User  implements ImportSelector, BeanFactoryAware {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {User2.class.getName()};
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("获取到了bean工厂");
    }
}

也就是说我们能在User实现接口的方法内操作以下方法:

image.png

Aware接口处理完后,就是判断User是否是延迟加载:

if (selector instanceof DeferredImportSelector)

显然,我们的User实现的ImportSelector接口,没有实现DeferredImportSelector接口,所以就不是延迟加载,DeferredImportSelector是ImportSelector的子接口。如果我们要实现延迟加载功能,让User实现DeferredImportSelector接口即可:

public class User  implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("延迟加载User2");
        return new String[] {User2.class.getName()};
    }
}

启动spring,就会进入上面的if判断里的this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);方法,handle方法内会将selector再封装成DeferredImportSelectorHolder类型的对象,然后保存在数组中以后调用,调用时机之后再将,先记住这点。 如果不是延迟加载的话,if条件不执行,执行else中的语句:

// 普通的ImportSelector ,执行其selectImports方法,获取需要导入的类的全限定类名数组
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
////获取需要导入类的class
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归调用
processImports(configClass, currentSourceClass, importSourceClasses, false);

为什么要递归调用,因为你导入的类可能还有Import注解啊,那也要处理的。 ImportBeanDefinitionRegistrar接口:ImportBeanDefinitionRegistrar接口是也是spring的扩展点之一,它可以支持我们自己写的代码封装成BeanDefinition对象;实现此接口的类会回调postProcessBeanDefinitionRegistry方法,注册到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;还可以实现此接口。我们继续打个比方,比方太惨了,全文被打:

public class User  implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(User2.class.getName());
        MutablePropertyValues values = beanDefinition.getPropertyValues();
        values.addPropertyValue("id", 1);
        values.addPropertyValue("name", "源码之路");
        //这里注册bean
        registry.registerBeanDefinition("testBean", beanDefinition );
    }
}

其实,很小儿科,不讲了,在BeanDefinition博文中讲烂了。

最后,既没有实现ImportSelector接口也没有实现ImportBeanDefinitionRegistrar接口:

 // 普通 @Configuration class
 this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 解析导入的@Configuration class
processConfigurationClass(candidate.asConfigClass(configClass));

按普通配置类处理拉~ @Import注解处理暂时先告一段落,下面继续看@ImportResource注解的处理。 @ImportResource用来导入Spring 的配置文件,如核心配置文件 "beans.xml",从而让配置文件里面的内容生效。讲道理,大家平时都不用xml配置文件来做开发了吧,注解方式差不多完全替代XML方式了。这个也很简单,不讲了,这篇文章还能坚持看到这里的读者,想必有能力分析这块的源码了。注意,这块源码的处理并没有解析配置文件,而是先保存在map中,后续解析。先记着,整个方法分析完了你就知道了。 下一步,处理@Bean注解的方法:

    //找到含有@Bean注解的方法
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            //先添加到集合中
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

将@Bean方法转化为BeanMethod对象,保存在集合中,后续再用。 下一步,处理配置类所实现接口的缺省方法:java8中,新增加了一个特性,接口中可以有默认实现方法:

public interface UserInterface {
    /*
     * 默认方法,有方法体
     * 任何一个实现了UserInterface接口的类都会向动继承isEmpty的实现
     */
    @Bean
    default User2 getUser2() {
        return new User2();
    }
}

默认方法,有方法体,任何一个实现了Sized接口的类都会向动继承isEmpty的实现,这个默认方法加一个@Bean注解的话,且返回一个对象,spring会将这个对象注册到spring容器中。processInterfaces(configClass, sourceClass);就是完成这个功能的。这个方法的源码也比较简单:

    private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
        //找到配置类的所有接口,遍历接口
        for (SourceClass ifc : sourceClass.getInterfaces()) {
            //找到含有@Bean注解的默认方法
            Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
            for (MethodMetadata methodMetadata : beanMethods) {
                if (!methodMetadata.isAbstract()) {
                    // A default method or other concrete method on a Java 8+ interface...
                    //添加到集合
                    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
                }
            }
            //递归,因为接口可能有父接口
            processInterfaces(configClass, ifc);
        }
    }

最后,最后,最后一步,如果有父类,则解析父类。坚持下!

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();
    }
}

如果有父类则返回父类Class对象,继续调用该方法。直到返回null,外层循环结束。怎么讲?doProcessConfigurationClass此时结束了,我们看调用此方法的上一层方法processConfigurationClass

      以上省略
do {
            // 真正的做解析
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

就是说,如果你有父类,此时返回来的sourceClass 是父类,继续递归调用doProcessConfigurationClass方法,直到返回的是null后while循环才结束,如果没有父类,则执行doProcessConfigurationClass方法的最后一行代码return null;,此时的while循环也结束了,继续执行最后一行代码 this.configurationClasses.put(configClass, configClass); 这行代码的意思是,需要被处理的配置类configClass已经被分析处理,将它记录到已处理配置类记录。

至此,我们的processConfigurationClass->doProcessConfigurationClass方法分析完了,往回返,一直到入口处,即ConfigurationClassPostProcessor.processConfigBeanDefinitions方法中parser.parse(candidates);代码处,对吧,上面讲了那么多,就是讲了parse方法的处理。

我们继续分析parse的后续源码,坚持住,马上看到曙光了。 parser.validate();将解析完的@Configuration配置类进行校验,主要包括两方面的校验:

  1. 配置类不能是final(CGLIB限制)
  2. @Configuration配置类中的@Bean方法必须可重写以容纳CGLIB 这是为CGLIB做准备,后面会开一篇博客专门将CGLIB,源码比较简单,这里不做分析啦。

继续: this.reader.loadBeanDefinitions(configClasses); 在这里统一处理没有注册的进行注册,将上一步parser解析出的ConfigurationClass类加载成BeanDefinition,实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean注解的方法,因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法。是不是蒙蔽了这里,起始之前的源码都经历过了,建议大家收藏这篇博文,对照着源码反复看。 再有20多行代码就分析完了,加油!

if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                // 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }

这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition,实际上就是看配置类, 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length ,这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析,这里的Config类向容器中添加的bean,实际上在parser.parse()这一步已经全部被解析了,所以为什么还需要做这个判断,目前没看懂,似乎没有任何意义。

好了,到此为止,ConfigurationClassPostProcessor 中最牛逼的postProcessBeanDefinitionRegistry的方法就分析完了,spring启动过程中,会自动调用改处理器的postProcessBeanDefinitionRegistry方法完成扫描与注册。这篇博文,能看到这里的我想不会有,自己安慰一下自己吧,我觉着很多人没有耐心读到这里,spring源码太难讲了,调用关系很复杂。讲源码不能泛泛的讲,我也不知道咋讲好了。写这一篇博客,用了一个周的时间,回头看看感觉讲的还是有点乱,程序员喜欢看框图,一目了然,那就用框图总结一下吧:

ConfigurationClassPostProcessor.png

·

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 逐行阅读Spring5.X源码(三) BeanDefinition的实现类详解,拔高

    上回咱们讲了BeanDefinition的父接口及父接口实现类。本篇博文咱么继续讲BeanDefinition的实现类。包括AbstractBe...

    源码之路
  • 逐行阅读Spring5.X源码(八)Mybatis是如何利用MapperScan完成扫描的?

    mybaits是通过@MapperScan注解完成扫描的,具体是如何完成的呢?首先看一下MapperScan的源码:

    源码之路
  • 「高并发通信框架Netty4 源码解读(四)」NIO缓冲区之字节缓冲区ByteBuffer详解

    非字节类型的基本类型,除了布尔型 3都是由组合在一起的几个字节组成的。这些数据类型及其大小总结在下表中

    源码之路
  • 聊聊spring cloud的HystrixCircuitBreakerConfiguration

    本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration

    codecraft
  • springboot之mvc原理(二)-能力支持

    前边一篇文章《springboot之mvc原理(一)-请求处理》我们详细分析了springboot应用接收http请求的处理过程,那么进一步的思考一下,...

    叔牙
  • The Clean Architecture in PHP 读书笔记(二)

    设计模式是对软件中通用问题的总结,有了设计模式,方便我们进行交流,譬如一说MVC,我们就知道是怎么回事了,不然我们必须巴拉巴拉一大堆话去描述,不易于传播、交流,...

    zhuanxu
  • SpringMVC源码解析(一)

    为了简单起见,再一个就是现在这个年代也没有啥项目使用JSP了。所以本次分析使用SpringBoot结合thymeleaf来搞

    Java学习录
  • Excel解析工具easyexcel全面探索

    之前我们想到Excel解析一般是使用POI,但POI存在一个严重的问题,就是非常消耗内存。所以阿里人员对它进行了重写从而诞生了easyexcel,它解决了过于消...

    老梁
  • Spring源码学习笔记(7)——使用@Import导入组件

    @Import注解的作用是导入其他的配置类或者组件,等同于在applicationContext.xml文件中添加如下配置

    张申傲
  • 面试官问我单例模式真的安全吗?我懵逼了

    某天晚上,我和基友正在开黑排位,刚刚被敌方EZ用E躲了我混分巨兽的石破天惊,恼羞成怒的我正准备咬牙切齿还回去时,手机响了。

    大王叫下

扫码关注云+社区

领取腾讯云代金券