若是你使用过SpringBoot, 你必定会知道porfile配置所带来的方便, 经过配置开发环境仍是生产环境, 咱们能够十分方便的切换开发环境,部署环境,更换不一样的数据库。 可能为了让Java开发者转向SpringBoot开发, Spring在5.x以后中止了对这个属性的支持。因此本文也就再也不继续描述这一属性。node
Spring中的标签分为默认标签和自定义标签两种,而这两种标签的用法及解析方式存在着很大的不一样,默认标签是在parseDefaultElement中进行的,函数中的功能一目了然,分别对4种标签(import, alias、bean、beans)作了不一样的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
咱们不得不认可,Spring5.x提倡咱们更少的使用xml文件,而是更多的使用注解进行配置,并且若是你常用Springboot的话,那么你确定知道习惯优于约定,而且springboot中只须要一个配置文件,虽然这有时根本没法知足需求,这里不作关于springboot的更多的说明。不过这并不影响Spring内部的实现,如今主要仍是从xml文件分析一下bean标签的解析及注册。spring
在4中标签的解析中,对bean标签的解析最为复杂也最为重要, 因此咱们从这个标签进行深刻的分析。 不过在这以前我仍是要将以前怎么加载这个文件的部分进行一下回忆
还记得上一部分,有一个这样的方法:数据库
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
若是确实对一步感兴趣能够追溯下去,这样就能够发现下面这段代码:springboot
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ 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)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
这段代码可能有点难以理解,不过当知道了if(delegate.isDefaultNamespace(ele)) 这个方法的做用就知道了,这其实就是在对标签进行一次处理而已, 是默认标签的就交给默认的处理方式,是自定义标签的话就换另外一种处理方式。这就是这个方法中作的事了。架构
public boolean isDefaultNamespace(Node node) { return isDefaultNamespace(getNamespaceURI(node)); }
这里的Node节点定义了全部的Spring提供的默认标签的解析结果。ide
那parseDefaultElement(ele, delegate)这个方法又在作些什么呢?其实不过是对根级节点的标签进行解析分类而已,如今咱们先分析一下bean标签, 因此如今只看针对于标签作了些什么。函数
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } 进入这个方法 /** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
这里使用Spring源码深刻解析的一段:
这个部分是Spring解析配置文件的最重要的部分, 根本就是解析标签并加载, 在使用Spring进行配置的时候不难发现, 咱们有如下几个重要的根级标签,bean, imort, alias, nested-beans, 下面的内容就主要介绍一下bean标签的解析。上一个小结的结尾部分已经涉及了这个的处理,继续上面的内容, 咱们会发现,实际上Spring首先是先经过“Bean定义解析委托”来得到了一个BeanDefinitionHolder, 在上面的分析中,咱们彷佛只注意了Element,而忘记了委托的是何时出现的,事实上这个委托是在DefaultBeanDefinitionDocumentReader在这个类中的时候就已经建立了这个委托, 而且一直经过参数的方式保存着这个委托, 知道们但愿得到一个BeanDefinitionHolder的时候才真正的发挥做用,那么这个委托具体是什么呢?这个委托的做用是状态的保存, 早在DefaultBeanDefinitionDocumentReader 这个类中使用的时候就经过xml解析的上下文,保存了bean标签中的全部状态,这些状态包括,ui
....
等等等……
那么BeanDefinitionHolder的做用又是什么呢? 经过这个holder, 但是实现注册的功能这是一个十分重要的功能,后面会具体分析这个功能。如今首先要看的是怎么得到的这个holder呢:this
/** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable 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 (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = 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); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isTraceEnabled()) { logger.trace("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
此处引用Spring源码解析中的一段内容:spa
以上即是对默认标签解析的全过程了。固然,对Spring的解析犹如洋葱剥皮同样,一层一层的进行,尽管如今只能看到对属性id以及name的解析,可是很庆幸,思路咱们已经了解了。在开始对属性进行全面分析以前, Spring在最外层作了一个当前成的功能架构, 在当前成完成的主要工做包括如下的内容。
(1)提取元素中的id和name属性。
(2)进一步解析其余全部属性并统一封装至GenericBeanDefinition类型的实例中。
(3)若是检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
(4)将检测到的信息封装到BeanDefintionHolder的实例中。
继续跟进代码:
/** * Parse the bean definition itself, without regard to name or aliases. May return * {@code null} if problems occurred during the parsing of the bean definition. */ @Nullable 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 = createBeanDefinition(className, parent); parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); parseConstructorArgElements(ele, bd); parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
经过对代码的跟踪,事实上,咱们很容易发现,这里的className就是从上一个方法中的经过解析别名获得beanName,在这里经过beanName又从已存的元素中获取获得的。一样的这个标签的父级元素parent也是这样获取获得。而接下来的操做也就是对各类属性的具体的解析操做了,诸如:me他, lookup-method, replace-method, property, qualifier子元素等。
BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和<bean>中的属性是一一对应的。其中RootBeanDefinition是最经常使用的实现类,它对应通常性的<bean>元素标签,GenericBeanDefinition是自2.5版本之后新加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中能够定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而没有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition对二者共同的类信息进行抽象。
Spring经过BeanDefinition将配置文件中<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRestry就像是Spring配置信息的内存数据库,主要是以map的形式保存。后续操做直接从BeanDefinitionRegistry中读取配置信息。
![]()
BeanDefinition 及其实现类 |
由此可知,要解析属性首先要建立用于承载属性的实例,也就是建立GenericBeanDefinition类型的实例。而代码createBeanDefinition(className, parent)的做用就是实现此功能。