三.Spring默认标签的解析

默认标签解析入口方法:

private void parseDefaultElement(Element ele,BeanDefinitionParserDelegate dalegate){
	if(delegate.nodeNameEquals(ele,IMPORT_ELEMENT)){
		//说明是import变迁
		importBeanDefinitionResource(ele);
	}
	//剩下的代码类似
	//对alias、bean、beans变迁的解析
}

1.默认标签之bean标签的解析

入口代码:

protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate){
	//返回BeanDefinitionHolder 
	//BeanDefinitionHolder 里面已经包含了bean标签内所有的属性
	BeanDefinitionHolder holder = delegate.parseBeanDefinitionElement(ele);
	if(holder != null){
		//如果有自定义子标签,那么这句代码就起作用了,继续解析自定义标签
		holder =delegate.decorateBeanDefinitionIfRequired(ele,holder);
	}
	//注册bean
	BeanDefinitionReaderUtils.registerBeanDefinition(holder,getReaderContext());
	//通知监听器,bean已经完成解析
	getReaderContext().fireComponentRegistered(new BeanComponentDefinition(holder));

}

注:BeanDefinitionParserDelegate 是一个解析bean的辅助类,里面定义了很多字符串和一些默认解析的方法,比如填充默认属性等等。

(1)beanName的解析

首先就是对Element对象的解析,也就是获取BeanDefinitionHolder的过程

//解析id值
String id = ele.getAttribute(ID_ATTRIBUTE);
//解析name属性,因为name有可能是多个,所以还需要继续解析
//id="1 2 3 4" --->一个bean被一个id(1 2 3 4)标识
//name = "1 2 3 4"或者 name="1,2,3,4"--->一个bean被四个name(1)(2)(3)(4)标识
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<>();
//然后就是将name分割成字符串数组,然后转成List放到aliases中
String beanName = id;
if(”id是空串" && “alias不为null”){
	//如果未设置id并且设置了name
	//将第一个name作为bean的id
	/*
		总结:
			如果设置了id 则id作为唯一标识,name作为别名
			如果未设置id但是设置了name,则将name的第一个值作为唯一标识
	*/
	
	beanName = alias.remove(0);
}
//解析BeanDefinition,生成GenericBeanDefiniton
//AbstractBeanDefiniton 是其父类--->抽象类
AbstractBeanDefiniton beanDefinition = parseBeanDefinitionElement(ele,beanName,containingBean);
	/*
			如果未设置id和name 则将全类名作为唯一标识
			如果有多个未设置id和name的bean,并且bean相同,则在全类名后面加#序号(序号是配置文件读取顺序,从1开始)
	*/
if("beanName是空串"){
	//自动生成beanName
	if(containingBean != null){
		beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition,this.readerContext().getRegistry,true);
	}else{
		beanName = this.readerContext().generateBeanName(BeanDefinition);
	}
}

上面主要演示了bean的标识属性的处理逻辑,下面对GenericBeanDefinition的生成过程进行解析

解析class属性
解析parent属性
创建GenericBeanDefiniton
解析各种属性并且封装到GenericBeanDefiniton

(2) BeanDefinition的解析,生成GenericBeanDefinition

BeanDefinition是一个接口,在Spring中有三种实现类,并且都继承了AbstractBeanDefinition:

(1)GenericBeanDefinition:
	一站式服务类,可以用于各种情况(也就是通用bean)
	
(2)RootBeanDefinition
	没有父bean的bean可以用此类
	
(3)ChildBeanDefinition
	如果有父bean,则需要使用该类

补:父bean和子bean
spring中的父bean和子bean并不是内部属性的形式,比如A类中有B类的一个实例,而是类似继承关系。

1.  父bean和子bean并不需要在代码中实现继承关系,而是在配置文件中实现(在子bean的parent属性中配置父bean)
2.  子bean必须与父bean保持兼容,也就是子bean必须有父类中的所有属性
3.  父子bean实际上是参数的继承,两者的类型可以是不同的,只需要对应的属性以及变量名相同--->参数值层次的继承
	而继承是属性的继承,可以冲父类中继承子类没有的属性---->结构层次继承
4.  如果父类定义了class而子类未定义,则子类的class和父类相同
    如果父类未定义而子类定义,父类其实就是一个参数模板,没有什么意义,可以简化配置
5.  父类如果只是作为一个配置模板,可以将abstract置为true

创建GenericBeanDefinition代码分析

protected AbstractBeanDefiniton createBeanDefinition(String className,ClassLoader classLoader){
	GenericBeanDefinition bd = new GenericBeanDefinition ();
	bd.setParentName(parentName);
	if(className != null){
		if(classLoader != null){
			//使用该classLoader在虚拟机中加载此对象
			bd.setBeanClass(ClassUtils.forName(className,classLoader));
		}else{
			//只记录className
			bd.seBeanClassName(className);
		}
		
	}
	return bd;
}

创建了GenericBeanDefinition 后,就可以设置各种配置的属性了

//解析scope属性
if(ele.hasAttribute(SCOPE_ATTRIBUTE)){
	//spring 2.x 具有的scope属性
 	bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
	//sington和scope属性只可以设置一个,如果还有sington属性,则报错
	...
}

//解析sington属性
else if(ele.hasAttribute(SINGTON_ATTRIBUTE)){
	//spring 1.x 具有的sington属性
	...
}
//解析abstract属性--->略
//解析lazy-init属性(如果没有设置或者不是true,都不会被解析成false)--->略
//解析meta元素--->略
/*
解析lookup-method
	lookup-method作用:
	比如A类有个方法getBean,而这个方法返回B类的bean
	如果想要返回的bean是在spring中配置的B类型的bean,可以使用该标签
	<bean id="a" class="com.A">
		<lookup-method name="getBean" bean="b"/>
	</bean>
	<bean id="b" class="com.B"/>
*/
这里只是获取bean标签的所有子标签,然后遍历找到所有的lookup-method标签
LookupOverride类用于封装methodName和beanRef属性

//解析replaced-method子标签
//解析contructor-arg子标签
//解析property字标签
//解析qualifier属性

注册解析出来的Bean

//使用唯一标识beanName注册
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName,definitionHolder.getBeanDefinition());

//注册别名
String aliases = definitionHolder.getAlias();
if(aliaes != null){
	for(String alias : aliases){
		registry.registerAlias(beanName,alias);
	}
}

使用beanName注册Bean的流程如下:

//预处理逻辑省略(assert、validate等等)
//处理并发情况,使用同步锁
synchronized(this.beanDefinitionMap){
	Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
	if(oldBeanDefinition  != null){
		//处理bean已经注册过的情况
		//如果不允许覆盖bean的属性,则抛出异常
		if.....代码略
		//允许覆盖bean的属性,记录日志
	}else{
		this.beanDefinitionNames.add(beanName);
		this.forzenBeanDefinitionNames = null;
	}
	//真正注册bean(或者可以说覆盖bean)
	this.beanDefinitionMap.put(beanName,beanDefinition);
	//刷新缓存
	resetBeanDefinition(beanName);
}


别名注册

if(alias.equals(name)){
	//别名与唯一标识相同,移除
	this.aliasMap.remove(alias);
}else{
	//覆盖检测
	//如果该别名已经指向了别的bean,需要特殊处理
	if....
	//alias循环检查
	/*
		map中已经有 A->B和B->C
		如果存入C->A是需要进行循环检测
	
		或者A->C时,如果存入A->B->C时需要进行处理
		           
	*/
}


2.Alias标签的解析—>类似注册别名

补:alias和beanName对应关系分析
alias顶层接口:AliasRegistry
实现类:SimpleAliasRegistry

//首先使用了concurrentMap来缓存alias和beanName的关系
private final Map<String,String> aliasMap = new ConcurrentMap<>(16);

注册别名逻辑方法:registerAlias(String name,String alias);

synchronized(this.aliasMap){
	if(alias.equals(name)){
		//传经来的beanName和alias是相同的
		this.aliasMap.remove(alias);
	}else{
		//拿到这个别名对应的beanName
		String registeredName = this.aliasMap.get(alias);
		if(registeredName  != null){
			//说明这个别名已经存在对应的beanName了
			if(registeredName.equals(name)){
				//别名已经注册的beanName和即将要注册的BeanName是同一个
				//说明即将要注册的beanName和alias的对应关系已经存在于map中,直接返回什么也不做
				return ;
			}
			//到此if时,说明alias现在即将要注册第二套Alias-->beanName的映射关系
			if(不允许覆盖别名){
				抛出异常
			}
			
		}
	}
		checkForAliasCircle(name.alias);--->循环检测
		this.aliasMap.put(alias,name);--->alias和beanName在map中的结构,key为alias,value为name

}

别名循环检测代码逻辑:

param:name和alias

hasAlias(alias,name);
//首先注意这里
//alias对应方法形参的name,而name对应形参的alias--->检测循环依赖a->b,而b>a?
public boolean hasAlias(String name,String alias){
	for(Map.Entry<String,String> entry : this.aliasMap.entrySet()){
		String registeredName = entry.getValue():
		//for循环遍历entry来找到和参数中相同的name
		if(registeredName.equals(name)){
			String registeredAlias==entry.getKey();
			/*
				
				本次别名要放进去map的映射是a(alias)->b(beanName)
				检查循环依赖传进来的参数是hasAlias(b,a)
				也就是此时name才是真正的alias,而alias是真正的name
				然后现在registeredName与name相同,也就是在map中存在一个entry,value为a
				现在要检查的是key是否是b,也就是registeredAlias是否与当前方法中的alias相同
				如果相同,则说明在当前map中已经存在了b->a的映射,会产生循环依赖,return true
				如果不相同,说明现在有了c(registeredAlias)->a的映射,但是不排除会存在b->c是否有映射关系
				所以还需要递归hasAlias(registeredAlias,alias);注:hasAlias(m,n)检查的是在map中有没有n->m的映射
			
				
			*/
			if(registeredAlias.equals(alias) || hasAlias(registeredAlias,alias)){
				return true;
			}
		}
	}
	return false;
}

3.Import标签的解析—>获取resource并加载

4.beans标签的解析—>类似Import标签的功能,不常用

猜你喜欢

转载自blog.csdn.net/xkshihaoren/article/details/87898017
今日推荐