Spring 源碼分析之IOC容器的基本實(shí)現(xiàn)

說到Spring 大家可能都知道,也都用過 ,那么今天就給大家來解析一下,Spring 從解析Xml 到最后的創(chuàng)建Bean 都用到了 什么類,用到了那些模式>

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

功能分析:
1.讀取配置文件beanFactory.xml
2.根據(jù)beanFacotry.xml 找到配置所對(duì)應(yīng)的類,并且初始化。
3.調(diào)用后實(shí)例化。

Spring 解析Xml讀取Bean 核心類介紹

-DefaultListableBeanFacotry 和XmlBeanDefintionReader
1.XmlBeanFactory
XmlBeanFacotry繼承自DefaultListableBeanFactory ,而DefaultListableBeanFacotry是整個(gè)加載bean的核心部分,是Spring注冊(cè)及加載bean的默認(rèn)實(shí)現(xiàn),而XmlBeanfactory與DefaultListableBeanFactory不同的地方在于 XmlBeanFactory 實(shí)現(xiàn)了自定義的Xml讀取器XmlBeanDefinationReader實(shí)現(xiàn)了BeanDefinitionReader讀取,
2.XmlBeanDefinitionReader
XmlBeanDefintionReader 讀中有資源文件讀取,解析以及注冊(cè)bean 的類 ,如:ResourceLoader,BeanDefinitionReader DocumentLoader 等等我就不列舉了

容器的基礎(chǔ)XmlBeanFactory

1.對(duì)xml文件的封裝
Spring 通過對(duì)配置文件的讀取封裝到對(duì)應(yīng)的類中,如,New ClassPathResource("beanfacotry.xml");那么ClassPathResouce完成了什么功能呢?
1.將當(dāng)前URL封裝到Resouce 我們知道URL 沒有默認(rèn)定義ClassPath或者SerevletContext 等資環(huán)的handler,此時(shí)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對(duì)象呢 ?其實(shí)很簡單,我們用IDEA編輯器打開ClassPathResource 對(duì)象看看

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

當(dāng)通過Resource 相關(guān)類對(duì)xml文件進(jìn)行封裝后具體對(duì)配置文件的讀取工作就交個(gè)XmlBeanDefinitionReader來處理了。
我們可以用IDEA 打開XmlBeanFacotry debug下

  public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);//調(diào)用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) 的構(gòu)造方法
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);//調(diào)用父類的構(gòu)造器初始化忽略給定接口的裝配功能
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);//loadBeanDefinitions 才是加載bean的真正位置
    }
}

調(diào)用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對(duì)象 并在此從Resource中獲取InpuStream();
                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());//設(shè)置編碼
                    }
                  //  這里才是真正的加載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;
        }
    }

調(diào)用doLoadBeanDefinitions 加載bean 再次進(jìn)入到doLoadBeanDefinitions 具體的方法中

   protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            int validationMode = this.getValidationModeForResource(resource);//獲取xml的驗(yàn)證模式
            Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware());//加載xml文件 獲取Document 對(duì)象
            return this.registerBeanDefinitions(doc, resource);//獲取Document 注冊(cè)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驗(yàn)證模式
我們知道xml驗(yàn)證 有種驗(yàn)證方式XSD(Xml Schemas Definition)和DTD (Document type Definition)兩種驗(yàn)證方式

<!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 的聲明方式
其中,如果沒有指明 聲明方式 就是用默認(rèn)的聲明方式

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = this.getValidationMode();
        if (validationModeToUse != 1) { //如果手動(dòng)了制定了驗(yàn)證模式則使用指定的驗(yàn)證模式
            return validationModeToUse;
        } else { //否則就是 默認(rèn)的驗(yàn)證方式使用自動(dòng)檢測
            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);//這里才是真正的獲取驗(yàn)證模式
            } 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 驗(yàn)證
                        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

經(jīng)過了獲取驗(yàn)證模式 就可以進(jìn)行 Document的加載了 同樣在XmlBeanFacotry類中,對(duì)于文檔的讀取并沒有真正的而在這里解析而是委托給 了DocumentLoader去執(zhí)行

 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        //創(chuàng)建DocumentBuilderFactory 對(duì)象 其中調(diào)用newsInstance 方法//FactoryFinder.find( 
                DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
                "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");返回DocumentBuilderFactory 的實(shí)現(xiàn)類 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);//根據(jù)DocumentBuilderFactoryImpl 創(chuàng)建DocumentBuilder對(duì)象
        return builder.parse(inputSource);//真正的創(chuàng)建Document 對(duì)象
    }

1.加載Document 這里也簡單 就是獲取DocumentBuilderFactory 的實(shí)現(xiàn)類DocumentBuilderFactoryImpl
2.根據(jù)DocumentBuilderFactoryImpl 獲取DocumentBuilder
3.通過DocumentBuilder 來獲取Document

這里有必要說明一下 在調(diào)用loadDocument 之前調(diào)用的getEntityResolver()方法
調(diào)用getEntityResolver 方法 返回 EntityResolver 對(duì)象,EntityResolver的作用
1.獲取SystemId 和publicId
如果 為XSD 的驗(yàn)證模式
SystemId : http://www.springframework.org/schema/beans/spring-beans.xsd
publicId :null
DTD的驗(yàn)證模式
SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
publicId: //SPRING//DTD BEAN 2.0//EN

解析及注冊(cè)BeanDefinitions

當(dāng)把xml文件轉(zhuǎn)換成Document 后,接下來 就是提取和注冊(cè)bean 了

 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();//實(shí)例化BeanDefinitionDocumentReader
        documentReader.setEnvironment(this.getEnvironment());//將環(huán)境變量設(shè)置其中
        int countBefore = this.getRegistry().getBeanDefinitionCount();//記錄統(tǒng)計(jì)前BeanDefinitions的加載個(gè)數(shù)
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));//加載和注冊(cè)bean
        return this.getRegistry().getBeanDefinitionCount() - countBefore;//記錄統(tǒng)計(jì)后BeanDefinitions的加載個(gè)數(shù)
    }

其中document 是上一節(jié) loadDocument加載轉(zhuǎn)換出來的,在這個(gè)方法中很好的體現(xiàn)了 單一制則原則,將邏輯委托給單一的類處理, 而這個(gè)邏輯處理類就是BeanDefinitionReaderDocumentReader 。而 BeanDefinitionReaderDocumentReader 的類型已經(jīng)是DefaultBeanDefinitionReaderDocumentReader 了,進(jìn)入registerBeanDefinitions 的方法 其實(shí)目的只有一個(gè)就是提取Root

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

山路18彎 ,如果說我們以前一直是XMl解析的準(zhǔn)備階段,那么現(xiàn)在, 我們真正的開始解析了,

 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); //解析前 對(duì)bean進(jìn)行處理 留給子類實(shí)現(xiàn)
        this.parseBeanDefinitions(root, this.delegate);//這里專門解析
        this.postProcessXml(root);//解析后,對(duì)bean進(jìn)行處理 留給子類實(shí)現(xiàn)
        this.delegate = parent;
    }

通過上面的代碼我們看到了處理流程,首先是對(duì)profile的處理,然后看是解析,可是當(dāng)我們更加preProcessXml和postProcessXml的時(shí)候 發(fā)現(xiàn)里面是空的,,如果你有了解過設(shè)計(jì)模式 那么這就是模板方法模式,如果子類需要對(duì)加載bean 之前和加載 bean 之后進(jìn)行處理 則子類需要繼承DefaultBeanDefinitionDocumentReader

處理profile 標(biāo)簽
<beans profile="dev"></beans>
<beans profile="product"></beans>

繼承到web環(huán)境是,在web.xml 加入以下代碼,

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

從這里我們就可以看出 在配置文件中部署兩套配置 來試用于測試環(huán)境和生產(chǎn)環(huán)境,這樣就可以方便的進(jìn)行切換

解析并且注冊(cè)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);//對(duì)bean進(jìn)行處理
                    } else {
                        delegate.parseCustomElement(ele);//對(duì)bean進(jìn)行處理
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }

在Springde Xml 的配置中有兩大類Bean聲明一種是默認(rèn)的 如:

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

另一種就是自定義的
<tx:annotation-driven/>
如果是自定以的Spring知道該怎么做,則采用this.parseDefaultElement方法進(jìn)行解析,否則則采用 delegate.parseCustomElement(ele)方法 對(duì)自定義 命名空間進(jìn)行解析,而判斷是否默認(rèn)命名空間還是自定義空間的辦法是用 delegate.isDefaultNamespace(ele) 中的 node.getNamespaceURI() 來獲取命名空間,并且于Spring中固定的命名空間進(jìn)行對(duì)比http://www.springframework.org/schema/beans,如果一樣則是默認(rèn)的 否則 則不是

對(duì)于默認(rèn)標(biāo)簽解析于自定義標(biāo)簽解析 我們下次再說

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容