bean子元素的解析
Posted joe-go
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bean子元素的解析相关的知识,希望对你有一定的参考价值。
前言
在上篇文章bean标签的解析中讲述了BeanDefinition已经完成了对bean标签属性的解析工作。在完成bean标签基本属性解析后,会依次调用parseMetaElements()、parseLookupOverrideSubElements()、parseReplacedMethodSubElements()等对子元素meta、lookup-method、replace-method等进行解析。下面分别对其说明解析过程。
meta子元素
在开始解析分析前,先来回顾一下meta属性的使用。
<bean id="car" class="test.CarFactoryBean"> <meta key = "testMeta" value = "hello"> </bean>
这段代码不会体现在CarFactoryBean的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过BeanDefinition的getAttribute(key)方法进行获取。
在Spring中,对meta属性的解析代码如下:
1 public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { 2 //获取当前节点所有子元素 3 NodeList nl = ele.getChildNodes(); 4 for (int i = 0; i < nl.getLength(); i++) { 5 Node node = nl.item(i); 6 //提取meta 7 if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { 8 Element metaElement = (Element) node; 9 String key = metaElement.getAttribute(KEY_ATTRIBUTE); 10 String value = metaElement.getAttribute(VALUE_ATTRIBUTE); 11 BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); 12 attribute.setSource(extractSource(metaElement)); 13 attributeAccessor.addMetadataAttribute(attribute); 14 } 15 } 16 }
解析过程较为简单,获取相应的BeanMetadataAttribute对象,然后通过addMetadataAttribute(attribute)加入到BeanMetadataAttributeAccessor中。如下:
public void addMetadataAttribute(BeanMetadataAttribute attribute) { super.setAttribute(attribute.getName(), attribute); }
委托给AttributeAccessorSupport类来实现:
public void setAttribute(String name, @Nullable Object value) { Assert.notNull(name, "Name must not be null"); if (value != null) { this.attributes.put(name, value); } else { removeAttribute(name); } }
AttributeAccessorSupport是接口AttributeAccessor的实现者。AttributeAccessor接口定义了与其他对象的元数据进行连接和访问的约定,可以通过该接口对属性进行获取、设置、删除操作。
设置完元数据后,则可以通过getAttribute()获取元数据,如下:
public Object getAttribute(String name) { Assert.notNull(name, "Name must not be null"); return this.attributes.get(name); }
lookup-method子元素
lookup-method子元素不是很常用,但是在某些时候它的确是非常有用的属性。通常我们称它为获取器注入:获取器注入是一种特殊的方式注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计某些可插拔的功能上,解除程序依赖。先来看一下具体的应用:
先声明一个类:
public interface Car { void display(); } public class Bmw implements Car{ @Override public void display() { System.out.println("我是 BMW"); } } public class Hongqi implements Car{ @Override public void display() { System.out.println("我是 hongqi"); } } public abstract class Display { public void display(){ getCar().display(); } public abstract Car getCar(); } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Display display = (Display) context.getBean("display"); display.display(); } }
Spring配置文件如下:
<bean id="display" class="org.springframework.core.test1.Display"> <lookup-method name="getCar" bean="hongqi"/> </bean>
运行结果:
我是 hongqi
如果将bean="hongqi"替换为bean="bmw",则运行结果为:
我是 BMW
到这里,我们已经初步了解了lookup-method子元素所提供的大致功能,下面就来看一下Spring中解析这个子元素的源码:
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //在Spring默认bean的子元素下且为<lookup-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; //获取要修饰的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); //获取配置文件中返回的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
与上一个子元素解析的过程基本相似,只是在数据存储上使用LookupOverride类型的实体类来进行数据承载并记录在 AbstractBeanDefinition中的methodOverride属性中。
replaced-method子元素
这个子元素的作用是对bean中replace-method的提取,称之为方法替换:可以在运行时用新的方法替换现有的方法。与之前的lookup-method不同的是replace-method不但可以动态的替换返回实体bean,而且还能动态的更改原有方法的逻辑。来看一下实例:
public class Method { public void display(){ System.out.println("我是原始方法"); } } public class MethodReplace implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { System.out.println("我是替换方法"); return null; } } public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Method method = (Method) context.getBean("method"); method.display(); }
Spring配置文件如下:
<bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/> <bean id="method" class="org.springframework.core.test1.Method"/>
运行结果:
我是原始方法
在Spring配置文件中增加replace-method子元素:
<bean id="methodReplace" class="org.springframework.core.test1.MethodReplace"/> <bean id="method" class="org.springframework.core.test1.Method"> <replaced-method name="display" replacer="methodReplace"/> </bean>
运行结果为:
我是替换方法
至此已经知道了,replaced-method 的用法,来看一下它的源码解析:
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //在Spring默认bean的子元素下且为<replaced-method有效 if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { Element replacedMethodEle = (Element) node; //提取要替换的方法 String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); //提取对应的新的替换方法 String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); // Look for arg-type match elements. List<Element> 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)); overrides.addOverride(replaceOverride); } } }
从上面的代码可以看出,无论是lookup-method还是replaced-method都是构造了一个MethodOverride,并最终记录在了AbstractBeanDefinition中的methoOverrides属性中。
子元素constructor-arg
对构造函数的解析是非常常用的,同时也是非常复杂的,先来举个例子:
<beans> <bean id="helloBean" class="com.joe.HelloBean"> <constructor-arg index=‘0‘> <value>hello</value> </constructor-arg> <constructor-arg index=‘1‘> <value>joe</value> </constructor-arg> ...... </bean> ....... </beans>
上面的配置是Spring构造函数配置中最简单基础的配置,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去,现在我们来看看具体的Spring解析过程:
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)) { parseConstructorArgElement((Element) node, bd); } } }
可以看出,parseConstructorArgElement()方法对constructor-arg进行解析的。具体代码:
1 public void parseConstructorArgElement(Element ele, BeanDefinition bd) { 2 //提取index属性 3 String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); 4 //提取type属性 5 String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); 6 //提取name属性 7 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); 8 //判断配置文件中是否包含了index属性 9 if (StringUtils.hasLength(indexAttr)) { 10 try { 11 int index = Integer.parseInt(indexAttr); 12 if (index < 0) { 13 error("‘index‘ cannot be lower than 0", ele); 14 } 15 else { 16 try { 17 this.parseState.push(new ConstructorArgumentEntry(index)); 18 //解析ele对应的属性元素 19 Object value = parsePropertyValue(ele, bd, null); 20 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); 21 if (StringUtils.hasLength(typeAttr)) { 22 valueHolder.setType(typeAttr); 23 } 24 if (StringUtils.hasLength(nameAttr)) { 25 valueHolder.setName(nameAttr); 26 } 27 valueHolder.setSource(extractSource(ele)); 28 //判断参数是否重复 29 if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { 30 error("Ambiguous constructor-arg entries for index " + index, ele); 31 } 32 else { 33 bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); 34 } 35 } 36 finally { 37 this.parseState.pop(); 38 } 39 } 40 } 41 catch (NumberFormatException ex) { 42 error("Attribute ‘index‘ of tag ‘constructor-arg‘ must be an integer", ele); 43 } 44 } 45 else { 46 try { 47 this.parseState.push(new ConstructorArgumentEntry()); 48 Object value = parsePropertyValue(ele, bd, null); 49 ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); 50 if (StringUtils.hasLength(typeAttr)) { 51 valueHolder.setType(typeAttr); 52 } 53 if (StringUtils.hasLength(nameAttr)) { 54 valueHolder.setName(nameAttr); 55 } 56 valueHolder.setSource(extractSource(ele)); 57 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); 58 } 59 finally { 60 this.parseState.pop(); 61 } 62 } 63 }
上面的代码很多,但是涉及的逻辑其实并不复杂:
(1)第2~7行:提取constructor-arg上必要的属性index、type、name。
(2)第9行:判断配置文件中是否指定了index属性。
(3)第10~44行:配置文件中指定了index属性。
1.第19行:解析constructor-arg的子元素。
2.第20行:使用ConstructorArgumentValues.ValueHolder 类型来封装解析出来的元素。
3.第33行:将type、name、index属性一起封装到ConstructorArgumentValues.ValueHolder类型中并添加到当前BeanDefinition的ConstructorArgumentValues的indexedArgumentValues属性中。
(4)第46~60行:配置文件中没有指定index属性。与步骤3基本一致,只是没有对index属性进行封装和将属性封装的位置变了,没有index 属性是将其他属性封装在genericArgumentValues中。
有以上的逻辑可以看出,对于是否制定index属性来讲,Spring的处理流程是不一样的,关键是在于属性信息的保存位置不同。
在以上了解了整个流程后,我们来进一步的了解解析构造函数配置中子元素的过程,进入parsePropertyValue方法:
1 public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { 2 String elementName = (propertyName != null ? 3 "<property> element for property ‘" + propertyName + "‘" : 4 "<constructor-arg> element"); 5 6 // 一种属性只能对应一种类型:ref、value、list等 7 NodeList nl = ele.getChildNodes(); 8 Element subElement = null; 9 for (int i = 0; i < nl.getLength(); i++) { 10 Node node = nl.item(i); 11 //对应description或者meta不处理 12 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && 13 !nodeNameEquals(node, META_ELEMENT)) { 14 // Child element is what we‘re looking for. 15 if (subElement != null) { 16 error(elementName + " must not contain more than one sub-element", ele); 17 } 18 else { 19 subElement = (Element) node; 20 } 21 } 22 } 23 24 //判断constructor-arg上是否含有ref属性 25 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); 26 //判断constructor-arg上是否含有value属性 27 boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); 28 //不能同时存在ref属性和value属性;也不能存在ref属性或者value属性且又有子元素 29 if ((hasRefAttribute && hasValueAttribute) || 30 ((hasRefAttribute || hasValueAttribute) && subElement != null)) { 31 error(elementName + 32 " is only allowed to contain either ‘ref‘ attribute OR ‘value‘ attribute OR sub-element", ele); 33 } 34 35 if (hasRefAttribute) { 36 //对ref属性进行处理,使用RuntimeBeanReference封装对应的ref名称 37 String refName = ele.getAttribute(REF_ATTRIBUTE); 38 if (!StringUtils.hasText(refName)) { 39 error(elementName + " contains empty ‘ref‘ attribute", ele); 40 } 41 RuntimeBeanReference ref = new RuntimeBeanReference(refName); 42 ref.setSource(extractSource(ele)); 43 return ref; 44 } 45 else if (hasValueAttribute) { 46 //对value属性的处理,使用TypedStringValue进行处理 47 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); 48 valueHolder.setSource(extractSource(ele)); 49 return valueHolder; 50 } 51 else if (subElement != null) { 52 //解析子元素 53 return parsePropertySubElement(subElement, bd); 54 } 55 else { 56 // 既没有ref,value,也没有子元素。 57 error(elementName + " must specify a ref or value", ele); 58 return null; 59 } 60 }
从上述代码来看,对构造函数中属性的解析,经历了如下过程(大致结果,上述代码注释已基本可以清楚了解):
(1)略过description或者meta。
(2)提取constructor-arg上的ref和value属性,以便于根据规则验证正确性。
(3)ref属性的处理。在Spring配置中使用<constructor-arg ref="aaaaa">。
(4)value属性的处理。在Spring配置中使用<constructor-arg value="aaaaa">。
(5)子元素的处理。在Spring配置中使用:
<constructor-arg> <map> <entry key="hello" value="Hello"/> </map> </constructor-arg>
子元素property
先来回顾一下property的使用方式:
<bean id="test" class="com.joe.Hello"> <property name="testStr" value="Hello"/> </bean> 或者 <bean id="test"> <property name="pro"> <list> <value>aaaa</value> <value>bbbbb</value> </list> </property> </bean>
Spring源码的解析过程是:
public void parsePropertyElements(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, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }
这个函数先提取所有的property的子元素,然后调用parsePropertyElement处理,来看这个方法的源码:
public void parsePropertyElement(Element ele, BeanDefinition bd) { //获取配置文件中的name属性 String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { error("Tag ‘property‘ must have a ‘name‘ attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { //不允许对同一属性进行多次配置 if (bd.getPropertyValues().contains(propertyName)) { error("Multiple ‘property‘ definitions for property ‘" + propertyName + "‘", ele); return; } Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } }
可以看出与构造函数注入方式不同的是将返回值使用PropertyValue进行封装,并记录在了BeanDefinition中的propertyValues属性中。
子元素qualifier
对于qualifier元素的获取,接触得更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选bean数目必须有且仅有一个。当找不到一个匹配的bean时,Spring容器将抛出BeanCreationException异常,并指出必须至少拥有一个匹配的bean。
Spring允许我们通过qualifier指定注入的bean名称,这样歧义就消除了,使用方式如下:
<bean id="hello" class="com.joe.Hello"> <qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/> </bean>
Spring解析的源码:
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) { parseQualifierElement((Element) node, bd); } } }
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { String typeName = ele.getAttribute(TYPE_ATTRIBUTE); if (!StringUtils.hasLength(typeName)) { error("Tag ‘qualifier‘ must have a ‘type‘ attribute", ele); return; } this.parseState.push(new QualifierEntry(typeName)); try { AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName); qualifier.setSource(extractSource(ele)); String value = ele.getAttribute(VALUE_ATTRIBUTE); if (StringUtils.hasLength(value)) { qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value); } NodeList nl = ele.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) { Element attributeEle = (Element) node; String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) { BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue); attribute.setSource(extractSource(attributeEle)); qualifier.addMetadataAttribute(attribute); } else { error("Qualifier ‘attribute‘ tag must have a ‘name‘ and ‘value‘", attributeEle); return; } } } bd.addQualifier(qualifier); } finally { this.parseState.pop(); } }
可以看出其解析过程与property的解析大同小异。就不再赘述。
参考:《Spring源码深度解析》 郝佳 编著:
以上是关于bean子元素的解析的主要内容,如果未能解决你的问题,请参考以下文章