为什么要强调SpringBoot中的BeanDefinition加载过程? 在阅读很多相对比较旧的讲解Spring容器
的书籍或文章时,由于当时SpringBoot
并不是很盛行,甚至还没有SpringBoot
,导致对于Spring容器
启动的讲解并没有提到与SpringBoot
容器启动过程的差异,导致很多读者默认为这两者是一样的。
事实上,在容器启动时,传统的配置式的Spring
项目与使用了SpringBoot
的项目在启动时大体上是相近的,但是区别也还是不小的,本文要提到的BeanDefinition
加载过程中是其中一个例子。
找不同 因为讲解Spring
启动过程的文章已经很多了,在这里不详细介绍这部分内容,但是为了让读者阅读更清晰,以及便于理解,我们从源码中寻找一下两种方式差异的原因。
这里假设读者已经自行阅读过Spring
启动的相关文章了,所以我们知道,Spring
启动的核心过程是ApplicationContext#refresh
方法。而加载BeanDefinition
是在obtainFreshBeanFactory
方法中,贴一下代码
1 2 3 4 5 6 7 8 9 protected ConfigurableListableBeanFactory obtainFreshBeanFactory () { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
这里调用了refreshBeanFactory
方法进行刷新容器,想进一步查询其源码时,发现这个方法有两个实现类,分别是AbstractRefreshableApplicationContext
和GenericApplicationContext
。
代码到了这里出现了分岔路口,我们看看具体应该进入哪个分支。先回想一下传统方式创建一个Spring容器
的方式
1 ApplicationContext applicationContext = new FileSystemXmlApplicationContext ("/src/main/resources/spring.xml" );
看一下FileSystemXmlApplicationContext
类的类继承图
可以发现FileSystemXmlApplicationContext
继承AbstractRefreshableApplicationContext
从而实现了refreshBeanFactory
方法。我们进入AbstractRefreshableApplicationContext#refreshBeanFactory
可以看到,跟我们之前了解的一样,在这里加载了BeanDefinition
,简单贴一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected final void refreshBeanFactory () throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this .beanFactoryMonitor) { this .beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException ("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
那么SpringBoot
是如何呢?我们从代码中寻找答案。因为SpringBoot
没有显示创建ApplicationContext
的代码,我们只能从入口寻找。
一般情况下,我们是这么启动SpringBoot
项目的:
1 2 3 4 5 6 7 8 @SpringBootApplication public class Application { public static void main (String[] args) { SpringApplication.run(Application.class, args); } }
SpringBoot
再神通广大,终归是用Java
实现的,这个main
方法我们再熟悉不过了,里面只调用了一个方法,即SpringApplication#run
,那么秘密一定在里面。
run
方法里面调用了自己的重载方法,然后又调用了另一个重载方法,直到
1 2 3 4 public static ConfigurableApplicationContext run (Class<?>[] primarySources, String[] args) { return new SpringApplication (primarySources).run(args); }
我们继续进入其run
方法看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch (); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList <>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments ( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class [] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this .logStartupInfo) { new StartupInfoLogger (this .mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException (ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null ); throw new IllegalStateException (ex); } return context; }
这个方法代码很长,但我们的目的是找到ApplicationContext
的实现类,所以我们不关心其他逻辑,在这里寻找ApplicationContext
。
很容易在第四行就找到ConfigurableApplicationContext
类型的声明,可惜这里只有接口,没有实现类,但是通过context
变量,我们可以很容易找到赋值的代码,第16行。从调用的方法名也可以确认,就是在createApplicationContext
中创建了上下文,我们看一下其实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 protected ConfigurableApplicationContext createApplicationContext () { Class<?> contextClass = this .applicationContextClass; if (contextClass == null ) { try { switch (this .webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break ; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break ; default : contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException ( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass" , ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
不出所料,在createApplicationContext
方法中判断了应用环境选择了对应的上下文类型,并且将其实例化,也可以很容易发现,这里三个分支所对应的三个类,均继承自GenericApplicationContext
。
到这里,我们终于在代码中找到两种方式启动的差异点,我们看一下GenericApplicationContext#refreshBeanFactory
方法的实现
1 2 3 4 5 6 7 protected final void refreshBeanFactory () throws IllegalStateException { if (!this .refreshed.compareAndSet(false , true )) { throw new IllegalStateException ( "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once" ); } this .beanFactory.setSerializationId(getId()); }
可以说是几乎什么也没做,只设置了一个序列化id。完全找不到之前看的那些加载BeanDefinition
的代码。至此可以确定,SpringBoot
项目的加载BeanDefinition
过程并不在此处。
SpringBoot 加载 BeanDefinition的入口 那么SpringBoot
具体是通过什么方式方式加载项目中的所有BeanDefinition
的呢?
其实SpringBoot
中,是通过@Configuration
注解来作为所有配置的入口标记的,例如上文中的例子,Application
类被添加了@SpringBootApplication
注解,而@SpringBootApplication
有被@SpringBootConfiguration
注解标记,而@SpringBootConfiguration
正是@Configuration
的子类。
那么具体是在哪里提供了扫描@Configuration
注解并加载所有配置的功能,在这里就不卖关子了,直接介绍本文的主角 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 我们先简单看一下这个类的继承关系,发现它继承自BeanDefinitionRegistryPostProcessor
,关于这个类在Spring支持的扩展接口 一文中介绍过,其中介绍postProcessBeanDefinitionRegistry
方法内容如下:
该方法在所有BeanDefinition
被加载完成后,BeanFactoryPostProcessor#postProcessBeanFactory
之前被调用。
可以在此处修改已加载的BeanDefinition
,或添加自定义的BeanDefinition
,来实现动态注册Bean
;也可以在此方法中注册其他BeanDefinitionRegistryPostProcessor
,但如果当前Bean也是被其他BeanDefinitionRegistryPostProcessor#postProcessBeanFactory
加载的,那么当前Bean
加载的BeanDefinitionRegistryPostProcessor
将没有机会被执行。
这个方法功能正好符合我们对ConfigurationClassPostProcessor
这个类功能的期待,那么接下来看ConfigurationClassPostProcessor#postProcessBeanFactory
的实现是否符合预期。
还是按照惯例贴上包括简单注释的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); }
发现这个方法中没有太多秘密,只是做了一个简单的检查,最终调用了processConfigBeanDefinitions
方法,深入看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public void processConfigBeanDefinitions (BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList <BeanDefinitionHolder>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { 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)); } } if (configCandidates.isEmpty()) { return ; } Collections.sort(configCandidates, new Comparator <BeanDefinitionHolder>() { @Override public int compare (BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0 ; } }); SingletonBeanRegistry sbr = null ; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this .localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); this .componentScanBeanNameGenerator = generator; this .importBeanNameGenerator = generator; } } ConfigurationClassParser parser = new ConfigurationClassParser ( this .metadataReaderFactory, this .problemReporter, this .environment, this .resourceLoader, this .componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet <BeanDefinitionHolder>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet <ConfigurationClass>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet <ConfigurationClass>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); 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 <String>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet <String>(); 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()); if (sbr != null ) { if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } } if (this .metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this .metadataReaderFactory).clearCache(); } }
读者可以先通过代码外加笔者加的的中文注释对这个方法有个大致的理解,其实可以很容易发现这个方法正是用来处理@Configuration
注解的。
简单总结一下这个方法中的流程:
从当前已注册的BeanDefinition
中找出有@Configuration
的类作为候选集
排序
遍历候选集
解析
加载解析完的BeanDefinition
如果有新的BeanDefinition
被加载,需要判断其是否被@Configuration
标记,如果是则加入候选集
在执行这个方法前,被@SpringBootApplication
标记的类(即项目的入口类)的BeanDefinition
已经被注册到了Spring
中,所以在第一步中就会被加入到候选集中。
在接下来的步骤解析和加载BeanDefinition
时,会通过特定的规则进行扫描,详细内容后文继续介绍,主要是55行的ConfigurationClassParser#parse
和70行的ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
两个方法。
解析配置类 上文提到过,ConfigurationClassPostProcessor
在处理BeanDefinition
时,需要进行解析操作,而解析操作是委托给ConfigurationClassParser
类处理的,这个类的作用通过类名就可以知道,就是用来解析配置类的,我们看一下其parse
方法的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void parse (Set<BeanDefinitionHolder> configCandidates) { this .deferredImportSelectors = new LinkedList <DeferredImportSelectorHolder>(); 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); } } processDeferredImportSelectors(); }
parse
方法依次遍历了所有的候选配置类,然后根据类型的不同,调用了不同的解析方法,其实三个分支最终都是调用了同一个实现方法,只是要根据类型不同做不同的准备。通过追踪代码,可以发现其最终调用的是processConfigurationClass
方法。
接下来单独分析一下processConfigurationClass
和processDeferredImportSelectors
两个方法。
processConfigurationClass 先贴上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 protected void processConfigurationClass (ConfigurationClass configClass) throws IOException { 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); } return ; } else { this .configurationClasses.remove(configClass); for (Iterator<ConfigurationClass> it = this .knownSuperclasses.values().iterator(); it.hasNext(); ) { if (configClass.equals(it.next())) { it.remove(); } } } } SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null ); this .configurationClasses.put(configClass, configClass); }
这个方法主要做了三件事:
第一判断当前类是否需要解析,判断委托给了ConditionEvaluator
类进行处理,这个类型是根据当前类的@Conditional
注解进行处理的,关于这个注解的使用如果有不了解的读者可以自行学习一下,其用法与实现与本文主要内容无关,在此不再展开叙述。
第二是判断当前类是否已经被加载过,如果是被@Import
依赖的,那么记录一下就直接返回不重复处理了;如果不是被@Import
依赖的,那么就再解析一遍(会把上一次的解析结果覆盖)。
第三就是具体解析的调用,回调用doProcessConfigurationClass
方法进行处理,可以发现这个方法被一个循环所包围,因为方法的会返回当前类型的父类,如果其父类存在,则会循环解析,知道不存在父类时,会返回null
。
那么看一下doProcessConfigurationClass
方法的实现,这个方法基本已经到了本文的核心逻辑,读者先根据注释对其有大概了解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 protected final SourceClass doProcessConfigurationClass (ConfigurationClass configClass, SourceClass sourceClass) throws IOException { processMemberClasses(configClass, sourceClass); 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" ); } } 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) { Set<BeanDefinitionHolder> scannedBeanDefinitions = this .componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 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()); } } } } processImports(configClass, sourceClass, getImports(sourceClass), true ); if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); 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); } } Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod (methodMetadata, configClass)); } processInterfaces(configClass, sourceClass); if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java" ) && !this .knownSuperclasses.containsKey(superclass)) { this .knownSuperclasses.put(superclass, configClass); return sourceClass.getSuperClass(); } } return null ; }
第三行调用processMemberClasses
方法用来处理当前类的内部类,获取当前类的内部类,并循环递归调用processConfigurationClass
方法。这个方法的具体实现感兴趣的读者可自行阅读,比较简单不再展开。
上文说到当前方法是本文的核心方法,是因为可以发现该方法的代码中,依次处理了配置类中所需要处理的五个注解,下文只会大概介绍其处理的原理,有些细节不再展开。
处理@PropertySource @PropertySource
用来实现将指定的配置文件加载到当前Spring
环境中
处理@ComponentScan @ComponentScan
的作用是自动扫描指定包中的所有类,并根据其是否有特定注解(例如@Service
、@Component
等)将其转化为BeanDefinition
加载当上下文中。
在此处的实现方式是将注解中配置的包路径依次委托给ClassPathBeanDefinitionScanner
进行处理,关于ClassPathBeanDefinitionScanner
已经在Spring常用工具 一文中大致介绍过,不再赘述。
在这一步如果解析到了新的BeanDefinition
且使用了@Configuration
注解,直接调用parse
方法进行递归解析。
处理@Import @Import
可以将其他类引入当前上下文中,在该方法中,先通过getImports
方法解析需要导入的类型,再调用processImports
方法处理这些类型
先贴一下processImports
的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 { this .importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this .environment, this .resourceLoader, this .registry); if (this .deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this .deferredImportSelectors.add( new DeferredImportSelectorHolder (configClass, (DeferredImportSelector) selector)); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false ); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this .environment, this .resourceLoader, this .registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { this .importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException ( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]" , ex); } finally { this .importStack.pop(); } } }
在processImports
方法中,循环处理每个需要导入的类型,根据其类型分为三种处理方式:
如果导入的类实现了ImportSelector
接口:ImportSelector
是一个动态导入接口,可以实现其selectImports
方法,在该方法中根据条件返回最终需要导入的类。而在当前方法的实现则是会实例化这个ImportSelector
子类,调用其selectImports
方法获取需要导入的类型,并递归调用processImports
方法。
不过此处有个例外,如果导入类实现了DeferredImportSelector
接口,则不会在此处直接调用其selectImports
方法,而会延迟调用,在此处只是进行记录,具体调用时机会在后文中提到。
如果导入类实现了ImportBeanDefinitionRegistrar
接口:关于ImportBeanDefinitionRegistrar
在Spring支持的扩展接口 中介绍过,也提到了该类是由@Import
引入并可使用的。具体实现是在此处将其实例化,并调用了几个Aware
接口,再将其暂存起来,后文会再次提到。
如果不是上述的两种接口的子类:将该类当做普通的@Configuration注解的类处理,递归解析。
处理@ImportResource 使用@ImportResource
可以将xml
类型的配置导入并解析到当前项目中,但是在此处并没有真正进行解析,也将其暂存起来,在后面会统一处理。
处理@Bean @Bean
是通过注解方式进行Bean
定义最常用的方式,在此处扫描所有加了@Bean
注解的方法并将其暂存,后面统一处理。
处理DeferredImportSelectors接口实现 上文提到,对于通过@Import
导入的DeferredImportSelector
子类需要延迟处理,正是在该方法中进行加载,处理逻辑基本与处理ImportSelector
一致,不再赘述
加载BeanDefinition 读者需要把思路重新回到ConfigurationClassBeanDefinitionReader#processConfigBeanDefinitions
方法中,前文我们已经把ConfigurationClassParser#parse
方法分析完了,接下来就是ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
方法。
这里调用的方法名称为loadBeanDefinitions
,直译过来就是加载BeanDefinition
,但其实根据上面的阅读可以发现,前面解析时已经加载了很多BeanDefinition
了,但是对于有些情况只做了记录,没有真正进行加载,而处理这些工作,正是ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
做的事情。
贴一下代码:
1 2 3 4 5 6 7 public void loadBeanDefinitions (Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator (); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
这个方法很简单,遍历一下候选集,然后依次调用loadBeanDefinitionsForConfigurationClass
方法,追踪到里面看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 ; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
上面代码注释里面其实已经写得很清晰了,此处就是把上面遗留下来的几件事处理完成,分别是:
将被@Import
引入的类自身注册成BeanDefinition
将被@Bean
注解的方法解析成BeanDefinition
并注册
加载被@ImportResource
依赖的配置文件
被@Import
导入的ImportBeanDefinitionRegistrar
类在此处处理
其中配置文件的加载,是被委托给对应的BeanDefinitionReader
加载的,例如xml
文件被委托给XmlBeanDefinitionReader
处理,这个过程与传统的Spring
项目的启动时加载配置文件的过程是一样的。
对于ImportBeanDefinitionRegistrar
子类的处理过程是依次调用了其registerBeanDefinitions
方法,而其子类可以在这个方法中动态加载BeanDefinition
。
总结 至此,本文已经大致把SpringBoot
中加载BeanDefinition
的过程基本理了一遍,跟之前的源码分析风格一致,没有深入一些细节进行分析,尤其是本文的目的是介绍跟传统Spring
容器加载BeanDefinition
的差异,所以一些类似的功能就也没必要着重讲了。