BeanDefinition注册-下篇
前言
接着上文,此文主要讲了import和alias标签的解析,另外补充IOC容器注册的一些细节。
importBeanDefinitionResource
处理import标签大致步骤:
获取resource配置的地址
按照《BeanDefinition资源加载》中的方法解析资源
发送一个ImportProcessed事件。
protected void importBeanDefinitionResource(Element ele) {
//获取 resource 属性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
//如果resource是表达式的话,就解析成普通的路径
location = getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
//确认resource是不是绝对路径
boolean absoluteLocation = false;
try {
//判断是不是绝对路径
//如果以classpath*: 或 classpath: 开头就是绝对路径
//或一个绝对的URL
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
//如果是绝对路径,就进入
if (absoluteLocation) {
try {
//这一步直接调用了XmlBeanDefinitionReader.loadBeanDefinitions方法,所以就是重复之前的步骤,这里不再赘述。
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
// 相对路径的处理策略
try {
int importCount;
//首先获取按照相对路径去获取资源
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//如果存在,还是和上面一样进行解析
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
//如果获取相对路径的资源失败,就使用URL获取相对路径资源
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
//把成功加载的资源,放置到actualResources中
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
//并分发一个ImportProcessed事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
processAliasRegistration
处理alias标签,alias的处理比较简单,首先获取alias和name属性值,然后注册到IOC容器中,最后发送一个AliasRegistered事件
protected void processAliasRegistration(Element ele) {
//获取 name 和 alias 属性值
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
//判空
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//直接向IOC容器注册name 和 alias,具体注册,见下文
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
//发送一个AliasRegistered事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
BeanDefinition在IOC容器中的注册
IOC是怎么管理BeanDefinition?这就要从DefaultBeanDefinitionDocumentReader说起:
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
//调用了静态方法
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
//获取beanName
String beanName = definitionHolder.getBeanName();
//把beanName和BeanDefinition注册到容器中,下面继续探究
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
//下面继续探究别名的注册
registry.registerAlias(beanName, aliase);
}
}
}
BeanDefintion的注册
BeanFactory的默认实现是 DefaultLisableBeanFactory,Spring中大部分情况下也都是使用DefaulltLisableBeanFactory作为基本的IOC容器实现。
DefaultLisableBeanFactory 时BeanFactory等接口的集大成者,我会单独写一篇文章来讲解,暂定名称为《IOC默认容器-DefaultLisableBeanFactory》。
在此篇中,我只简单地分析下注册BeanDefinition这一方法
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");
//根据上文,我们此处获取的BeanDefinition的类型时GenericBeanDefinition,时AbstractBeanDefinition的子类。
//所以会进入此代码块
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//验证beanDefinition,只验证一项内容:beanDefinition不能同时具有overrideMethod和factoryMethod两个属性,如果两个都有,就验证失败
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
//创建一个临时变量,用于存放之前同名的beanDefinition(如果有的话)
BeanDefinition oldBeanDefinition;
synchronized (this.beanDefinitionMap) {
//从map中查询是否已经存在名为beanName的beanDefinition
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
//如果查到了,就有点意思了。
//Spring这里有一个标识位allowBeanDefinitionOverriding,表示是否允许BeanDefinition被覆盖。
//一般这个值都是false,除非特定的业务情境下,允许覆盖同名的BeanDefinition。
//如果allowBeanDefinitionOverriding是false的话,就抛出异常,表示beanName冲突了
//如果allowBeanDefinitionOverriding是true的话,就直接覆盖原来的beanDefinition
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 {
//如果没有同名的BeanDefinition,就把beanName放入到beanDefinitionNames集合中,
//这个beanDefinitionNames是一个Set,用于存储所有的BeanDefinition的name,并不是别名,主要是为了加速BeanDefinition的名称检查
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
//将beanDefinition放入到IOC中,这个beanDefinition至此终于纳入了SpringIOC的怀抱
this.beanDefinitionMap.put(beanName, beanDefinition);
}
//清理之前的oldBeanDefinition(如果有的话)
//清理单例(如果有的话)
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
Alias在IOC中的注册
IOC是怎么管理alias的
alias是全部放置在一个aliasMap中的进行管理的,别名作为key,真名作为value;允许多个别名对应同一个name。
但是要注意别名循环引用的问题,比如下表:
序号 别名 bean名称 1 alias_1 alias_2 2 alias_2 alias_1 这种引用实际是没有任何意义的,所以Spring需要剔除这些循环引用的别名。
//从readerContext中获取BeanDefinitionRegistry ,来注册别名
//这个BeanDefinitionRegistry也是DefaultLisableBeanFactory,但是DefaultLisableBeanFactory自己没有实现注册别名的方法,而是继承自SimpleAliasRegistry。
getReaderContext().getRegistry().registerAlias(name, alias);
// SimpleAliasRegistry.registerAlias
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
//如果name和别名一样,就不用注册别名了,所以从aliasMap中剔除此别名
if (alias.equals(name)) {
this.aliasMap.remove(alias);
}
else {
//查询spring属性allowBeanDefinitionOverriding,是否允许别名被覆盖
if (!allowAliasOverriding()) {
//查询此alias是否已经注册了
String registeredName = this.aliasMap.get(alias);
if (registeredName != null && !registeredName.equals(name)) {
throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
}
//检查别名是否循环引用,见下文
checkForAliasCircle(name, alias);
//没有循环引用,就放入到aliasMap中,至此一个bean的别名就纳入到SpringIOC管理了。
this.aliasMap.put(alias, name);
}
}
checkForAliasCircle 检查别名循环引用
别名的循环引用,会造成死循环并且无法创建出一个有用的Bean,所以Spring一定要避免出现这种情况。
protected void checkForAliasCircle(String name, String alias) {
//如果alias和规范名称相同,说明别名发生了循环引用,抛出异常就完事了
if (alias.equals(canonicalName(name))) {
throw new IllegalStateException("Cannot register alias '" + alias +
"' for name '" + name + "': Circular reference - '" +
name + "' is a direct or indirect alias for '" + alias + "' already");
}
}
//获取相应的规范名称,如果是别名就取出真正的bean名称
public String canonicalName(String name) {
String canonicalName = name;
// Handle aliasing...
String resolvedName;
//这个while循环,就是一直在aliasMap中查找,直到获取到真正的beanName,并把这个值存到canonicalName中
//最后这个canonicalName就是name对应的规范名称
do {
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}
解析流程看下图
查找name真正的名称就是一个“蛇形走位”的过程,通过不断地切换resolvedName和canonicalName地值,在aliasMap中不断地反复横跳,最终找到了真实名称。