本文档针对spring4.2.x版本
Spring IOC容器初始化的过程,分为定位,载入解析以及注册,接下来本文主要分析的是spring如何去解析Bean为BeanDefinition对象的,这个只对xml声明的Bean进行分析,对于通过spring注解扫描的方式以后再做分析
当spring定位到spring的xml文件以后,将xml读作为文件流的形式,作为InputSource和Resource对象传递给文档解析器进行解析,文档解析的开始是XmlBeanDefinitionReader的doLoadBeanDefinitions方法。
inputSource是SAX的InputSource, resource对象是对xml文件描述的一个对象 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { /** 传递inputSource对象进入doLoadDocument方法,此方法中设置了xml的版本,xml解析的模式等相关信息,然后input Source文件流读作为Document对象返回,把Document对象传递给registerBeanDefinitions方法,这个方法才是真正的把Document对象解析为BeanDefinition对象的具体实现 **/ Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch … }
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
先实例化出来一个BeanDefinitionDocumentReader,这个对象是Java通过自己封装工具类BeanUtils.instantiateClass 通过反射的方式实例化出来的,然后记录一下在注册之前BeanDefinition中对象的个数,接着开始去解析document,spring本身设计非常灵活,BeanDefinitionDocumentReader 的registerBeanDefinitions方法是一个抽象方法,spring自身实现一个默认的BeanDefinitionDocumentReader的一个注册器,DefaultBeanDefinitionDocumentReader,在这个子类中去实现具体的解析工作。
@Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); } protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate对象描述了spring中bean节点中定义的所所有属性和子节点 //所有的内嵌<beans>节点,都会被递归这个方法中通过递归的方式找到 //<beans>节点的默认属性都会在此方法中设置, //保存当前对象(或者父对象)BeanDefinitionParserDelegate轨迹,可能为空,但是会创建 //子类的属性描述(BeanDefinitionParserDelegate)会持有父类的一个引用 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(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 (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //在xml解析的预处理,客户端可以自己定义一些自己的节点属性,用以特殊的作用,或者增强,此方法spring默认实现为空 preProcessXml(root); //把Document对象解析为BeanDefinition对象 parseBeanDefinitions(root, this.delegate); //在xml解析为BeanDefinition之后做一些后置处理,客户端可以在解析完xml之后,做一些自己的业务逻辑,目前spring的默认实现为空 postProcessXml(root); this.delegate = parent; }
接下来分析parseBeanDefinition方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //校验是不是spring的默认命名空间,默认命名空间为http://www.springframework.org/schema/beans,如果是默认命名空间,则按照默认命名空间来解析,否则则按照客户自定义程序来解析 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; //循环xml节点,看delegate对象是否是默认命名空间,如果是则按照默认命名空间来解析节点,否则则按照客户自定义来解析 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
下面对parseDefaultElement 方法进行分析
此方法会查询文档中的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); } }
1、 Import节点的解析
首先要求import节点中必须有resource属性,然后根据resource的值判断是相对路径还是绝对路径。
并且对于resource中的占位符进行解析,例如"${user.dir}" 解析为真正的路径
然后调用XmlBeanDefinitionReader的loadBeanDefinitions方法,把import节点映射为BeanDefinition对象,初始化了一个4个元素大小的LinkHashSet对象,准备好相关容器和资源,然后调用本类中的doLoadBeanDefinitions方法,做真正的映射工作 ,这是一个递归的过程,它需要把所有的import节点中的xml文件递归的找出来,然后做的均是bean节点的解析
2、 Alias节点解析
3、 Bean节点解析
使用list存储bean信息
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
/** List of bean definition names, in registration order */
private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);
/** List of names of manually registered singletons, in registration order */
private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
把以上信息进行存储完以后逐个转化为BeanDefinition的属性
4、 Beans节点解析
最终转化为bean节点的解析