Spring 源码分析之IOC容器的基本实现

说到Spring 大家可能都知道,也都用过 ,那么今天就给大家来解析一下,Spring 从解析Xml 到最后的创建Bean 都用到了 什么类,用到了那些模式>

 BeanFactor beanfactor = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

功能分析:
1.读取配置文件beanFactory.xml
2.根据beanFacotry.xml 找到配置所对应的类,并且初始化。
3.调用后实例化。

Spring 解析Xml读取Bean 核心类介绍

-DefaultListableBeanFacotry 和XmlBeanDefintionReader
1.XmlBeanFactory
XmlBeanFacotry继承自DefaultListableBeanFactory ,而DefaultListableBeanFacotry是整个加载bean的核心部分,是Spring注册及加载bean的默认实现,而XmlBeanfactory与DefaultListableBeanFactory不同的地方在于 XmlBeanFactory 实现了自定义的Xml读取器XmlBeanDefinationReader实现了BeanDefinitionReader读取,
2.XmlBeanDefinitionReader
XmlBeanDefintionReader 读中有资源文件读取,解析以及注册bean 的类 ,如:ResourceLoader,BeanDefinitionReader DocumentLoader 等等我就不列举了

容器的基础XmlBeanFactory

1.对xml文件的封装
Spring 通过对配置文件的读取封装到对应的类中,如,New ClassPathResource("beanfacotry.xml");那么ClassPathResouce完成了什么功能呢?
1.将当前URL封装到Resouce 我们知道URL 没有默认定义ClassPath或者SerevletContext 等资环的handler,此时Spring 自己底层封装了Resouce 即URl封装底层资源。代码如下

public interface Resource extends InputStreamSource{
   boolean exists();
   boolean isReadable();
   boolean isOpen();    
   URL getURL() throws IOException;
   URI getURI() throws IOException;
   .......
}

这里我们可以借鉴一下,Spring读取xml的方式
Resource resource = new ClassPathResource("bean.xml");
InputStream inputStream = resouce.getInputStream();

Spring 是如何通过 .xml 得到ClassPathResource对象呢 ?其实很简单,我们用IDEA编辑器打开ClassPathResource 对象看看

      public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        } else {
            is = this.classLoader.getResourceAsStream(this.path);
        }

        if (is == null) {
            throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
        } else {
            return is;
        }
    }

当通过Resource 相关类对xml文件进行封装后具体对配置文件的读取工作就交个XmlBeanDefinitionReader来处理了。
我们可以用IDEA 打开XmlBeanFacotry debug下

  public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);//调用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) 的构造方法
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);//调用父类的构造器初始化忽略给定接口的装配功能
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);//loadBeanDefinitions 才是加载bean的真正位置
    }
}

调用loadBeanDefinitions 加载Bean 跟踪一下

XmlBeanDefinitionReader.java

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
        //通过属性来记录 已近加载的资源
        Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }

        if (!((Set)currentResources).add(encodedResource)) {
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        } else {
            int var6;
            try {
                InputStream inputStream = encodedResource.getResource().getInputStream();//从encodedResource中获取已近封装的Resource对象 并在此从Resource中获取InpuStream();
                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());//设置编码
                    }
                  //  这里才是真正的加载bean的部分
                    var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } finally {
                    inputStream.close();
                }
            } catch (IOException var15) {
                throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
            } finally {
                ((Set)currentResources).remove(encodedResource);
                if (((Set)currentResources).isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }

            }
            return var6;
        }
    }

调用doLoadBeanDefinitions 加载bean 再次进入到doLoadBeanDefinitions 具体的方法中

   protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            int validationMode = this.getValidationModeForResource(resource);//获取xml的验证模式
            Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware());//加载xml文件 获取Document 对象
            return this.registerBeanDefinitions(doc, resource);//获取Document 注册bean信息
        } catch (BeanDefinitionStoreException var5) {
            throw var5;
        } catch (SAXParseException var6) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
        } catch (SAXException var7) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
        } catch (ParserConfigurationException var8) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
        } catch (IOException var9) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
        } catch (Throwable var10) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
        }
    }

1.获取xml验证模式
我们知道xml验证 有种验证方式XSD(Xml Schemas Definition)和DTD (Document type Definition)两种验证方式

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">

这种为Dtd的声明方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd">
  </beans>

这种为Xsd 的声明方式
其中,如果没有指明 声明方式 就是用默认的声明方式

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = this.getValidationMode();
        if (validationModeToUse != 1) { //如果手动了制定了验证模式则使用指定的验证模式
            return validationModeToUse;
        } else { //否则就是 默认的验证方式使用自动检测
            int detectedMode = this.detectValidationMode(resource);
            return detectedMode != 1 ? detectedMode : 3;
        }
    }

   protected int detectValidationMode(Resource resource) {
        if (resource.isOpen()) {
            throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance.");
        } else {
            InputStream inputStream;
            try {
                inputStream = resource.getInputStream();
            } catch (IOException var5) {
                throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", var5);
            }

            try {
                return this.validationModeDetector.detectValidationMode(inputStream);//这里才是真正的获取验证模式
            } catch (IOException var4) {
                throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
            }
        }
    }

那我们可以看detectValidationMode 里面是如何处理的

    public int detectValidationMode(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        try {
            boolean isDtdValidated = false;
            while(true) {
                String content;
                if ((content = reader.readLine()) != null) {
                    content = this.consumeCommentTokens(content); 
                    if (this.inComment || !StringUtils.hasText(content)) {//如果是注释或者行或者是空的则跳过
                        continue;
                    }
                    if (this.hasDoctype(content)) {    // hasDoctype   如果 content.indexOf("DOCTYPE") > -1; 则为DTD 验证
                        isDtdValidated = true;
                    } else if (!this.hasOpeningTag(content)) {//否则就是XSD
                        continue;
                    }
                }

                int var6 = isDtdValidated ? 2 : 3;
                return var6;
            }
        } catch (CharConversionException var9) {
            ;
        } finally {
            reader.close();
        }

        return 1;
    }

获取Document

经过了获取验证模式 就可以进行 Document的加载了 同样在XmlBeanFacotry类中,对于文档的读取并没有真正的而在这里解析而是委托给 了DocumentLoader去执行

 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        //创建DocumentBuilderFactory 对象 其中调用newsInstance 方法//FactoryFinder.find( 
                DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
                "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");返回DocumentBuilderFactory 的实现类 DocumentBuilderFactoryImpl

        DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }

        DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);//根据DocumentBuilderFactoryImpl 创建DocumentBuilder对象
        return builder.parse(inputSource);//真正的创建Document 对象
    }

1.加载Document 这里也简单 就是获取DocumentBuilderFactory 的实现类DocumentBuilderFactoryImpl
2.根据DocumentBuilderFactoryImpl 获取DocumentBuilder
3.通过DocumentBuilder 来获取Document

这里有必要说明一下 在调用loadDocument 之前调用的getEntityResolver()方法
调用getEntityResolver 方法 返回 EntityResolver 对象,EntityResolver的作用
1.获取SystemId 和publicId
如果 为XSD 的验证模式
SystemId : http://www.springframework.org/schema/beans/spring-beans.xsd
publicId :null
DTD的验证模式
SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
publicId: //SPRING//DTD BEAN 2.0//EN

解析及注册BeanDefinitions

当把xml文件转换成Document 后,接下来 就是提取和注册bean 了

 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();//实例化BeanDefinitionDocumentReader
        documentReader.setEnvironment(this.getEnvironment());//将环境变量设置其中
        int countBefore = this.getRegistry().getBeanDefinitionCount();//记录统计前BeanDefinitions的加载个数
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));//加载和注册bean
        return this.getRegistry().getBeanDefinitionCount() - countBefore;//记录统计后BeanDefinitions的加载个数
    }

其中document 是上一节 loadDocument加载转换出来的,在这个方法中很好的体现了 单一制则原则,将逻辑委托给单一的类处理, 而这个逻辑处理类就是BeanDefinitionReaderDocumentReader 。而 BeanDefinitionReaderDocumentReader 的类型已经是DefaultBeanDefinitionReaderDocumentReader 了,进入registerBeanDefinitions 的方法 其实目的只有一个就是提取Root

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        this.logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        this.doRegisterBeanDefinitions(root);//这里才是真正的注册bean
    }

山路18弯 ,如果说我们以前一直是XMl解析的准备阶段,那么现在, 我们真正的开始解析了,

 protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute("profile");//处理profile 
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "environment property must not be null");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }

        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createHelper(this.readerContext, root, parent);
        this.preProcessXml(root); //解析前 对bean进行处理 留给子类实现
        this.parseBeanDefinitions(root, this.delegate);//这里专门解析
        this.postProcessXml(root);//解析后,对bean进行处理 留给子类实现
        this.delegate = parent;
    }

通过上面的代码我们看到了处理流程,首先是对profile的处理,然后看是解析,可是当我们更加preProcessXml和postProcessXml的时候 发现里面是空的,,如果你有了解过设计模式 那么这就是模板方法模式,如果子类需要对加载bean 之前和加载 bean 之后进行处理 则子类需要继承DefaultBeanDefinitionDocumentReader

处理profile 标签
<beans profile="dev"></beans>
<beans profile="product"></beans>

继承到web环境是,在web.xml 加入以下代码,

<context-param>
  <param-name>Spring.profile.active</param-name>
  <param-value>dev</param-value>
</context-param>

从这里我们就可以看出 在配置文件中部署两套配置 来试用于测试环境和生产环境,这样就可以方便的进行切换

解析并且注册BeanDefinition
 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)) {
                        this.parseDefaultElement(ele, delegate);//对bean进行处理
                    } else {
                        delegate.parseCustomElement(ele);//对bean进行处理
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }

在Springde Xml 的配置中有两大类Bean声明一种是默认的 如:

<bean id ="user" class="com.zhh.User"/>

另一种就是自定义的
<tx:annotation-driven/>
如果是自定以的Spring知道该怎么做,则采用this.parseDefaultElement方法进行解析,否则则采用 delegate.parseCustomElement(ele)方法 对自定义 命名空间进行解析,而判断是否默认命名空间还是自定义空间的办法是用 delegate.isDefaultNamespace(ele) 中的 node.getNamespaceURI() 来获取命名空间,并且于Spring中固定的命名空间进行对比http://www.springframework.org/schema/beans,如果一样则是默认的 否则 则不是

对于默认标签解析于自定义标签解析 我们下次再说

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容