一、环境准备

  对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子:

package com.zjl;

public class Person {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public void sayHello(){
System.out.println("hello "+this.name);
}
}

  bean的定义:

    <bean id="person" class="com.zjl.Person">
<property name="name" value="zhangsan"></property>
</bean>

  从很久以前,spring的第一个例子惯性的我们都是用XmlBeanFactory来进行,测试代码如下:

        XmlBeanFactory xmlBeanFactory=new XmlBeanFactory(new ClassPathResource("bean.xml"));
Person person=(Person)xmlBeanFactory.getBean("person");
person.sayHello();

  不过,很可惜,这个类在后来的版本中被删除了,不过没关系,他的源码很简单:

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    /**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
} /**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
} }

  我们可以看到,只是他继承了DefaultListableBeanFactory ,并持有一个XmlBeanDefinitionReader的引用,然后调用了this.reader.loadBeanDefinitions(resource)方法。所以我们很容易根据这个源码将程序改造一下

        DefaultListableBeanFactory beanFacory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(beanFacory);
reader.loadBeanDefinitions(new ClassPathResource("bean.xml"));
Person person=(Person)beanFacory.getBean("person");
person.sayHello();

 二、源码解析

  结合上一节的内容,在本章节,我们只是详细看下针对xml配置的bean的一个加载过程,并不会调用getBean方法具体生成bean的对象,入口程序我们锁定为reader.loadBeanDefinitions(new ClassPathResource("bean.xml"))

  1、对resource进行了再次包装,添加编码和字符集

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

  2、读取配置文件,将inputStream转化为sax的inputSource,并设置字符集

try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
         //入口程序
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}

  3、进入doLoadBeanDefinitions方法,将inputSource转化为Dom解析的Document

            Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);

  4、创建dom的读取方法DefaultBeanDefinitionDocumentReader的实例,,并获取bean的数量,使用registerBeanDefinitions加载bean,解析完成后,返回此次加载bean的数量,注:此处对resource再次做了封装,疑似注入一些监听函数,但是暂时不管他们

    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;
}

  5、进入DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法,获取到配置文件的根节点beans,针对根节点开始遍历

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}

  6、创建了一个delegate,并判断节点是否为NameSpaceURI-http://www.springframework.org/schema/beans

    public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}

  7、如果是默认的,获取profile属性,此处资料上可以配置开发、生产或者测试环境,暂不深究

        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)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}

  8、继续解析,在真正解析前后,可以使用pre和post方法对xml进行处理,此处均为空,只看parseBeanDefinitions方法即可

        preProcessXml(root);
     //入口
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

  9、此处再次判断,root是否为默认的namespace,如果是,获取所有子节点,进行遍历子节点,并解析。如果根节点或者子节点不是默认的namespace,将使用delegate.parseCustomElement进行解析,由此可以看出,spring为我们预留了一个自定义配置文件解析的入口(此部分作为下一个章节重点分析)

  此次我们只有默认的namespace的应用,所以进行解析他的Element的子节点,我们知道唯一一个子节点为“bean”

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);
}
}

10、对子节点的解析分为4中情况,分别为import,alias,bean,beans,其中

  beans:之前处理过,再次解析子节点即可

  import:表示引入另外的配置文件资源进行解析,此处预留其他文件解析入口

  alias:支持此格式<alias name="doSpring" alias="do"/>,是对一个bean起别名

  bean:是我们目前解析的重点,进入下一步解析

    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);
}
}

11、根据节点,创建一个BeanDefinitionHolder,

    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));
}
}

12、我们具体看下他的源码,可以知道它是根据Element的id,name等两个属性进行生成,如果有id,id作为beanName,如果id不存在,name的第一个作为beanName,其他作为别名,此处不再贴出,然后根据name和元素,创建一个AbstractBeanDefinition

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

13、 分别获得className、parent、descriptioString className = null;        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();

        }

        try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
       //14、初始化GenericBeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
       //15、设置bean的其他属性,包括单例,抽象,延迟加载、autowire、dependency-check、depends-on、autowire-candidate、primary、init-method、destroy-method、factory-method、factory-bean等属性,
此部分的特点是有一个默认值,但也通过系统默认设置
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
       //解析子标签中的meta标签
parseMetaElements(ele, bd);
       //解析look-up标签
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
       //解析replaced-method标签
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
       //constructor-arg标签,为了便于构造函数注入
parseConstructorArgElements(ele, bd);
      //解析Property标签,根据value或者ref标签分别注入TypedStringValue或RuntimeBeanReference到PropertyValue中
parsePropertyElements(ele, bd);
       //解析qualifier
parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele)); return bd;
}

14、与13一起,根据parentName和className创建GenericBeanDefinition的实例,此处classLoader不为null时候,似乎客户设置复杂的数据格式,暂时跳过

    public static AbstractBeanDefinition createBeanDefinition(
String parentName, String className, 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;
}

15、如果有其他标签,仍按照此方法继续处理。到此部分,我们已经解析完了配置文件中的所有标签

16、根据别名,beanDefinition beanName创建BeanDefinitionHolder

String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);

17、对holder进行进一步装饰,主要装饰[autowire="default", autowire-candidate="default", class="com.zjl.Person", id="person", lazy-init="default"]等

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.将最终装饰后的BeanDefinition注入registry,
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));
}

18、调用registerBeanDefinition,注入bean时,先做一个父类检测((AbstractBeanDefinition) beanDefinition).validate()

主要检测是否在配置文件中设置了factoryMethodName,

    public void validate() throws BeanDefinitionValidationException {
if (!getMethodOverrides().isEmpty() && getFactoryMethodName() != null) {
throw new BeanDefinitionValidationException(
"Cannot combine static factory method with method overrides: " +
"the static factory method must create the instance");
} if (hasBeanClass()) {
prepareMethodOverrides();
}
}

19、将beanDefinition放入map,beanName放入list,将alias也放到map

this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);

20、触发注入的事件,此处触发了第四部分注入的监听事件,由于创建时候使用的XmlBeanDefinitionReader的new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, getNamespaceHandlerResolver());方法中的EmptyReaderEventListener方法均为空,所以不做任何事情,此处我们也知道了,可以在自定义reader的子类中注入新的监听类,可以得到其他通知

// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

三、总结

1、到此为止,整个ioc的配置文件读取和解析过程以及完成,此处只有一个bean,如果有多个,会从根继续读取下一个子节点进行解析。,最终我们在DefaultListableBeanFactory里得到了bean的map,beanName的list,alias的map等,通过这些容器就可以得到配置文件中bean的内容,但如何在调用getBean的时候获得到需要的bean,需要我们在以后的学习中进一步阅读源码

2、在源码中我们可以看到除了自己配置的一些简单信息外,spring还有大量的默认配置和全局配置,这些配置的不同,是spring的各种功能的入口和配置,需要我们进行关注

3、学习过程中,还有很多暂时不明白的功能,只能先跳过

[spring源码学习]二、IOC源码——配置文件读取的更多相关文章

  1. Spring 循环引用(二)源码分析

    Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环引用相关文章: & ...

  2. Spring Boot REST(二)源码分析

    Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...

  3. Dubbo源码学习(二)

    @Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...

  4. Spring Ioc源码分析系列--Ioc源码入口分析

    Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...

  5. Spring Boot 项目学习 (二) MySql + MyBatis 注解 + 分页控件 配置

    0 引言 本文主要在Spring Boot 基础项目的基础上,添加 Mysql .MyBatis(注解方式)与 分页控件 的配置,用于协助完成数据库操作. 1 创建数据表 这个过程就暂时省略了. 2 ...

  6. Spring源码学习之IOC实现原理(二)-ApplicationContext

    一.Spring核心组件结构 总的来说Spring共有三个核心组件,分别为Core,Context,Bean.三大核心组件的协同工作主要表现在 :Bean是包装我们应用程序自定义对象Object的,O ...

  7. 【 js 基础 】【 源码学习 】backbone 源码阅读(二)

    最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 ...

  8. spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign

    基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...

  9. python 协程库gevent学习--gevent源码学习(二)

    在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...

随机推荐

  1. iOS-关于使用其他应用打开本应用文档

    简介:本片文章是对官方文档的翻译,非常的感谢文章的翻译者:颐和园 官方地址:Document Interaction Programming Topics for iOS 文章的介绍内容: ***** ...

  2. Android控制ScrollView滚动

    有两种办法,第一种,使用scrollTo(),这个方法不需要handler,直接调用就行 第二种方式,使用fullScrol() 下面我们看一下这个函数: scrollView.fullScroll( ...

  3. python之路十四

    概述 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他,这样就可以让浏览器 ...

  4. 码途有道----基于系统观的核心能力构建-by-韩宏老师

    原文链接:http://blog.sina.com.cn/s/blog_7d5a09f90102v341.html 有感于同学们在大学中如何学习计算机技术有些感概,将我书(老码识途)中的序言整理了一下 ...

  5. CF576E

    *在#里发他一直WA这道CF题,然后我就去看了看,感觉还挺有趣的,那我就在这里整理一下我的思路..毕竟一边听歌.. 题意: 给个图,每条边初始无色,每次给一个询问(e,c)表示把e涂成颜色c,如果此时 ...

  6. Ternary Expression Parser

    Given a string representing arbitrarily nested ternary expressions, calculate the result of the expr ...

  7. Total Hamming Distance

    The Hamming distance between two integers is the number of positions at which the corresponding bits ...

  8. win10 设置声卡驱动 --- 解决喇叭没有声音!

    win10 设置声卡驱动 --- 解决喇叭没有声音! 1)安装驱动,必须能够在:"控制面板\硬件和声音" 下找到安装好的: "Realtek高清晰音频管理器" ...

  9. Java字节流和字符流区别

    1.字节流:直接操作文件本身. 2.字符流:通过缓冲区来操作文件. 所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节 ...

  10. OE学习笔记流水

    Terrain.cpp中的getWorldCoordsUnderMouse函数,进行标记.