上一篇幅我們講到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,這個方法掃描得到所有的BeanDefinition,那具體是怎麼掃描得到所有BeanDefinition的呢?postProcessBeanDefinitionRegistry調用了一個核心方法:processConfigBeanDefinitions,代碼如下:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
//關鍵點一
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
//關鍵點二
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
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 {
//關鍵點三
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());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
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());
}
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();
}
}
我們來分析下該方法的幾個關鍵點:
關鍵點一
調用ConfigurationClassUtils.checkConfigurationClassCandidate方法來檢查有哪些是配置類候選者?因為隻有有了配置類,Spring才直到如何進一步掃描。Spring是如何來确定哪些是配置類的呢?且看checkConfigurationClassCandidate方法的邏輯代碼:
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
......
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}else {
return false;
}
......
}
如上代碼,有三種類型的類是配置類:
- 增加了Configuration注解的類,這類配置類的BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性被設定為了CONFIGURATION_CLASS_FULL,還記得我們上一篇幅講的對于CONFIGURATION_CLASS_FULL這種配置類是需要特别處理的,對,就是在這裡辨別的。
- 通過isConfigurationCandidate方法檢測的類,這類配置類的BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性被設定為了CONFIGURATION_CLASS_LITE,是不是和上面不一樣。哪些是通過檢測的呢?看代碼:
private static final Set<String> candidateIndicators = new HashSet<>(8);
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) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
看到了吧,加了Component、ComponentScan、Import、ImportResource注解的類,或者這個類裡面有方法加了Bean注解的類都是通過檢測的類。
關鍵點二
如果目前BeanFactory支援單例bean,則設定一個BeanNameGenerator,用來在掃描@Component和@Import某個Bean時取一個名字,這裡程式員可以通過調用applicationContext.setBeanNameGenerator方法來指定一個beanName生成器。
關鍵點三
parser.parse這個是核心中的核心,在"關鍵點一"中已經獲得了需要解析的配置類,然後這裡調用parse開始對配置BeanDefinition進行解析,在該方法中會周遊配置類的BeanDefinition,通過ASM這種位元組碼處理技術來得到資源資訊,注意這裡不是直接加載位元組碼到記憶體中,得到資源資訊後,調用doProcessConfigurationClass方法來進行處理,在該方法中會針對@Component、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean注解的配置類分别處理,由于篇幅原因,詳細我們下一篇介紹,本篇幅中大家隻要直到,這個方法就是掃描得到具體的BeanDefinition就可以了。
掃描完成後,目前這個注解類也是需要調用this.reader.loadBeanDefinitions(configClasses)方法注冊成為BeanDefinition的;
我們再來關注另外一個問題,這裡為什麼要用個do...while循環呢?
其實很簡單,因為咱們在掃描完成後,肯定會掃描到新的配置類,是以需要循環調用parse來對新的配置類進行處理。