为什么要强调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的差异,所以一些类似的功能就也没必要着重讲了。