ConfigurationClassParser: @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { ... // 先解析nested内部类(内部类会存在@Bean方法嘛~) ... // 解析@PropertySource资源,加入到environment环境 ... // 解析@ComponentScan注解,把组件扫描进来 scannedBeanDefinitions = ComponentScanAnnotationParser.parse(componentScan, ...); // 把扫描到的Bean定义信息依旧需要一个个的判断,是否是配置类 // 若是配置类,就继续当作一个@Configuration配置类来解析parse() 递归嘛 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { ... if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } ... // 解析@Import注解 ... // 解析@ImportResource注解 ... // 解析当前配置里配置的@Bean方法 ... // 解析接口默认方法(因为配置类可能实现接口,然后接口默认方法可能标注有@Bean ) ... // 处理父类(递归,直到父类为java.打头的为止) }
这个方法是Spring对配置类解析的最核心步骤,通过它顺带也能够解答你的疑惑了吧:为何你仅需在类上标注一个@Configuration注解即可让它成为一个配置类?因为被Scan扫描进去了嘛~
通过以上两个使用处的分析和对比,对于@Configuration配置类的理解,你至少应该掌握了如下讯息:
@Configuration配置类肯定是个组件,存在于IoC容器里
@Configuration配置类是有主次之分的,主配置类是驱动整个程序的入口,可以是一个,也可以是多个(若存在多个,支持使用@Order排序)
我们平时一般只书写次配置类(而且一般写多个),它一般是借助主配置类的@ComponentScan能力完成加载进而解析的(当然也可能是@Import、又或是被其它次配置类驱动的)
配置类可以存在嵌套(如内部类),继承,实现接口等特性
聊完了最为重要的checkConfigurationClassCandidate()方法,当然还有必要看看ConfigurationClassUtils的另一个工具方法isConfigurationCandidate()。
isConfigurationCandidate()
它是一个public static工具方法,通过给定的注解元数据信息来判断它是否是一个Configuration。
ConfigurationClassUtils: static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); } public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // 不考虑接口 or 注解 说明:注解的话也是一种“特殊”的接口哦 if (metadata.isInterface()) { return false; } // 只要该类上标注有以上4个注解任意一个,都算配置类 for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 若一个注解都没标注,那就看有木有@Bean方法 若有那也算配置类 return metadata.hasAnnotatedMethods(Bean.class.getName()); }
步骤总结:
- 若是接口类型(含注解类型),直接不予考虑,返回false。否则继续判断
- 若此类上标注有@Component、@ComponentScan、@Import、@ImportResource任意一个注解,就判断成功返回true。否则继续判断
- 到此步,就说明此类上没有标注任何注解。若存在@Bean方法,返回true,否则返回false。
需要特别特别特别注意的是:此方法它的并不考虑@Configuration注解,是“轻量级”判断,这是它和checkConfigurationClassCandidate()方法的最主要区别。当然,后者依赖于前者,依赖它来根据注解元数据判断是否是Lite模式的配置。
Spring 5.2.0版本变化说明
因为本文的讲解和代码均是基于Spring 5.2.2.RELEASE的,而并不是所有小伙伴都会用到这么新的版本。关于此部分的实现,以Spring 5.2.0版本为分界线实现上有些许差异,所以在此处做出说明。