上一篇我们梳理了容器初始化的整体流程,了解了一个 bean 是如何从静态配置变为一个可运行的实例的,但是对于过程中涉及到的具体细节并未进行深入探究。从本篇开始,我们将回到起点重新沿着主线走一遍,与之前不同的是,这一次我们更加关注细节。
由前面的分析我们已经大致知晓 IoC 容器在初始化期间主要分为两个阶段:加载并解析配置文件和初始化 bean 实例。本文所要介绍的对于默认标签的解析发生在加载并解析配置文件阶段,以 XML 配置为例,容器会将 XML 形式的静态配置解析成对应的 BeanDefinition 对象注册到容器中。在配置方面,Spring 为开发者提供了许多可用的标签,比如 <beans />
、<bean />
、<import />
,以及 <alias />
等等。这些标签统称为 默认标签 (个人觉得翻译成内置标签更加合理),同时 Spring 还允许开发者自己定义标签,方法 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
中的逻辑就是判断当前标签是默认标签还是自定义标签,并调用相应的方法对标签进行解析(实现如下),本篇我们主要分析默认标签的解析过程,对于自定义标签则留到下一篇进行讲解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected void parseBeanDefinitions (Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0 ; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { this .parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
我们在上一篇中已经和上述方法打过照面,如果不清楚该方法的调用时机,可以重新阅读一下。
本文,我们主要关注 DefaultBeanDefinitionDocumentReader#parseDefaultElement
方法。该方法用于对默认标签进行解析,整个方法的逻辑很清晰,判断当前的标签类型,然后调用对应的解析器去做解析处理,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void parseDefaultElement (Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { this .importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { this .processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { this .processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { this .doRegisterBeanDefinitions(ele); } }
方法按照标签类型分而治之,下面逐个探究各个标签的解析过程。
标签 bean 的解析过程
标签 <bean/>
是基于 XML 进行配置时使用频次最多的标签,所以我们首先从这里开挖。该标签对应的处理方法是 DefaultBeanDefinitionDocumentReader#processBeanDefinition
,先来总览一下整个方法的执行逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void processBeanDefinition (Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null ) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this .getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { this .getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'" , ele, ex); } this .getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
上述方法的执行流程可以概括为:
解析各个默认标签元素,将配置语义转换成 BeanDefinition 对象,并记录到 BeanDefinitionHolder 中;
检查当前标签下是否有自定义标签元素,若存在则进行解析;
注册由前两步得到的 BeanDefinitionHolder 对象;
发布事件,通知相应的监听者当前 bean 的默认标签已经解析完成。
解析默认标签元素
默认标签元素的解析位于 DefinitionParserDelegate#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 60 61 public BeanDefinitionHolder parseBeanDefinitionElement (Element ele) { return this .parseBeanDefinitionElement(ele, null ); } public BeanDefinitionHolder parseBeanDefinitionElement (Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0 ); } if (containingBean == null ) { this .checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = this .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); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this .readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } } catch (Exception ex) { this .error(ex.getMessage(), ele); return null ; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null ; }
上述解析 <bean />
标签的执行过程可以概括为:
获取 id 和 name 属性,并检查属性值在整个配置中的唯一性;
解析其它属性元素,封装成 GenericBeanDefinition 对象;
Spring 会以 id 或第一个 name 值作为 bean 的唯一标识(即 beanName),如果没有设置则依据命名规则自动生成一个;
将解析得到的 BeanDefinition 实例、beanName,以及 alias 列表封装成 BeanDefinitionHolder 对象返回。
整个过程中最核心的步骤是第 2 步,这一步完成了由配置到 BeanDefinition 实例的转换,实现如下:
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 public AbstractBeanDefinition parseBeanDefinitionElement ( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this .parseState.push(new BeanEntry(beanName)); String className = null ; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null ; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { AbstractBeanDefinition bd = this .createBeanDefinition(className, parent); this .parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); this .parseMetaElements(ele, bd); this .parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); this .parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); this .parseConstructorArgElements(ele, bd); this .parsePropertyElements(ele, bd); this .parseQualifierElements(ele, bd); bd.setResource(this .readerContext.getResource()); bd.setSource(this .extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { this .error("Bean class [" + className + "] not found" , ele, ex); } catch (NoClassDefFoundError err) { this .error("Class that bean class [" + className + "] depends on not found" , ele, err); } catch (Throwable ex) { this .error("Unexpected failure during bean definition parsing" , ele, ex); } finally { this .parseState.pop(); } return null ; }
上述方法会先获取配置的 class 和 parent 属性值,然后基于这两个值创建最初的 BeanDefinition 对象:
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 protected AbstractBeanDefinition createBeanDefinition ( @Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this .readerContext.getBeanClassLoader()); } public static AbstractBeanDefinition createBeanDefinition ( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setParentName(parentName); if (className != null ) { if (classLoader != null ) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
由上述实现可以看到,这里创建的 BeanDefinition 对象是 GenericBeanDefinition 类型,并设置对象的 GenericBeanDefinition#parentName
属性。如果指定了类加载器,则基于该类加载器获取 bean 对应的 Class 对象,并设置 AbstractBeanDefinition#beanClass
属性,否则直接将 bean 的 className 记录到该属性上。
解析属性元素
接下来,Spring 会去解析 <bean />
标签中的各种属性配置,并依据获取到的属性值去设置上面创建的 GenericBeanDefinition 对象。相关实现位于 BeanDefinitionParserDelegate#parseBeanDefinitionAttributes
方法中:
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 public AbstractBeanDefinition parseBeanDefinitionAttributes (Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { this .error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration" , ele); } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean != null ) { bd.setScope(containingBean.getScope()); } if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (this .isDefaultValue(lazyInit)) { lazyInit = this .defaults.getLazyInit(); } bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(this .getAutowireMode(autowire)); if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if (this .isDefaultValue(autowireCandidate)) { String candidatePattern = this .defaults.getAutowireCandidates(); if (candidatePattern != null ) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this .defaults.getInitMethod() != null ) { bd.setInitMethodName(this .defaults.getInitMethod()); bd.setEnforceInitMethod(false ); } 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 ); } if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }
整个方法解析了 <bean />
标签所有的属性,包括常用和不常用的,虽然实现很长,但是都是在做一件事情,即判断是否配置了相应的属性,如果配置了则解析并记录到 BeanDefinition 对象中。对于部分具备继承性质的属性,如果没有配置则沿用上游配置的值。下面选择几个不是特别常用的属性举例说明一下。
属性 abstract 与 parent 属性组合使用,让配置具备继承性质。如果多个 bean 在配置上存在大量的重复,这个时候就可以考虑使用继承的配置,抽象出重复的属性配置在父 bean 中,而子 bean 则配置特有的属性,如下:
1 2 3 <bean id ="abstractCar" class ="org.zhenchao.spring.ioc.Car" abstract ="true" p:brand ="benz" /> <bean id ="whiteCar" parent ="abstractCar" p:color ="white" /> <bean id ="blackCar" parent ="abstractCar" p:color ="black" />
通过 abstract="true"
属性将父 bean 置为抽象,然后在子 bean 中利用 parent 属性进行引用,这样相同的属性只需要配置一份即可。
在自动注入时,有时候往往存在多个候选的注入对象,Spring 在无法确定正确的候选者时就会抛出 UnsatisfiedDependencyException 异常,这个时候我们可以将某个或多个候选者配置为 autowire-candidate=false
,从而剥夺其候选者的身份。
属性 primary 的功能类似于 autowire-candidate,后者是剥夺某个 bean 的自动注入候选者身份,但是如果存在多个候选者时,一个个的配置会比较麻烦,这个时候我们可以使用 primary 属性将某个候选 bean 设置为 primary=true
,这样就使得该 bean 在候选时具备了最高优先级。
解析子标签元素
标签 <bean />
除了提供了大量的属性配置外,还允许我们在该标签中配置子标签,Spring 内置了多种子标签,下面对各个子标签的功能和解析过程逐一分析。
标签 meta 使用的不多,这个配置最终会记录到 BeanDefinition 实例中。假设有如下配置:
1 2 3 <bean id ="myBean" class ="org.zhenchao.spring.ioc.MyBean" > <meta key ="meta_name" value ="zhenchao" /> </bean >
那么我们可以通过如下方式从 bean 对应的 BeanDefinition 实例中获取配置的 meta 值:
1 2 final BeanDefinition definition = beanFactory.getBeanDefinition("myBean" );final Object metaName = definition.getAttribute("meta_name" );
通过分析源码我们可以清晰看到 <meta />
标签解析和记录的过程,位于 BeanDefinitionParserDelegate#parseMetaElements
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void parseMetaElements (Element ele, BeanMetadataAttributeAccessor attributeAccessor) { NodeList nl = ele.getChildNodes(); for (int i = 0 ; i < nl.getLength(); i++) { Node node = nl.item(i); if (this .isCandidateElement(node) && this .nodeNameEquals(node, META_ELEMENT)) { Element metaElement = (Element) node; String key = metaElement.getAttribute(KEY_ATTRIBUTE); String value = metaElement.getAttribute(VALUE_ATTRIBUTE); BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); attribute.setSource(this .extractSource(metaElement)); attributeAccessor.addMetadataAttribute(attribute); } } }
由上述实现可以看到,容器是将 <meta />
标签配置值通过 BeanMetadataAttribute 对象进行封装,并调用 BeanMetadataAttributeAccessor#addMetadataAttribute
方法记录到 attributes 属性中。该属性是一个 map 型的对象,而我们之所以可以从 BeanDefinition 实例中获取到配置值,是因为 BeanDefinition 继承自 BeanMetadataAttributeAccessor 类。
标签 <lookup-method />
一般用于希望在一个 singleton 对象中获取一个 prototype 对象的场景。假如我们在一个 singleton 对象中寄希望每次调用 getter 方法时获取一个 prototype 类型的新对象,因为外围 bean 实例是 singleton 类型的,其属性也都只有一份,所以不可能每次都返回 prototype 类型属性的新实例,此时我们就可以使用 <lookup-method />
标签来达到目的。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class MyBean { private PrototypeBean prototypeBean; public PrototypeBean getPrototypeBean () { return prototypeBean; } public void setPrototypeBean (PrototypeBean prototypeBean) { this .prototypeBean = prototypeBean; } }
MyBean 为 singleton 类型,虽然 PrototypeBean 是 prototype 的,但是我们每次调用 MyBean#getPrototypeBean
方法返回的仍然是同一个对象,这个时候就可以使用 <lookup-method />
标签:
1 2 3 4 5 <bean id ="prototype-bean" class ="org.zhenchao.spring.ioc.PrototypeBean" scope ="prototype" /> <bean id ="myBean" class ="org.zhenchao.spring.ioc.MyBean" > <lookup-method name ="getPrototypeBean" bean ="prototype-bean" /> </bean >
标签 <lookup-method />
让 MyBean#getPrototypeBean
方法每次都会去调用一遍 prototype 对象,从而每次都返回新的对象。该标签的解析位于 BeanDefinitionParserDelegate#parseLookupOverrideSubElements
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void parseLookupOverrideSubElements (Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0 ; i < nl.getLength(); i++) { Node node = nl.item(i); if (this .isCandidateElement(node) && this .nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; String methodName = ele.getAttribute(NAME_ATTRIBUTE); String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(this .extractSource(ele)); overrides.addOverride(override); } } }
上述方法的执行逻辑主要是解析 <lookup-method>
标签配置封装成 LookupOverride 对象,并调用 MethodOverrides#addOverride
方法将该对象添加到 overrides 属性中,这是一个线程安全的 Set 集合。实现每次返回一个新对象的机制是通过覆盖目标 bean 的对应方法,在每次调用该方法时都创建一个被引用 bean 的新实例。
标签 <replaced-method />
顾名思义,是用来替换一个方法的实现。如果希望替换 MyBean 的如下方法:
1 2 3 public void originalMethod () { System.out.println("This is original method." ); }
我们首先需要定义一个实现了 MethodReplacer 接口的 bean,并实现 MethodReplacer#reimplement
方法:
1 2 3 4 5 6 7 8 9 public class MyMethodReplacer implements MethodReplacer { @Override public Object reimplement (Object o, Method method, Object[] objects) throws Throwable { System.out.println("This is replaced method." ); return o; } }
然后利用 <replaced-method />
子标签进行配置:
1 2 3 4 5 <bean id ="method-replacer" class ="org.zhenchao.spring.ioc.MyMethodReplacer" /> <bean id ="myBean" class ="org.zhenchao.spring.ioc.MyBean" > <replaced-method name ="originalMethod" replacer ="method-replacer" /> </bean >
这样在调用 MyBean#originalMethod
方法时,本质上是在调用 MyMethodReplacer#reimplement
方法。该标签的解析位于 parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) 方法中:
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 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 (this .isCandidateElement(node) && this .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); 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(this .extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }
对于 <replaced-method />
标签的解析类似于 <lookup-method />
标签,不过标签 <replaced-method />
在替换方法时可能存在多个方法的重载版本,这个时候就需要通过参数类型进一步确认,所以实现时增加了对于参数类型配置的解析逻辑。
构造函数标签 <constructor-arg />
是我们常用的标签,也是三种依赖注入方式之一。使用示例:
1 2 3 4 <constructor-arg index ="0" name ="id" type ="long" value ="100001" /> <constructor-arg index ="1" name ="username" type ="java.lang.String" value ="zhenchao" /> <constructor-arg index ="2" name ="password" type ="java.lang.String" value ="123456" />
配置顺序与构造方法中参数定义顺序没有直接关系,所以大部分时候都可能会映射错误,此时我们可以凭借 index 属性和 type 属性从两个维度上做唯一性限制。标签 <constructor-arg />
可以看作是 <property />
标签的一种特殊形式,在实现上复用了解析 <property />
标签的实现,这些复用的实现将留到后面分析 <property />
标签时再展开。该标签的解析实现位于 BeanDefinitionParserDelegate#parseConstructorArgElements
方法中:
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 (this .isCandidateElement(node) && this .nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { this .parseConstructorArgElement((Element) node, bd); } } } public void parseConstructorArgElement (Element ele, BeanDefinition bd) { String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(indexAttr)) { try { int index = Integer.parseInt(indexAttr); if (index < 0 ) { this .error("'index' cannot be lower than 0" , ele); } else { try { this .parseState.push(new ConstructorArgumentEntry(index)); Object value = this .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(this .extractSource(ele)); if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { this .error("Ambiguous constructor-arg entries for index " + index, ele); } else { bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); } } finally { this .parseState.pop(); } } } catch (NumberFormatException ex) { this .error("Attribute 'index' of tag 'constructor-arg' must be an integer" , ele); } } else { try { this .parseState.push(new ConstructorArgumentEntry()); Object value = this .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(this .extractSource(ele)); bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); } finally { this .parseState.pop(); } } }
标签 <constructor-arg />
的解析过程中首先会获取 index、type 和 name 三个属性,然后依据是否设置了 index 属性分成两部分。如果设置了 index 属性,则整个解析过程如下:
解析 <constructor-arg />
标签元素,这里复用了 <property />
标签的解析过程;
利用 ValueHolder 封装解析出来的元素值,如果设置 type 和 name 属性则记录到到 ValueHolder 对象中;
检查是否存在重复的 index 配置,没有则以 index 为 key,将 ValueHolder 对象以 Map 的形式记录到 BeanDefinition 实例中。
如果没有设置 index 属性,则整个解析过程如下:
解析 <constructor-arg />
标签元素,这里复用了 <property />
标签的解析过程;
利用 ValueHolder 封装解析出来的元素值,如果设置 type 和 name 属性则记录到到 ValueHolder 对象中;
以 type 或 name 去推断当前 ValueHolder 对象所对应的参数,并以 List 的形式记录到 BeanDefinition 实例中。
上述步骤 3 的源码如下:
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 public void addGenericArgumentValue (ValueHolder newValue) { Assert.notNull(newValue, "ValueHolder must not be null" ); if (!this .genericArgumentValues.contains(newValue)) { this .addOrMergeGenericArgumentValue(newValue); } } private void addOrMergeGenericArgumentValue (ValueHolder newValue) { if (newValue.getName() != null ) { for (Iterator<ValueHolder> it = this .genericArgumentValues.iterator(); it.hasNext(); ) { ValueHolder currentValue = it.next(); if (newValue.getName().equals(currentValue.getName())) { if (newValue.getValue() instanceof Mergeable) { Mergeable mergeable = (Mergeable) newValue.getValue(); if (mergeable.isMergeEnabled()) { newValue.setValue(mergeable.merge(currentValue.getValue())); } } it.remove(); } } } this .genericArgumentValues.add(newValue); }
上述方法会去检查当前 ValueHolder 对象是否之前有加载过,没有则判断当前参数是否设置了 name 属性,如果有设置且当前 ValueHolder 对象与已有的 ValueHolder 对象存在相同的 name,则尝试对这个两个对象执行 merge 操作,最后记录 ValueHolder 对象到 ConstructorArgumentValues#genericArgumentValues
属性中,这是一个 List 类型属性。
标签 <constructor-arg />
的解析过程还是相当复杂的,这里面有相当一部分逻辑复用了 <property />
标签的解析过程。
标签 <property />
应该是 <bean />
标签中最常用的子标签,配置方式相当多元化,并且包含丰富的子标签元素。该标签的解析位于 BeanDefinitionParserDelegate#parsePropertyElements
方法中:
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 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 (this .isCandidateElement(node) && this .nodeNameEquals(node, PROPERTY_ELEMENT)) { this .parsePropertyElement((Element) node, bd); } } } public void parsePropertyElement (Element ele, BeanDefinition bd) { String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { this .error("Tag 'property' must have a 'name' attribute" , ele); return ; } this .parseState.push(new PropertyEntry(propertyName)); try { if (bd.getPropertyValues().contains(propertyName)) { this .error("Multiple 'property' definitions for property '" + propertyName + "'" , ele); return ; } Object val = this .parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); this .parseMetaElements(ele, pv); pv.setSource(this .extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this .parseState.pop(); } }
上述方法首先会去获取 <property />
标签的 name 属性,并确保 name 配置在 <bean />
标签范围内的唯一性;然后调用 BeanDefinitionParserDelegate#parsePropertyValue
方法解析标签值,接着调用 BeanDefinitionParserDelegate#parseMetaElements
方法来解析标签内的 <meta />
子标签,这个与之前 <bean />
标签的 <meta />
子标签的解析过程是一样的,唯一的区别在于这里将最终的解析结果存放到 PropertyValue 实例中,不过最终该实例还是交由 BeanDefinition 实例持有。
下面的示例演示了在 <property />
标签下嵌入 <meta />
子标签,假设我们为 MyBean#age
属性配置了 meta:
1 2 3 4 5 <bean id ="myBean" class ="org.zhenchao.spring.ioc.MyBean" > <property name ="age" value ="26" > <meta key ="age-meta-key" value ="age-meta-value" /> </property > </bean >
那么我们可以通过如下方式获取到该 meta 值:
1 2 final BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean" );final String metaValue = beanDefinition.getPropertyValues().getPropertyValue("age" ).getMetadataAttribute("age-meta-key" ).getValue();
上述过程的核心在于解析 <property />
标签的 value 配置,即 BeanDefinitionParserDelegate#parsePropertyValue
方法,这也是之前在分析 <constructor-arg />
标签时留着没有分析的方法,实现如下:
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 public Object parsePropertyValue (Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element" ); NodeList nl = ele.getChildNodes(); Element subElement = null ; for (int i = 0 ; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && !this .nodeNameEquals(node, DESCRIPTION_ELEMENT) && !this .nodeNameEquals(node, META_ELEMENT)) { if (subElement != null ) { this .error(elementName + " must not contain more than one sub-element" , ele); } else { subElement = (Element) node; } } } boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null )) { this .error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element" , ele); } if (hasRefAttribute) { String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { this .error(elementName + " contains empty 'ref' attribute" , ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(this .extractSource(ele)); return ref; } else if (hasValueAttribute) { TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(this .extractSource(ele)); return valueHolder; } else if (subElement != null ) { return this .parsePropertySubElement(subElement, bd); } else { this .error(elementName + " must specify a ref or value" , ele); return null ; } }
标签 <property />
的配置方式分为三种:<property name="" value=""/>
、<property name="" ref=""/>
,以及 <property name=""></property>
,并且同一时刻只能使用其中的一种方式,上述方法也是针对这三种配置分而治之。
如果配置方式是 <property name="" ref=""/>
,则获取并解析 ref 属性值封装成 RuntimeBeanReference 对象返回。如果配置方式是 <property name="" value=""/>
,则获取并解析 value 值封装成 TypedStringValue 对象返回。而对于 <property name=""></property>
方式来说,因为配置的多元化,Spring 采用专门的方法 BeanDefinitionParserDelegate#parsePropertySubElement
对其进行解析处理:
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public Object parsePropertySubElement (Element ele, BeanDefinition bd) { return this .parsePropertySubElement(ele, bd, null ); } public Object parsePropertySubElement (Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) { if (!this .isDefaultNamespace(ele)) { return this .parseNestedCustomElement(ele, bd); } else if (this .nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = this .parseBeanDefinitionElement(ele, bd); if (nestedBd != null ) { nestedBd = this .decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (this .nodeNameEquals(ele, REF_ELEMENT)) { String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false ; if (!StringUtils.hasLength(refName)) { refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true ; if (!StringUtils.hasLength(refName)) { this .error("'bean' or 'parent' is required for <ref> element" , ele); return null ; } } if (!StringUtils.hasText(refName)) { this .error("<ref> element contains empty target attribute" , ele); return null ; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(this .extractSource(ele)); return ref; } else if (this .nodeNameEquals(ele, IDREF_ELEMENT)) { return this .parseIdRefElement(ele); } else if (this .nodeNameEquals(ele, VALUE_ELEMENT)) { return this .parseValueElement(ele, defaultValueType); } else if (this .nodeNameEquals(ele, NULL_ELEMENT)) { TypedStringValue nullHolder = new TypedStringValue(null ); nullHolder.setSource(this .extractSource(ele)); return nullHolder; } else if (this .nodeNameEquals(ele, ARRAY_ELEMENT)) { return this .parseArrayElement(ele, bd); } else if (this .nodeNameEquals(ele, LIST_ELEMENT)) { return this .parseListElement(ele, bd); } else if (this .nodeNameEquals(ele, SET_ELEMENT)) { return this .parseSetElement(ele, bd); } else if (this .nodeNameEquals(ele, MAP_ELEMENT)) { return this .parseMapElement(ele, bd); } else if (this .nodeNameEquals(ele, PROPS_ELEMENT)) { return this .parsePropsElement(ele); } else { this .error("Unknown property sub-element: [" + ele.getNodeName() + "]" , ele); return null ; } }
上述方法的逻辑还是很清楚的,即判断当前标签类型,然后调用对应的解析方法进行针对性的处理。当前标签可以是自定义标签,也可以是默认标签,甚至是两种标签类型的多层嵌套,而 Spring 的实现也是采用方法的嵌套来处理复杂的配置。
对于 <ref />
标签来说,提供的使用方式有 <ref bean="..." />
和 <ref parent="..." />
两种类型,这两种配置的区别如下:
<ref bean="ref-bean"/>
表示可以引用同一容器或父容器中的 bean,这是最常用的形式。
<ref parent="ref-bean"/>
表示引用父容器中的 bean。
对于剩余的标签而言,除了 <idref />
、<value />
和 <null />
,其余的基本都是对集合类型的处理,对应的实现逻辑都比较清晰,这里不再深入。
注解 @Qualifier
在日常使用中较为常用,标签 <qualifier />
的作用与其相同,但是在配置中我们一般较少使用。该标签的解析实现位于 BeanDefinitionParserDelegate#parseQualifierElements
方法中:
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 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 (this .isCandidateElement(node) && this .nodeNameEquals(node, QUALIFIER_ELEMENT)) { this .parseQualifierElement((Element) node, bd); } } } public void parseQualifierElement (Element ele, AbstractBeanDefinition bd) { String typeName = ele.getAttribute(TYPE_ATTRIBUTE); if (!StringUtils.hasLength(typeName)) { this .error("Tag 'qualifier' must have a 'type' attribute" , ele); return ; } this .parseState.push(new QualifierEntry(typeName)); try { AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName); qualifier.setSource(this .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 (this .isCandidateElement(node) && this .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(this .extractSource(attributeEle)); qualifier.addMetadataAttribute(attribute); } else { this .error("Qualifier 'attribute' tag must have a 'name' and 'value'" , attributeEle); return ; } } } bd.addQualifier(qualifier); } finally { this .parseState.pop(); } }
上述解析过程的逻辑比较简单,通过 AutowireCandidateQualifier 对象对 <qualifier />
标签及其子标签进行封装,然后记录到 BeanDefinition 实例中。
至此,默认标签中的默认元素都已经全部解析完成,并设置到 GenericBeanDefinition 对象的相应属性中,该对象会被记录到 BeanDefinitionHolder 对象中返回,最后再回顾一下我们最开始出发的地方:
1 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
解析自定义标签
上一节我们介绍了 Spring 对于默认标签的解析过程,实际上在默认标签作用域内,Spring 还允许我们按照规范自定义标签。在开始分析自定义标签的解析过程之前,我们先来简单介绍一下自定义标签的用法,可能很多人还从来没有自定义过属于自己的标签。
需要注意的一点是,这里我们自定义的标签与下一篇讲解的与默认标签对标的自定义标签并不是同一概念,这里的自定义标签是嵌套在默认标签内的,更准确的说是一种 自定义属性标签 。自定义的过程分为如下几步:
创建标签实体类;
定义标签的描述 XSD 文件;
创建一个标签元素解析器,实现 BeanDefinitionDecorator 接口;
创建一个 handler 类,继承自 NamespaceHandlerSupport;
编写 spring.handlers 和 spring.schemas 文件。
下面通过自定义一个简化版的 <property />
标签演示自定义和使用的过程,该自定义标签包含 name 和 value 两个属性,目的是将 value 值注入到 bean 对应的名为 name 值的属性中。
第一步,先定义一个 <property />
标签对应的 POJO 类:
1 2 3 4 5 6 7 public class Property { private String name; private String value; }
第二步,定义标签的 XSD 文件,以约束配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <schema xmlns ="http://www.w3.org/2001/XMLSchema" targetNamespace ="http://www.zhenchao.org/schema/property" xmlns:tns ="http://www.zhenchao.org/schema/property" elementFormDefault ="qualified" > <element name ="property" > <complexType > <attribute name ="id" type ="string" /> <attribute name ="name" type ="string" /> <attribute name ="value" type ="string" /> </complexType > </element > </schema >
第三步,创建标签元素解析器,这里需要实现 BeanDefinitionDecorator 接口。解析器用于对标签配置属性进行解析,并设置到 BeanDefinition 实例的属性集合中,后续在执行 BeanFactory#getBean
时容器会将属性集合中的值赋值给 bean 实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class PropertyBeanDefinitionDecorator implements BeanDefinitionDecorator { @Override public BeanDefinitionHolder decorate (Node node, BeanDefinitionHolder definition, ParserContext parserContext) { Element element = (Element) node; if ("mytag:property" .equals(element.getNodeName())) { String name = element.getAttribute("name" ); Assert.hasText(name, "The 'name' in 'mytag:property' is missing!" ); String value = element.getAttribute("value" ); Assert.hasText(value, "The 'value' in 'mytag:property' is missing!" ); PropertyValue propertyValue = new PropertyValue(name, value); definition.getBeanDefinition().getPropertyValues().addPropertyValue(propertyValue); } return definition; } }
第四步,创建一个继承自 NamespaceHandlerSupport 抽象类的 handler 类,用于注册上面定义的解析器:
1 2 3 4 5 6 7 8 public class PropertyNamespaceHandler extends NamespaceHandlerSupport { @Override public void init () { this .registerBeanDefinitionDecorator("property" , new PropertyBeanDefinitionDecorator()); } }
第五步,编写 spring.handlers 和 spring.schemas 文件,放于 META-INF 目录下面,内容如下:
1 http\://www.zhenchao.org/schema/property=org.zhenchao.handler.PropertyNamespaceHandler
1 http\://www.zhenchao.org/schema/property.xsd=META-INF/property.xsd
下面来看一下如何使用。首先需要在头部定义标签命名空间:
1 2 3 4 5 6 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:mytag ="http://www.zhenchao.org/schema/property" xsi:schemaLocation = "http: //www.springframework.org /schema /beans http: //www.springframework.org /schema /beans /spring-beans.xsd http: //www.zhenchao.org /schema /property http: //www.zhenchao.org /schema /property.xsd "
假设 MyBean 中有一个 tag 属性,那么我们可以利用我们自定义的标签进行如下配置:
1 2 3 <bean id ="myBean" class ="org.zhenchao.spring.ioc.MyBean" > <mytag:property name ="tag" value ="myCustomTagValue" /> </bean >
当我们获取 MyBean 实例的 tag 属性值时,就能够得到我们配置的值,类似于标准 <property />
标签的效果,不过 Spring 提供的 <property />
标签功能要强大很多,这里只是演示如何自定义一个属于自己的标签,实际开发中我们自定义的标签也会比这要复杂的多。当我们在使用 Spring 默认的标签配置时,如果发现配置异常复杂,这个时候就可以考虑是否可以通过自定义标签来简化配置。不过笔者也不推荐为了自定义而自定义,自定义的标签给后来人阅读源码带来了很大的负担,增加了学习成本。
介绍完了自定义标签的定义和使用方式,我们继续来剖析 Spring 对于自定义标签解析过程的实现。自定义标签的解析过程位于 BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired
方法中:
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 public BeanDefinitionHolder decorateBeanDefinitionIfRequired (Element ele, BeanDefinitionHolder originalDef) { return this .decorateBeanDefinitionIfRequired(ele, originalDef, null ); } public BeanDefinitionHolder decorateBeanDefinitionIfRequired ( Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { BeanDefinitionHolder finalDefinition = originalDef; NamedNodeMap attributes = ele.getAttributes(); for (int i = 0 ; i < attributes.getLength(); i++) { Node node = attributes.item(i); finalDefinition = this .decorateIfRequired(node, finalDefinition, containingBd); } NodeList children = ele.getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { finalDefinition = this .decorateIfRequired(node, finalDefinition, containingBd); } } return finalDefinition; }
上述方法的实现逻辑分为两步执行:第一步处理当前标签所有的自定义属性;第二步处理当前标签的所有自定义子标签。不过,这两步最终都是通过调用 BeanDefinitionParserDelegate#decorateIfRequired
方法完成处理,实现如下:
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 public BeanDefinitionHolder decorateIfRequired ( Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { String namespaceUri = this .getNamespaceURI(node); if (namespaceUri != null && !this .isDefaultNamespace(namespaceUri)) { NamespaceHandler handler = this .readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler != null ) { BeanDefinitionHolder decorated = handler.decorate(node, originalDef, new ParserContext(this .readerContext, this , containingBd)); if (decorated != null ) { return decorated; } } else if (namespaceUri.startsWith("http://www.springframework.org/schema/" )) { this .error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]" , node); } else { if (logger.isDebugEnabled()) { logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]" ); } } } return originalDef; }
解析过程与我们定义自定义标签的过程相呼应。首先获取标签的命名空间,并以此来判断当前属性或标签是否是自定义的,如果是则获取对应的 NamespaceHandler 类对象,也就是我们之前自定义的 PropertyNamespaceHandler 类,我们在该类的 PropertyNamespaceHandler#init
方法中注册了我们的 BeanDefinitionDecorator 实例:
1 2 3 public void init () { this .registerBeanDefinitionDecorator("property" , new PropertyBeanDefinitionDecorator()); }
然后调用 handler 处理自定义的标签,这里本质上还是调用了我们自定义实现的 PropertyBeanDefinitionDecorator#decorate
方法,这样就将我们自定义的实现和 Spring 框架集成在了一起。
注册 BeanDefinition 对象
在将 bean 的默认标签和自定义标签都设置到 BeanDefinition 实例中后,接下来就是向 IoC 容器注册 BeanDefinition 对象啦。Spring 定义了 BeanDefinitionHolder 类用于封装 BeanDefinition 对象,以及 bean 对应的唯一 name 和 alias 列表,并将 BeanDefinition 以 BeanDefinitionHolder 对象的形式注册到容器中。BeanDefinitionHolder 类的定义如下:
1 2 3 4 5 6 7 8 public class BeanDefinitionHolder implements BeanMetadataElement { private final BeanDefinition beanDefinition; private final String beanName; private final String[] aliases; }
往容器注册 BeanDefinitionHolder 对象的实现位于工具方法 BeanDefinitionReaderUtils#registerBeanDefinition
中,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void registerBeanDefinition ( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); String[] aliases = definitionHolder.getAliases(); if (aliases != null ) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
整个注册过程主要做了两件事情:
以唯一的 beanName 作为 key,注册 BeanDefinition 实例,本质上就是以 map 进行内存存储;
建立 alias 与 beanName 之间的映射关系。
下面分别对这两个过程展开分析。
往容器注册 BeanDefinition 对象
注册 BeanDefinition 实例由 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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); } } BeanDefinition existingDefinition = this .beanDefinitionMap.get(beanName); if (existingDefinition != null ) { if (!this .isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]" ); } } this .beanDefinitionMap.put(beanName, beanDefinition); } else { if (this .hasBeanCreationStarted()) { synchronized (this .beanDefinitionMap) { this .beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList<>(this .beanDefinitionNames.size() + 1 ); updatedDefinitions.addAll(this .beanDefinitionNames); updatedDefinitions.add(beanName); this .beanDefinitionNames = updatedDefinitions; this .removeManualSingletonName(beanName); } } else { this .beanDefinitionMap.put(beanName, beanDefinition); this .beanDefinitionNames.add(beanName); this .removeManualSingletonName(beanName); } this .frozenBeanDefinitionNames = null ; } if (existingDefinition != null || this .containsSingleton(beanName)) { this .resetBeanDefinition(beanName); } }
往容器注册 BeanDefinition 时首先会判断 BeanDefinition 是否是 AbstractBeanDefinition 类型实例,如果是则进一步验证其 methodOverrides 属性,防止出现与工厂方法并存或覆盖的方法根本不存在的情况。然后检查 beanName 是否已经绑定了 BeanDefinition 实例,如果已经绑定且允许覆盖已有的实例,则执行覆盖操作,如果没有绑定则直接注册。注册操作本质上是以 beanName 作为 key,以 BeanDefinition 实例作为 value 记录到 map 数据结构中,后续加载 bean 时会从依据给定的 beanName 从中获取 BeanDefinition,并依据 BeanDefinition 创建 bean 对象。
建立 alias 到 beanName 之间的映射
往容器注册 BeanDefinition 的第二步是建立 alias 与 beanName 之间的映射关系。如果把 BeanDefinition 实例看做是文件的话,那么 beanName 可以看作是文件的硬链接,而 alias 则可以看作是软连接,是 beanName 的快捷方式。建立映射的过程由 SimpleAliasRegistry#registerAlias
方法实现:
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 public void registerAlias (String name, String alias) { Assert.hasText(name, "'name' must not be empty" ); Assert.hasText(alias, "'alias' must not be empty" ); synchronized (this .aliasMap) { if (alias.equals(name)) { this .aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug("Alias definition '" + alias + "' ignored since it points to same name" ); } } else { String registeredName = this .aliasMap.get(alias); if (registeredName != null ) { if (registeredName.equals(name)) { return ; } if (!this .allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'." ); } } this .checkForAliasCircle(name, alias); this .aliasMap.put(alias, name); } } }
上述实现比较简单,如代码注释,不再多做撰述。
发布事件通知
至此,Spring 完成了对 <bean />
标签的解析过程,将 <bean />
标签配置转换成 BeanDefinition 对象注册到 IoC 容器中。考虑到一些应用可能需要感知这一事件,Spring 在完成对一个 <bean />
标签的解析之后会发布事件通知,通过调用 ReaderContext#fireComponentRegistered
方法将消息通知到具体的监听者。如果用户希望监听这一事件,可以实现 ReaderEventListener 接口,Spring 会在发布事件通知时回调 ReaderEventListener#componentRegistered
方法。
标签 import 的解析过程
标签 <import />
也是我们比较常用的标签,尤其是在大型项目中,通过将各个模块的 Spring 配置分开定义,并在需要的地方通过 <import />
标签引入,可以让配置更加的清晰,易于管理。该标签的解析过程位于 DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource
方法中:
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 60 61 62 63 64 protected void importBeanDefinitionResource (Element ele) { String location = ele.getAttribute(RESOURCE_ATTRIBUTE); if (!StringUtils.hasText(location)) { this .getReaderContext().error("Resource location must not be empty" , ele); return ; } location = this .getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4 ); boolean absoluteLocation = false ; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { } if (absoluteLocation) { try { int importCount = this .getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]" ); } } catch (BeanDefinitionStoreException ex) { this .getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]" , ele, ex); } } else { try { int importCount; Resource relativeResource = this .getReaderContext().getResource().createRelative(location); if (relativeResource.exists()) { importCount = this .getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } else { String baseLocation = this .getReaderContext().getResource().getURL().toString(); importCount = this .getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location), actualResources); } if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]" ); } } catch (IOException ex) { this .getReaderContext().error("Failed to resolve current resource location" , ele, ex); } catch (BeanDefinitionStoreException ex) { this .getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]" , ele, ex); } } Resource[] actResArray = actualResources.toArray(new Resource[0 ]); this .getReaderContext().fireImportProcessed(location, actResArray, this .extractSource(ele)); }
标签 <import />
仅包含一个 resource 属性,该属性指定了配置文件的路径。路径可以是相对路径,也可以是绝对路径,路径中还可能存在一些系统属性占位符,比如 ${user.dir}
。上述方法首先对系统属性进行了处理,然后判断当前路径属于绝对路径还是相对路径,并分而治之。
标签 alias 的解析过程
标签 <alias />
用于为一个已定义的 bean 设置别名,虽然在 <bean />
标签中可以通过 name 属性定义别名,但是存在即合理,标签 <alias/>
总有它的应用场景。针对该标签的解析由 DefaultBeanDefinitionDocumentReader#processAliasRegistration
方法实现:
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 protected void processAliasRegistration (Element ele) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); boolean valid = true ; if (!StringUtils.hasText(name)) { this .getReaderContext().error("Name must not be empty" , ele); valid = false ; } if (!StringUtils.hasText(alias)) { this .getReaderContext().error("Alias must not be empty" , ele); valid = false ; } if (valid) { try { this .getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { this .getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'" , ele, ex); } this .getReaderContext().fireAliasRegistered(name, alias, this .extractSource(ele)); } }
标签 <alias/>
的解析过程复用了上面介绍的 SimpleAliasRegistry#registerAlias
方法,用于建立 alias 与 beanName 之间的映射关系,同时避免出现环路。
标签 beans 的解析过程
上面介绍的几种标签都是位于 <beans />
标签下面,本小节将要分析的 <beans />
标签是嵌套在外围 <beans />
标签中的,本质上没有什么区别,所以 Spring 的解析过程也是递归调用了之前的解析过程,实现如下:
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 protected void doRegisterBeanDefinitions (Element root) { BeanDefinitionParserDelegate parent = this .delegate; this .delegate = this .createDelegate(this .getReaderContext(), root, parent); if (this .delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!this .getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this .getReaderContext().getResource()); } return ; } } } this .preProcessXml(root); this .parseBeanDefinitions(root, this .delegate); this .postProcessXml(root); this .delegate = parent; }
上述方法用于解析 <beans />
标签。慢着!是不是看着有点眼熟,没错,本文最开始就是从这个方法中的 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法开挖的。饶了一大圈,我们又回到了起点,突然想到了一部电影 《恐怖游轮》 ,不多说了,睡觉~
参考
Spring 源码深度解析