一、环境准备

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

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. python --> 正则表达式

    在python中使用正则表达式,需要导入 re 模块 一. 元字符,包括 []  {} | ? * +  .  ^ $ \  () . 号:通配符,一个点号就代表一个字符,一般情况下不能通配换行符 \ ...

  2. window server 2008配置FTP服务器550 Access is denied. 问题解决办法

    如果你是按照网上的通用方法,在搭建FTP服务器的时候临时建了一个用户的话,那么这个用户是普通用户,你即便勾选了“读”“写”权限,那么再打开防火墙的情况下,你还是不能上传文件,只能下载.只需要登录到服务 ...

  3. Notepad++源码编译及其分析

    Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然 ...

  4. 【Android】Android如何一进入一个activity就弹出输入法键盘

    在AndroidManife.xml中的Activity配置中加入 android:windowSoftInputMode="stateVisible|adjustResize"

  5. setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式

    <script type="text/javascript"> setInterval(function(){ var myDate = new Date(); var ...

  6. Ajax跨域:jsonp还是CORS

    跨域一般用jsonp,兼容性比较好.CORS是html5最新的XHR第二版本,不支持IE8,IE9,对移动端的支持非常好.但是考虑项目后期这部分会转到同域名下,而且网址不需要支持ie8,ie9,所以我 ...

  7. 设计模式--桥接模式Bridge(结构型)

    一.概述 在软件系统中,某些类型由于自身的逻辑,它具有两个或者多个维度的变化,如何应对这种"多维度的变化",就可以利用桥接模式. 引例: 设想如果要绘制矩形.圆形.椭圆.正方形,我 ...

  8. SAP SMARTFORM 记录实际打印次数

    http://blog.csdn.net/wangjolly/article/details/8334008

  9. C++ Bitstream类

    从raknet上剥下来的 比较适用于前后端通讯,可以对BitStream进行二次封装,方便使用. BitStream.h: #ifndef __BITSTREAM_H #define __BITSTR ...

  10. 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解

    题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...