代码入口
之前写文章都会啰啰嗦嗦一大堆再开始,进入【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这个类的继承关系:
大致的继承关系是如上图所示的,由于版面的关系,没有继续画下去了,左下角的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的继承关系图:
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
/** 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这个抽象类的定义:
这个类的属性与方法很多,这里就列举了一些最主要的方法和属性,可以看到包含了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。