我们在最开始接触spring的时候,看到不少书spring入门的例子如下

ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml");
atx.getBean("benefitService");

上面这个例子第一行是表示如何初始化一个spring 容器,第二表示如何从一个已经初始化后的spring容器中按bean id得到这个bean, 绝大部分spring应用中,bean都是按业务模板和层次分别配置在不同的xml文件中, spring容器根据配置的xml文件名路径去分别解析这些xml 配置文件,生成相应的BeanDefinition 实例,一个bean对应一个BeanDefinition, 解析完成bean 的xml配置文件之后,spring容器就开始初始bean,大概的过程如下:

这篇文章主要分析第一个阶段,即xml配置文件 ---->BeanDefinition这个过程,首先根据IDE工具看一下ClassPathXmlApplicationContext 这个类的继承关系:

通过这个继续关系,发现ClassPathXmlApplicationContext也是间接实现了ResourceLoader这个接口, ResourceLoader的实现类主要用于根据给定的资源文件地址返回对应的Resource,在本例中,这个资源文件就是application.xml;
接着往下看

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}
}

代码到了含有三个参数的构造方法,主要有三个步骤

super(parent)

这个步骤主要是调父类的构造器初始化容器的parent对象,这示例中,parent这个参数为空,其次是初始化资源模式解析器resourcePatternResolver,是一个实现了ResourceLoader的类,源码如下 :

public AbstractApplicationContext(ApplicationContext parent) {this.parent = parent;this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {Assert.notNull(resourceLoader, "ResourceLoader must not be null");this.resourceLoader = resourceLoader;
}

setConfigLocations(configLocations)

这行代码,主要就是初始化configLocations这个数组字段,源码如下:

private String[] configLocations;
public void setConfigLocations(String[] locations) {if (locations != null) {Assert.noNullElements(locations, "Config locations must not be null");this.configLocations = new String[locations.length];for (int i = 0; i < locations.length; i++) {this.configLocations[i] = resolvePath(locations[i]).trim();}}else {this.configLocations = null;}
}

其中resolvePath主要解析并填充资源路径中的一些系统占位符,
如开始符:${,
结束符: }
分割符: :

refresh()

前面两步基本上都是容器本身的设置初始化,这个步骤才是spring 容器解析,创建初始化bean的关键步骤,点时去,我们发现这个方法长,只分析xml解析的过程,其它的在这里不一 一细说,在refresh方法中,第二代码是这样的:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

直接进入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()这个方法,看一下这个方法体:

@Override
protected final void refreshBeanFactory() throws BeansException {if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {DefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}} catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}
}

这个方法的大意是这样的:

  1. 先判断有没有beanFactory, 如果已经存在beanFactory,就先销毁beanFactory中所有的bean,再关闭这个beanFactory
  2. 创建一个DefaultListableBeanFactory 类型实例的beanFactory;
  3. 设置beanFactory的id,其中id是根据类名生成的,具体代码是:

     obj.getClass().getName() + "@" + getIdentityHexString(obj)
  4. 定置化beanFactory, 主要是设置: Bean是否需求覆盖重写,是否允bean循环引用,参数自动发现和注释字段bean匹配解析,例如Autowired注解;
    5.解析xml文档,并生成BeanDefinition对象实例集合;

重点看第5个步骤,即loadBeanDefinitions(beanFactory)这行代码,根据继承关系,直接进入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)这个方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// Create a new XmlBeanDefinitionReader for the given BeanFactory.XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's// resource loading environment.beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);
}

这个方法的逻辑也很清晰

  1. 创建一个XmlBeanDefinitionReader 对象,这个类主要定义读取的Document,并注册BeanDefinition的功能;
  2. 设置beanDefinitionReader 对象的ResourceLoader,即ClassPathXmlApplicationContext类对象;
  3. 设置beanDefinitionReader 对象的解析对象:ResourceEntityResolver
  4. 初始化beanDefinitionReader 对象,在spring框架中,这个方法为空,是一个重写spring框架的扩展点;
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}
  1. 正式解析xml文件,并生成BeanDefinitions, 方法体如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = getConfigLocations();if (configLocations != null) {for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}}
}

针对每个资源文件,重点看下面这两行代码:

Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);

第一行代码主是获取路径中以classpath:开头的xml文件,当然也有很多其它前缀开头的资源,org.springframework.util.ResourceUtils 这个类中有详细的说明
第二主要是生成加载BeanDefinitions

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));
}

前面都是一些准备过程,接下来,到了真正执行loadBeanDefinitions

  1. getValidationModeForResource这个方法主要是获取xml文件的验证模式,主要有DTD和XSD两种验证模式,这两种验证模式,spring都支持,方法源码如下:

DTD模式示例:

XSD模式示例:

  1. 加载xml,并得到转换成对应的Document实例
  2. 解析并注册xml文件定义相关的bean;
    重点看一下步骤3:

相关的方法源码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 使用 DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader对象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 记录统计前已经加载的BeanDefinition数量int countBefore = getRegistry().getBeanDefinitionCount();// 加载并注册beandocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 记录本次加载的 BeanDefinition数量return getRegistry().getBeanDefinitionCount() - countBefore;
}

重点分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))这行代

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;logger.debug("Loading bean definitions");Element root = doc.getDocumentElement();BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);preProcessXml(root);parseBeanDefinitions(root, delegate);postProcessXml(root);
}

registerBeanDefinitions这个方法的逻辑也比较清晰

  1. 赋值readerContext
  2. 获取xml对象加载包装后对应Document对象
  3. 创建解析器 BeanDefinitionParserDelegate 类型的对象实例:delegate
  4. 处理xml文件的前置处理,默认为空,这是一个扩展点;
  5. 解析xml文件,并注册到spring容器中
  6. 处理xml文件的后轩处理,默认为空,这也是一个扩展点;
    重点看一下parseBeanDefinitions这个方法,方法实现源码如下:
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)) {//默认标签bean的解析//<bean id="benefitService" class="com.tmall.xxx">parseDefaultElement(ele, delegate);} else {//自定义标签bean的解析<tx:annotation-driven /><dubbo:service  timeout="3000" interface="com.tmall.xxx" ref="xxxx"/><context:component-scan base-package="com.tmall.xxx"/>delegate.parseCustomElement(ele);}}}}  else {//自定义标签bean的解析<context:component-scan base-package="com.tmall.xxx"/>delegate.parseCustomElement(root);}
}

这个方法主要是针对bean的xml配置文件中默认标签和自定义标签分别进行解析
默认标签的解析方法parseDefaultElement的方法体如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {  // 对import标签的解析处理importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 对alias标签的解析处理processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//对bean标签的解析处理processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//对beanS标签的解析处理processBeanDefinition(ele, delegate);}
}

到这个方法,我们终于看到与我们平时写spring bean配置文件相关的代码,下面结bean标签的解析处理代码进行分析
bean标签的解析最终会到parseBeanDefinitionElement方法,这个方法的部分代码如下:

这个方法的主要工作内容包括:

  1. 提供元素中的id和name属性
  2. 进一步解析其他所有属性并统一封装到AbstractBeanDefinition类型的实例中
  3. 如果检测到bean没有指定的beanName,那么使用默认的规则为此Bean生成beanName
    4.将获取到的信息封装到BeanDefinitionHolder的实例中。

步骤2中对其它标签的解析过程部分源码如下:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,BeanDefinition containingBean, AbstractBeanDefinition bd) {if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {  // scope属性// Spring 2.x "scope" attributebd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 属性error("Specify either 'scope' or 'singleton', not both", ele);}}else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {// Spring 1.x "singleton" attributebd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);}else if (containingBean != null) {// Take default from containing bean in case of an inner bean definition.bd.setScope(containingBean.getScope());}if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {   // abstract属性bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));}String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);  // lazy-init属性if (DEFAULT_VALUE.equals(lazyInit)) {lazyInit = this.defaults.getLazyInit();}bd.setLazyInit(TRUE_VALUE.equals(lazyInit));String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);  //autowire属性bd.setAutowireMode(getAutowireMode(autowire));String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);bd.setDependencyCheck(getDependencyCheck(dependencyCheck));if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {  // depends-on属性String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, BEAN_NAME_DELIMITERS));}// autowire-candidate 属性String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(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));}// primary 属性if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));}// init-method属性if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);if (!"".equals(initMethodName)) {bd.setInitMethodName(initMethodName);}}else {if (this.defaults.getInitMethod() != null) {bd.setInitMethodName(this.defaults.getInitMethod());bd.setEnforceInitMethod(false);}}// destroy-method属性if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);if (!"".equals(destroyMethodName)) {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;
}// 解析购造函数constructor-arg标签元素
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);}}
}
//解析property属性
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);}}
}

其它的标签解析过程请参考下面这个类org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

上面解析分析了默认标签bean的源码解析过程,下面再来看一下自定标签的解析过程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源码如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {//获取对应的命名空间String namespaceUri = getNamespaceURI(ele);// 根据命名空间得到对相应的NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的NamespaceHandler handler进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

由于spring自定义标签大家在平时用得比较少,由于时间关系和篇幅关系,本来就不对自定义标签进行详细分析,下一篇文章会结合spring源码对自定义标签的使用和解析原理进行详细的介绍分析

由于时间关系,文中有些地方没有写细致,讲得不够清楚,可能不少地方还会出现低级的错误,请大家指正。

spring 源码分析(1)-xml文件解析相关推荐

  1. Tomcat7.0源码分析——server.xml文件的加载与解析

    前言 作为Java程序员,对于Tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析进行分析. 加载过程分析 Bootst ...

  2. Tomcat源码分析——server.xml文件的加载

    前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载的进行分析. 源码分析 Bootstrap的 ...

  3. spring源码分析06-spring配置类解析

    什么是spring配置类? 类上有注解:@Configuration .@Component.@ComponentScan.@Import.@ImportResource 或者类中的任意方法有@Bea ...

  4. springSecurity源码分析-spring-security.xml文件配置

    在spring-security.xml文件中配置 在配置文件中我们主要使用标签来过多成配置 <!-- 配置不拦截的资源 --> <security:http pattern=&qu ...

  5. ffmpeg源码分析及mp4文件解析

    一.mp4文件的组织 1. mp4文件的box(ffmpeg中叫atom) mp4是由一系列的box组成的,每个box的header是8个字节(4字节的长度,4字节的type) 第一个box比较特殊, ...

  6. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  7. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  8. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  9. 手撸spring源码分析IOC实现原理

    手撸spring源码分析IOC实现原理 文章出处:https://github.com/fuzhengwei/small-spring 根据小付哥的手撸spring核心源码一步步学习出来的结果收货总结 ...

最新文章

  1. 根据Forms名找出其所归属的权限组
  2. 基于插件化的企业级开发平台JXADF(开源)
  3. buuctf 二维码
  4. oracle 唯一索引,唯一约束,主键之间的联系
  5. MATLAB(五)在线性代数中的应用
  6. 计算机减法英语,英语的加减乘除如何表达?
  7. wince内存配置(转gooogleman的工作日志)
  8. Sony MZ-RH1 Hi-MD
  9. 中小企业什么OA办公系统适用呢?
  10. 浅谈unicode字符集及编码方式
  11. 问的书写规则是什么意思_汉字书写规则
  12. 笔试——字符串算法题——寻找最大回文子串
  13. 快门(曝光时间)、光圈、感光度(ISO)的关系
  14. java如何读取ic卡_java读取IC卡设备
  15. 简述神经网络学习过程
  16. Win7(老PC)Python环境搭建实战
  17. KMP算法求next数组
  18. 国产鸿蒙2.0尝鲜试用
  19. [重点]C语言——数组
  20. 不要做联表查询!!!

热门文章

  1. linux 下脚本查看带宽 (不需要安装其他工具包)
  2. LeetCode 435 无重叠区间
  3. 剑指 Offer 10- I. 斐波那契数列/剑指 Offer 10- II. 青蛙跳台阶问题
  4. USACO-Section2.2 Runaround Numbers
  5. 二十八种未授权访问漏洞合集(暂时最全)
  6. Linux C 存储映射IO
  7. 编写可测试的JavaScript代码
  8. Javaoop_继承
  9. C++11范围for循环
  10. Xcode7 添加PCH文件