-
qualifier子元素
-
构造函数的参数
-
lookup-method属性
-
replaced-method属性
-
关闭追踪
下面对每一步进行说明一下
开启追踪、并获取class和parent属性
这一步很简单。
-
将当前正在解析的Bean放在ParseState(底层是一个队列),通过队列去追踪当前解析的Bean。
-
解析Element去获取parent和class属性
创建用于属性承载的BeanDefinition
可以看到第一步的代码就是去创建BeanDefinition(BeanDefinitionParserDelegate中的方法),前面提到过,BeanDefinition其实相当于就是Bean,不信可以看一下官方文档
下面来看一下这个BeanDefinition
BeanDefinition是一个接口,有4个实现类,其中三个继承了AbstractBeanDefinition
BeanDefinition对应的就是配置文件元素标签在容器中的内部表示形式,说白了就是一个BeanDefinition就是IOC容器里的bean
bean标签里面的class、scope、lazy-init等配置属性,都会与BeanDefinition提供的方法一一对应
AbstractBeanDefinition
下面来看下这个AbstractBeanDefinition抽象类
可以看到,继承该抽象类的子类有三个
-
RootBeanDefinition:对应一般的bean元素标签,通俗来说,就是没有父bean的bean
-
ChildBeanDefinition:对应bean元素标签里面的bean元素标签,也就是bean的子标签
-
GenericBeanDefinition:2.5版本才加入的,一站式服务类
整体的流程就是
Spring通过BeanDefinition将配置文件里面的bean转换为容器的内部表示(具体就是RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition,这三个BeanDefinition),然后将这些BeanDefinition注册到BeanDefinitionRegistry。BeanDefinitionRegistry相当于Spring存储配置信息的内存数据库,使用map的形式去保存的,后面的操作就可以直接从BeanDefinitionRegistry中去读取配置信息
createBeanDefinition方法
/**
-
Create a bean definition for the given class name and parent name.
-
@param className the name of the bean class
-
@param parentName the name of the bean’s parent bean
-
@return the newly created bean definition
-
@throws ClassNotFoundException if bean class resolution was attempted but failed
*/
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)
throws ClassNotFoundException {
//可以看到,这里创建bean的工作交由了BeanDefinitionReaderUtils去做了
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName, className, this.readerContext.getBeanClassLoader());
}
BeanDefinitionReaderUtils的源码如下
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
//创建GenericBeanDefiniton
//使用GenericBeanDefinition
GenericBeanDefinition bd = new GenericBeanDefinition();
//注入parent(parent可能为空)
bd.setParentName(parentName);
//如果className不为空
if (className != null) {
//如果className不为空,classLoader也不为空
if (classLoader != null) {
//通过反射去获取bean对应java对象的class对象
//并且注入进bean中
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
//如果className不为空,但没有给classLoader
else {
//因为没有classLoader
//所以只将className保存进bean中
bd.setBeanClassName(className);
}
}
//返回GenericBeanDefinition
return bd;
}
从代码上可以看到
-
创建Bean的实际工作是由BeanDefinitionReaderUtils去做的
-
一开始创建的BeanDefinition类型为GenericBeanDefinition
-
注入parent与class属性
-
class属性代表的是Java类型,如果有给上classLoader,那就给BeanDefinition注入class对象
-
如果没有给上classLoader,那就注入class属性的值
-
这两者都是可以为空的!
解析各种属性
创建了BeanDefinition之后,就有了实例去承载bean标签的配置信息了,可以对bean的各种配置信息进行解析了
parseBeanDefinitionAttributes
首先是经过parseBeanDefinitionAttributes方法解析(Element就是bean在xml的配置信息)
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
//判断是否有singleton属性
//如果有,就直接error
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
//1.x版本的singleton已经被scope取代了!
//之前该标签是用来控制bean是否为单例的!
error(“Old 1.x ‘singleton’ attribute in use - upgrade to ‘scope’ declaration”, ele);
}
//下面两个else if的判断
//其实是判断有没有给bean设置scope属性
//scope其实就是bean的作用域(官方文档说有6种,并且可以自定义)
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
//如果设置了scope属性,那就采用配置文件的
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
//如果配置文件没有配置,那就再判断containingBean是否为空
//针对子bean标签
else if (containingBean != null) {
//这个设置是针对子bean标签的,因为只有bean子标签才会有containning bean
//也就是父标签,所以默认的子bean的scope是父bean一样的!
//这里避免是内部bean,所以使用containning bean的scope来作为默认的参考
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
//判断有没有abstract属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
//有abstract属性就进行,并且还规定abstract属性必须只能为true或者false
//否则会失效
//通过用true字符串去进行equals比较
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
//解析lazyInit属性
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
//判断lazyInit属性是不是为default
//为空,或者值为default,返回true
if (isDefaultValue(lazyInit)) {
//为空,或者lazyInit属性为default,那就设置默认值
//默认值由DocumentDefaultsDefinition决定
lazyInit = this.defaults.getLazyInit();
}
//注入lazyInit属性进bean
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
//获取autowire属性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
//注入autowire属性,总共有4种,其中一种弃用
//1.byName,2.byType,3.construct,4.autodetect
bd.setAutowireMode(getAutowireMode(autowire));
//判断有没有depends-on属性
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
//解析autowire-candidate属性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
//如果没有设置,或者为default
if (isDefaultValue(autowireCandidate)) {
//那就使用默认值
String candidatePattern = this.defaults.getAutowireCandidates();
//autowire-candidate属性可以设置多个,使用逗号间隔
//只有默认值,才会有多个的情况!
if (candidatePattern != null) {
//所以如果有设计的话,需要解析并且使用字符串数组来接收
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
//如果不是默认值,只可能是true或者false
//同理使用true字符串去进行equals比较
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
//解析primary属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
//同理为true或者为false
//不为空就进行注入
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
//下面是生命周期的配置
//解析init-method属性
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
//不为空就进行注入
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
bd.setInitMethodName(initMethodName);
}
//如果init-method属性没有设置(创建的时候执行的方法)
//那就去判断有没有默认值
else if (this.defaults.getInitMethod() != null) {
//没有设置且有默认值,init-method属性就设为默认值
bd.setInitMethodName(this.defaults.getInitMethod());
//并且init-method的方法设置为不强制执行
//因为是默认的,所以不强制执行
bd.setEnforceInitMethod(false);
}
//解析destory-method属性
//bean销毁时执行的方法
//与method-init属性一样,有的注入,没有就判断默认的有没有
//没有设置且有默认的,是不会强制执行
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
//解析factory-method属性
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
//解析factory-bean属性
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
总结一下这个方法
-
创建BeanDefinition,为GenericBeanDefinition
-
对bean的所有属性进行解析
至此,该bean标签基本上已经所有属性已经解析完成了,剩下的就是里面的子标签了
scope作用域与init-method、destory-method生命周期
官方文档截图
parseMetaElements
parseBeanDefinitionAttributes已经帮我们将bean的属性都解析完了,接下来就是子标签的解析了
子标签的解析,首先是对元数据meta的解析
可能很少人用过meta子标签,因为meta标签的作用其实就是做一个额外的声明信息,声明了之后,该bean对应的BeanDefinition可以通过getAttribute方法去获取里面的信息
下面是源码
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
//meta是子标签
//所以,先要获取当前标签的所有子标签
NodeList nl = ele.getChildNodes();
//遍历子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//提取出meta子标签出来
if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
//取meta子标签的key和value属性
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
//meta子标签对应的对象为BeanMetadataAttribute
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(extractSource(metaElement));
//存进BeanDefinition中(因为放进来的参数为BeanDefinition)
//底层为一个LinkedHashMap
//底层是交由AttributeAccessorSupport去实现的
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
@Override
public void setAttribute(String name, @Nullable Object value) {
Assert.notNull(name, “Name must not be null”);
//如果value为null
//代表删除动作
//不为null,才是插入,插入进底层的LinkedHashMap中
if (value != null) {
this.attributes.put(name, value);
}
else {
removeAttribute(name);
}
}
parseLookupOverrideSubElements
解析来解析的是lookup-method子标签
这个子标签跟meta一样,我们都很少使用,但在某些场景,这个标签是非常有用的,这个标签通常称为获取器注入
它的作用是,可以去直接获取其他bean作为方法的返回值,即父bean把一个方法声明为返回某一个类型的bean,我们可以通过lookup-method的子标签,从spring容器中去取出这个bean,然后给父bean的方法进行返回,总的来说,就是替换了返回bean
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
//原理与meta解析几乎一致
//获取子标签
NodeList nl = beanEle.getChildNodes();
//遍历子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//判断是否是lookup-method标签
if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
//获取lookup-method标签的name和bean属性
//bean对应的是容器中的另外一个bean的id属性
//name属性对应的是父bean的方法名!
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
String beanRef = ele.getAttribute(BEAN_ELEMENT);
//lookup-method标签对应的java对象为LookUpOverride
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
//同理放入BeanDefinition中
//这里BeanDefinition存储的底层是一个CopyOnWriteArraySet
overrides.addOverride(override);
}
}
}
parseReplacedMethodSubElements
接下来是解析replaced-method子标签
这个标签也很少有,也还是说一下它的用途,replaced-method标签可以取替换返回实体bean,还可以动态地更改原有方法的逻辑,总的来说就是可以将父bean的方法整体给替换掉,也就是执行的方法已经是另外一个bean的方法了,但这个方法并不是可以随便写的,必须另外一个bean实现MethodReplacer接口,重写的那个方法才可以进行替换
下面是解析的源码
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
//前面的逻辑与前面两个属性解析都差不多一致,就不再赘述
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
//获取name属性,代表要被替换的方法名
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
//获取replace属性,代表要替换成哪个bean的id
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
//replace-method标签对应的Java实体为replaceOverride
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// Look for arg-type match elements.
List argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
//取匹配参数
for (Element argTypeEle : argTypeEles) {
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
//标明来源于哪个标签
replaceOverride.setSource(extractSource(replacedMethodEle));
//同样存放进BeanDefinitions,注意这里的存放地方与lookup-method是同一个Set
//因为都是MethodOverrides
overrides.addOverride(replaceOverride);
}
}
}
parseConstryctorArgElements
下面就是到我们常用的子标签了,解析construct-arg标签
该标签就是可以使用对应的构造函数去创建bean
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
//与前面一致
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
//判断如果是construct-arg子标签,进行解析
parseConstructorArgElement((Element) node, bd);
}
}
}
parseConstructorArgElement
这个方法是可以解析一个construct-arg标签的逻辑(这代码量一看就十分复杂)
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
//解析construct-arg标签里面的index、type和name属性
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//判断index属性是否为空
if (StringUtils.hasLength(indexAttr)) {
//将索引值转为Integer类型,并且判断不能小于0
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
//索引值小于0 报错
error(“‘index’ cannot be lower than 0”, ele);
}
else {
//索引值正确
try {
//开启跟踪
this.parseState.push(new ConstructorArgumentEntry(index));
//获取construct-arg的value子标签里面的值
Object value = parsePropertyValue(ele, bd, null);
//使用ValueHolder去封装value
//因为后续还需要保存该value对应构造函数的哪个变量名、哪种类型
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
//如果有给type和name属性(construct-arg标签)
//就与value一起保存进valueHolder里面
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
//valueHolder标明来源于哪个标签
valueHolder.setSource(extractSource(ele));
//这里是判断BeanDefinition里面是否已经存在这个索引了
//如果已经存在这个索引,就代表出现了相同的索引
//一个构造方法怎么可能会出现相同索引的参数value
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
//报错
error("Ambiguous constructor-arg entries for index " + index, ele);
}
//如果没有出现过
else {
//那就存进去
//从这里可以看到,存储构造函数的底层是一个LinkedHashMap
//ConstructorArgumentVlues就是存储BeanDefinition的构造参数的
//并且通过Map,来让索引值和value可以映射
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
//接触监控
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
//索引值类型不正确,报错
error(“Attribute ‘index’ of tag ‘constructor-arg’ must be an integer”, ele);
}
}
//如果index属性为空
//那就按顺序
else {
try {
//与前面是一样的过程
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
//注意这里,使用ConstructorArgumentValues去存放构造参数值
//不再使用LinkedHashMap了,而是一个ArrayList
//因为都没给索引,不需要进行Key,value映射,直接用ArrayList的底层数组索引来代表即可
//可以按顺序进行存放
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
//解除监控
this.parseState.pop();
}
}
}
整个流程大体是
-
解析construct-arg的name、type和index属性
-
如果配置了Index属性
-
使用ValueHolder去封装解析出来的元素
-
并且将type、name和index一起封装进valueHolder
-
判断ConstructorArgumentValues的indexArgumentValues中是否已经有这个该索引位置
-
最后将valueHolder存放进ConstructorArgumentValues的indexArgumentValues中,即LinkedHashMap里面,使用(index,valueHolder)的键值对方式
-
如果没有配置Index属性
-
同样使用ValueHolder去封装解析出来的元素
-
并且将Type、name和index一起封装进valueHolder
-
最后将valueHolder存放进ConstructArgumentValues的genericArgumentValues中,即ArrayList里面,这样设计是想以默认升序顺序来顶替缺省的Index(ArrayList的索引可以充当value的索引,并且只能按顺序去存放,也是因为按顺序去存放value,所以不需要考虑这个索引位置已经存在value)
parsePropertyValue
这个方法就是处理当前construct-arg标签里面的value子标签(又是一大串代码)
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
String elementName = (propertyName != null ?
“ element for property '” + propertyName + “'” :
“ element”);
// Should only have one child element: ref, value, list, etc.
//按照约定,construct-arg应该只有一个子标签
NodeList nl = ele.getChildNodes();
Element subElement = null;
//遍历子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//筛选出不是meta和description子标签的
//所以construct-arg是可以有多个子标签的,但只能是meta和description
//且倘若出现 ref, value, list, etc其中一个,后面的meta和description会导致报错
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we’re looking for.
//筛选了之后,就代表这是我们想要的子标签
//这也说明了一个现象,在construct-arg中.meta和description子标签是失效的!
//下面这个判断就很有意思了
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
null;
//遍历子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//筛选出不是meta和description子标签的
//所以construct-arg是可以有多个子标签的,但只能是meta和description
//且倘若出现 ref, value, list, etc其中一个,后面的meta和description会导致报错
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we’re looking for.
//筛选了之后,就代表这是我们想要的子标签
//这也说明了一个现象,在construct-arg中.meta和description子标签是失效的!
//下面这个判断就很有意思了
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-tny6Ttl0-1715095158470)]
[外链图片转存中…(img-yIRoBTqZ-1715095158471)]
[外链图片转存中…(img-vSrVRE8r-1715095158471)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!