Spring源码分析:Bean加载流程概览及配置文件读取

代码入口

之前写文章都会啰啰嗦嗦一大堆再开始,进入【Spring源码分析】这个板块就直接切入正题了。

很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已,Spring的加载过程相对是不太透明的,不太好去找加载的代码入口。

下面有很简单的一段代码可以作为Spring代码加载的入口:

1 2 ApplicationContext ac = new ClassPathXmlApplicationContext( "spring.xml" ); ac.getBean(XXX. class );

ClassPathXmlApplicationContext用于加载CLASSPATH下的Spring配置文件,可以看到,第二行就已经可以获取到Bean的实例了,那么必然第一行就已经完成了对所有Bean实例的加载,因此可以通过ClassPathXmlApplicationContext作为入口。为了后面便于代码阅读,先给出一下ClassPathXmlApplicationContext这个类的继承关系:《Spring源码分析:Bean加载流程概览及配置文件读取》

大致的继承关系是如上图所示的,由于版面的关系,没有继续画下去了,左下角的ApplicationContext应当还有一层继承关系,比较关键的一点是它是BeanFactory的子接口。

最后声明一下,本文使用的Spring版本为3.0.7,比较老,使用这个版本纯粹是因为公司使用而已。

ClassPathXmlApplicationContext构造函数

看下ClassPathXmlApplicationContext的构造函数:

1 public ClassPathXmlApplicationContext(String configLocation) throws BeansException { 2     this ( new String[] {configLocation}, true , null ); 3 }
1 2 3 4 5 6 7 8 9 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)          throws BeansException {        super (parent);      setConfigLocations(configLocations);      if (refresh) {          refresh();      } }

从第二段代码看,总共就做了三件事:

1、super(parent)

没什么太大的作用,设置一下父级ApplicationContext,这里是null

2、setConfigLocations(configLocations)

代码就不贴了,一看就知道,里面做了两件事情:

(1)将指定的Spring配置文件的路径存储到本地

(2)解析Spring配置文件路径中的${PlaceHolder}占位符,替换为系统变量中PlaceHolder对应的Value值,System本身就自带一些系统变量比如class.path、os.name、user.dir等,也可以通过System.setProperty()方法设置自己需要的系统变量

3、refresh()

这个就是整个Spring Bean加载的核心了,它是ClassPathXmlApplicationContext的父类AbstractApplicationContext的一个方法,顾名思义,用于刷新整个Spring上下文信息,定义了整个Spring上下文加载的流程。

refresh方法

上面已经说了,refresh()方法是整个Spring Bean加载的核心,因此看一下整个refresh()方法的定义:

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 public void refresh() throws BeansException, IllegalStateException {          synchronized ( this .startupShutdownMonitor) {              // Prepare this context for refreshing.              prepareRefresh();                // Tell the subclass to refresh the internal bean factory.              ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();                // Prepare the bean factory for use in this context.              prepareBeanFactory(beanFactory);                try {                  // Allows post-processing of the bean factory in context subclasses.                  postProcessBeanFactory(beanFactory);                    // Invoke factory processors registered as beans in the context.                  invokeBeanFactoryPostProcessors(beanFactory);                    // Register bean processors that intercept bean creation.                  registerBeanPostProcessors(beanFactory);                    // Initialize message source for this context.                  initMessageSource();                    // Initialize event multicaster for this context.                  initApplicationEventMulticaster();                    // Initialize other special beans in specific context subclasses.                  onRefresh();                    // Check for listener beans and register them.                  registerListeners();                    // Instantiate all remaining (non-lazy-init) singletons.                  finishBeanFactoryInitialization(beanFactory);                    // Last step: publish corresponding event.                  finishRefresh();              }                catch (BeansException ex) {                  // Destroy already created singletons to avoid dangling resources.                  destroyBeans();                    // Reset 'active' flag.                  cancelRefresh(ex);                    // Propagate exception to caller.                  throw ex;              }          }      }

每个子方法的功能之后一点一点再分析,首先refresh()方法有几点是值得我们学习的:

1、方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文

2、尽管加锁可以看到是针对整个方法体的,但是没有在方法前加synchronized关键字,而使用了对象锁startUpShutdownMonitor,这样做有两个好处:

(1)refresh()方法和close()方法都使用了startUpShutdownMonitor对象锁加锁,这就保证了在调用refresh()方法的时候无法调用close()方法,反之亦然,避免了冲突

(2)另外一个好处不在这个方法中体现,但是提一下,使用对象锁可以减小了同步的范围,只对不能并发的代码块进行加锁,提高了整体代码运行的效率

3、方法里面使用了每个子方法定义了整个refresh()方法的流程,使得整个方法流程清晰易懂。这点是非常值得学习的,一个方法里面几十行甚至上百行代码写在一起,在我看来会有三个显著的问题:

(1)扩展性降低。反过来讲,假使把流程定义为方法,子类可以继承父类,可以根据需要重写方法

(2)代码可读性差。很简单的道理,看代码的人是愿意看一段500行的代码,还是愿意看10段50行的代码?

(3)代码可维护性差。这点和上面的类似但又有不同,可维护性差的意思是,一段几百行的代码,功能点不明确,不易后人修改,可能会导致“牵一发而动全身”

prepareRefresh方法

下面挨个看refresh方法中的子方法,首先是prepareRefresh方法,看一下源码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 /**   * Prepare this context for refreshing, setting its startup date and   * active flag.   */ protected void prepareRefresh() {      this .startupDate = System.currentTimeMillis();          synchronized ( this .activeMonitor) {          this .active = true ;      }        if (logger.isInfoEnabled()) {          logger.info( "Refreshing " + this );      } }

这个方法功能比较简单,顾名思义,准备刷新Spring上下文,其功能注释上写了:

1、设置一下刷新Spring上下文的开始时间

2、将active标识位设置为true

另外可以注意一下12行这句日志,这句日志打印了真正加载Spring上下文的Java类。

obtainFreshBeanFactory方法

obtainFreshBeanFactory方法的作用是获取刷新Spring上下文的Bean工厂,其代码实现为:

1 2 3 4 5 6 7 8 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {      refreshBeanFactory();      ConfigurableListableBeanFactory beanFactory = getBeanFactory();      if (logger.isDebugEnabled()) {          logger.debug( "Bean factory for " + getDisplayName() + ": " + beanFactory);      }      return beanFactory; }

其核心是第二行的refreshBeanFactory方法,这是一个抽象方法,有AbstractRefreshableApplicationContext和GenericApplicationContext这两个子类实现了这个方法,看一下上面ClassPathXmlApplicationContext的继承关系图即知,调用的应当是AbstractRefreshableApplicationContext中实现的refreshBeanFactory,其源码为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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);      } }

这段代码的核心是第7行,这行点出了DefaultListableBeanFactory这个类,这个类是构造Bean的核心类,这个类的功能会在下一篇文章中详细解读,首先给出DefaultListableBeanFactory的继承关系图:

《Spring源码分析:Bean加载流程概览及配置文件读取》

AbstractAutowireCapableBeanFactory这个类的继承层次比较深,版面有限,就没有继续画下去了,本图基本上清楚地展示了DefaultListableBeanFactory的层次结构。

为了更清晰地说明DefaultListableBeanFactory的作用,列举一下DefaultListableBeanFactory中存储的一些重要对象及对象中的内容,DefaultListableBeanFactory基本就是操作这些对象,以表格形式说明:

 对象名类  型 作    用归属类
 aliasMapMap<String, String>存储Bean名称->Bean别名映射关系 SimpleAliasRegistry
singletonObjects Map<String, Object> 存储单例Bean名称->单例Bean实现映射关系DefaultSingletonBeanRegistry 
 singletonFactories Map<String, ObjectFactory>存储Bean名称->ObjectFactory实现映射关系DefaultSingletonBeanRegistry
earlySingletonObjects Map<String, Object>存储Bean名称->预加载Bean实现映射关系 DefaultSingletonBeanRegistry
registeredSingletonsSet<String>存储注册过的Bean名 DefaultSingletonBeanRegistry
singletonsCurrentlyInCreationSet<String>存储当前正在创建的Bean名  DefaultSingletonBeanRegistry
 disposableBeans Map<String, Object>存储Bean名称->Disposable接口实现Bean实现映射关系   DefaultSingletonBeanRegistry
 factoryBeanObjectCache Map<String, Object>存储Bean名称->FactoryBean接口Bean实现映射关系FactoryBeanRegistrySupport
propertyEditorRegistrars Set<PropertyEditorRegistrar>存储PropertyEditorRegistrar接口实现集合AbstractBeanFactory
 embeddedValueResolversList<StringValueResolver>存储StringValueResolver(字符串解析器)接口实现列表AbstractBeanFactory
beanPostProcessorsList<BeanPostProcessor>存储 BeanPostProcessor接口实现列表AbstractBeanFactory
mergedBeanDefinitionsMap<String, RootBeanDefinition>存储Bean名称->合并过的根Bean定义映射关系AbstractBeanFactory
 alreadyCreatedSet<String>存储至少被创建过一次的Bean名集合 AbstractBeanFactory
ignoredDependencyInterfacesSet<Class>存储不自动注入的Class集合AbstractAutowireCapableBeanFactory
 resolvableDependenciesMap<Class, Object>存储修正过的依赖映射关系DefaultListableBeanFactory
beanDefinitionMapMap<String, BeanDefinition>存储Bean名称–>Bean定义映射关系DefaultListableBeanFactory
beanDefinitionNamesList<String>存储Bean定义名称列表 DefaultListableBeanFactory


XML文件解析

另外一个核心是第10行,loadBeanDefinitions(beanFactory)方法,为什么我们配置的XML文件最后能转成Java Bean,首先就是由这个方法处理的。该方法最终的目的是将XML文件进行解析,以Key-Value的形式,Key表示BeanName,Value为BeanDefinition,最终存入DefaultListableBeanFactory中:

1 /** Map of bean definition objects, keyed by bean name */ 2 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); 3 4 /** List of bean definition names, in registration order */ 5 private final List<String> beanDefinitionNames = new ArrayList<String>();

最终DefaultListableBeanFactory会先遍历beanDefinitionNames,从beanDefinitionMap中拿到对应的BeanDefinition,最终转为具体的Bean对象。BeanDefinition本身是一个接口,AbstractBeanDefinition这个抽象类存储了Bean的属性,看一下AbstractBeanDefinition这个抽象类的定义:

《Spring源码分析:Bean加载流程概览及配置文件读取》

这个类的属性与方法很多,这里就列举了一些最主要的方法和属性,可以看到包含了bean标签中的所有属性,之后就是根据AbstractBeanDefinition中的属性值构造出对应的Bean对象。

Spring没有直接拿到XML中的bean定义就直接转为具体的Bean对象,就是给Spring开发者留下了扩展点,比如之前BeanPostProcessor,在最后一部分会简单提及。接着看一下XML是如何转为Bean的,首先在AbstractXmlApplicationContext中将DefaultListableBeanFactory转换为XmlBeanDefinitionReader:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {      // Create a new XmlBeanDefinitionReader for the given BeanFactory.      XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);        // Configure the bean definition reader with this context's      // resource loading environment.      beanDefinitionReader.setResourceLoader( this );      beanDefinitionReader.setEntityResolver( new ResourceEntityResolver( this ));        // Allow a subclass to provide custom initialization of the reader,      // then proceed with actually loading the bean definitions.      initBeanDefinitionReader(beanDefinitionReader);      loadBeanDefinitions(beanDefinitionReader); }

XmlBeanDefinitionReader顾名思义,一个XML文件中读取Bean定义的工具,然后追踪13行的代码,先追踪到DefaultBeanDefinitionDocumentReader的parseDefaultElement方法:

1 2 3 4 5 6 7 8 9 10 11 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {      if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {          importBeanDefinitionResource(ele);      }      else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {          processAliasRegistration(ele);      }      else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {          processBeanDefinition(ele, delegate);      } }

XML文件的节点import、alias、bean分别有自己对应的方法去处理,以最常见的Bean为例,即第9行:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {      BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);      if (bdHolder != null ) {          bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);          try {              // Register the final decorated instance.              BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());          }          catch (BeanDefinitionStoreException ex) {              getReaderContext().error( "Failed to register bean definition with name '" +                      bdHolder.getBeanName() + "'" , ele, ex);          }          // Send registration event.          getReaderContext().fireComponentRegistered( new BeanComponentDefinition(bdHolder));      } }

核心的解析bean节点的代码为第2行,这里已经调用到了BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法,看下是怎么做的:

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 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {      String id = ele.getAttribute(ID_ATTRIBUTE);      String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);        List<String> aliases = new ArrayList<String>();      if (StringUtils.hasLength(nameAttr)) {          String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);          aliases.addAll(Arrays.asList(nameArr));      }        String beanName = id;      if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {          beanName = aliases.remove( 0 );          if (logger.isDebugEnabled()) {              logger.debug( "No XML 'id' specified - using '" + beanName +                      "' as bean name and " + aliases + " as aliases" );          }      }        if (containingBean == null ) {          checkNameUniqueness(beanName, aliases, ele);      }        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);      if (beanDefinition != null ) {          if (!StringUtils.hasText(beanName)) {              try {                  if (containingBean != null ) {                      beanName = BeanDefinitionReaderUtils.generateBeanName(                              beanDefinition, this .readerContext.getRegistry(), true );                  }                  else {                      beanName = this .readerContext.generateBeanName(beanDefinition);                      // Register an alias for the plain bean class name, if still possible,                      // if the generator returned the class name plus a suffix.                      // This is expected for Spring 1.2/2.0 backwards compatibility.                      String beanClassName = beanDefinition.getBeanClassName();                      if (beanClassName != null &&                                  beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&                                  ! this .readerContext.getRegistry().isBeanNameInUse(beanClassName)) {                          aliases.add(beanClassName);                      }                  }                  if (logger.isDebugEnabled()) {                      logger.debug( "Neither XML 'id' nor 'name' specified - " +                              "using generated bean name [" + beanName + "]" );                  }              }              catch (Exception ex) {                  error(ex.getMessage(), ele);                  return null ;              }          }          String[] aliasesArray = StringUtils.toStringArray(aliases);          return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);      }        return null ; }

总结一下代码逻辑:

(1)第2行和第3行,获取id属性和name属性

(2)第5行~第9行,如果填写了name属性的话,将name属性以”,;”,分割出来的字符串全部认为这个bean的别名,这里我们可以学到Spring的StringUtils的tokenizeToStringArray方法,可以将字符串按照指定分割符分割为字符串数组

(3)第11行~第18行,默认beanName为id属性,如果bean有配置别名(即上面的name属性的话),以name属性的第一个值作为beanName,发现很多人不知道beanName是什么,这几行代码就表示了容器是如何定义beanName的

(4)第20行~第22行,这段用于保证beanName的唯一性的,BeanDefinitionParserDelegate中有一个属性usedNames,这是一个Set,强制性地保证了beanName的唯一性

(5)第24行用于解析bean的其他属性,后面的代码不太重要,看一下parseBeanDefinitionElement的实现

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 public AbstractBeanDefinition parseBeanDefinitionElement(          Element ele, String beanName, BeanDefinition containingBean) {        this .parseState.push( new BeanEntry(beanName));        String className = null ;      if (ele.hasAttribute(CLASS_ATTRIBUTE)) {          className = ele.getAttribute(CLASS_ATTRIBUTE).trim();      }        try {          String parent = null ;          if (ele.hasAttribute(PARENT_ATTRIBUTE)) {              parent = ele.getAttribute(PARENT_ATTRIBUTE);          }          AbstractBeanDefinition bd = createBeanDefinition(className, parent);            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);          bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));            parseMetaElements(ele, bd);          parseLookupOverrideSubElements(ele, bd.getMethodOverrides());          parseReplacedMethodSubElements(ele, bd.getMethodOverrides());            parseConstructorArgElements(ele, bd);          parsePropertyElements(ele, bd);          parseQualifierElements(ele, bd);          bd.setResource( this .readerContext.getResource());          bd.setSource(extractSource(ele));            return bd;      }      catch (ClassNotFoundException ex) {          error( "Bean class [" + className + "] not found" , ele, ex);      }      catch (NoClassDefFoundError err) {          error( "Class that bean class [" + className + "] depends on not found" , ele, err);      }      catch (Throwable ex) {          error( "Unexpected failure during bean definition parsing" , ele, ex);      }      finally {          this .parseState.pop();      }        return null ; }

这里会取class属性、parent属性,18行的代码可以跟进去看一下这里就不贴了,会取得scope、lazy-init、abstract、depends-on属性等等,设置到BeanDefinition中,这样大致上,一个Bean的定义就被存入了BeanDefinition中。最后一步追溯到之前DefaultBeanDefinitionDocumentReader的processBeanDefinition方法:

1 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

这句语句将BeanDefinition存入DefaultListableBeanFactory的beanDefinitionMap中,追踪一下代码最终到DefaultListableBeanFactory的registerBeanDefinition方法中:

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 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)      throws BeanDefinitionStoreException {        Assert.hasText(beanName, "Bean name must not be empty" );      Assert.notNull(beanDefinition, "BeanDefinition must not be null" );        if (beanDefinition instanceof AbstractBeanDefinition) {          try {              ((AbstractBeanDefinition) beanDefinition).validate();          }          catch (BeanDefinitionValidationException ex) {              throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,                      "Validation of bean definition failed" , ex);          }      }        synchronized ( this .beanDefinitionMap) {          Object oldBeanDefinition = this .beanDefinitionMap.get(beanName);          if (oldBeanDefinition != null ) {              if (! this .allowBeanDefinitionOverriding) {                  throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,                          "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +                          "': There is already [" + oldBeanDefinition + "] bound." );              }              else {                  if ( this .logger.isInfoEnabled()) {                      this .logger.info( "Overriding bean definition for bean '" + beanName +                              "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]" );                  }              }          }          else {              this .beanDefinitionNames.add(beanName);              this .frozenBeanDefinitionNames = null ;          }          this .beanDefinitionMap.put(beanName, beanDefinition);            resetBeanDefinition(beanName);      } }

大致上就是beanDefinitionNames中增加一个beanName,beanDefinitionMap将老的BeanDefinition替换(假如不允许BeanDefinition重写的话会抛出异常)。这样一个漫长的流程过后,XML文件中的各个bean节点被转换为BeanDefinition,存入了DefaultListableBeanFactory中,后续DefaultListableBeanFactory可以根据BeanDefinition,构造对应的Bean对象出来。

<bean>中不定义id及id重复场景Spring的处理方式

这两天又想到了一个细节问题,<bean>中不定义id或者id重复,这两种场景Spring是如何处理的。首先看一下不定义id的场景,代码在BeanDefinitionParserDelegate类第398行的这个判断这里:

1 2 3 4 5 6 7 8 9 10 11 if (beanDefinition != null ) {      if (!StringUtils.hasText(beanName)) {          try {              if (containingBean != null ) {                  beanName = BeanDefinitionReaderUtils.generateBeanName(                          beanDefinition, this .readerContext.getRegistry(), true );              }              else {                  beanName = this .readerContext.generateBeanName(beanDefinition); ... }

当bean的id未定义时,即beanName为空,进入第2行的if判断。containingBean可以看一下,这里是由方法传入的,是一个null值,因此进入第9行的判断,即beanName由第9行的方法生成,看一下生成方式,代码最终要追踪到BeanDefinitionReaderUtils的generateBeanName方法:

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 public static String generateBeanName(          BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)          throws BeanDefinitionStoreException {        String generatedBeanName = definition.getBeanClassName();      if (generatedBeanName == null ) {          if (definition.getParentName() != null ) {              generatedBeanName = definition.getParentName() + "$child" ;          }          else if (definition.getFactoryBeanName() != null ) {              generatedBeanName = definition.getFactoryBeanName() + "$created" ;          }      }      if (!StringUtils.hasText(generatedBeanName)) {          throw new BeanDefinitionStoreException( "Unnamed bean definition specifies neither " +                  "'class' nor 'parent' nor 'factory-bean' - can't generate bean name" );      }        String id = generatedBeanName;      if (isInnerBean) {          // Inner bean: generate identity hashcode suffix.          id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);      }      else {          // Top-level bean: use plain class name.          // Increase counter until the id is unique.          int counter = - 1 ;          while (counter == - 1 || registry.containsBeanDefinition(id)) {              counter++;              id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;          }      }      return id; }

这段代码的逻辑很容易看懂,即:

  • 假如是innerBean(比如Spring AOP产生的Bean),使用【类全路径+#+对象HashCode的16进制】的格式来命名Bean
  • 假如不是innerBean,使用【类全路径+#+数字】的格式来命名Bean,其中数字指的是,同一个Bean出现1次,只要该Bean没有id,就从0开始依次向上累加,比如a.b.c#0、a.b.c#1、a.b.c#2

接着看一下id重复的场景Spring的处理方式,重复id是这样的,Spring使用XmlBeanDefinitionReader读取xml文件,在这个类的doLoadBeanDefinitions的方法中:

1 2 3 4 5 6 7 8 9 10 11 12 13 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)      throws BeanDefinitionStoreException {      try {          int validationMode = getValidationModeForResource(resource);          Document doc = this .documentLoader.loadDocument(                  inputSource, getEntityResolver(), this .errorHandler, validationMode, isNamespaceAware());          return registerBeanDefinitions(doc, resource);      }      catch (BeanDefinitionStoreException ex) {          throw ex;      }      ... }

第5行的代码将xml解析成Document,这里的解析使用的是JDK自带的DocumentBuilder,DocumentBuilder处理xml文件输入流,发现两个<bean>中定义的id重复即会抛出XNIException异常,最终将导致Spring容器启动失败。

因此,结论就是:Spring不允许两个<bean>定义相同的id

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_36090419/article/details/70477516
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞