天天看點

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

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基本就是操作這些對象,以表格形式說明:

 對象名 類  型  作    用 歸屬類
 aliasMap Map<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
registeredSingletons Set<String> 存儲注冊過的Bean名  DefaultSingletonBeanRegistry
singletonsCurrentlyInCreation Set<String> 存儲目前正在建立的Bean名   DefaultSingletonBeanRegistry
 disposableBeans  Map<String, Object> 存儲Bean名稱->Disposable接口實作Bean實作映射關系    DefaultSingletonBeanRegistry
 factoryBeanObjectCache  Map<String, Object> 存儲Bean名稱->FactoryBean接口Bean實作映射關系 FactoryBeanRegistrySupport
propertyEditorRegistrars  Set<PropertyEditorRegistrar> 存儲PropertyEditorRegistrar接口實作集合 AbstractBeanFactory
 embeddedValueResolvers List<StringValueResolver> 存儲StringValueResolver(字元串解析器)接口實作清單 AbstractBeanFactory
beanPostProcessors List<BeanPostProcessor> 存儲 BeanPostProcessor接口實作清單 AbstractBeanFactory
mergedBeanDefinitions Map<String, RootBeanDefinition> 存儲Bean名稱->合并過的根Bean定義映射關系 AbstractBeanFactory
 alreadyCreated Set<String> 存儲至少被建立過一次的Bean名集合  AbstractBeanFactory
ignoredDependencyInterfaces Set<Class> 存儲不自動注入的Class集合 AbstractAutowireCapableBeanFactory
 resolvableDependencies Map<Class, Object> 存儲修正過的依賴映射關系 DefaultListableBeanFactory
beanDefinitionMap Map<String, BeanDefinition> 存儲Bean名稱–>Bean定義映射關系 DefaultListableBeanFactory
beanDefinitionNames List<String> 存儲Bean定義名稱清單  DefaultListableBeanFactory

XML檔案解析

另外一個核心是第10行,loadBeanDefinitions(beanFactory)方法,為什麼我們配置的XML檔案最後能轉成Java Bean,首先就是由這個方法處理的。該方法最終的目的是将XML檔案進行解析,以Key-Value的形式,Key表示BeanName,Value為BeanDefinition,最終存入DefaultListableBeanFactory中:

1

2

private

final

Map<String, BeanDefinition> beanDefinitionMap =

new

ConcurrentHashMap<String, BeanDefinition>();

3

4

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(

);

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。