1. 程式人生 > >Spring原始碼解析筆記1——整體架構+配置檔案讀取

Spring原始碼解析筆記1——整體架構+配置檔案讀取

Spring整體架構

1.Core Container核心容器,包含Core,Beans,Context,Expression Language.Core和Beans提供最基本的IOC(控制反轉)和依賴注入。

  • Core模組主要包含Spring基本框架的核心工具類。
  • Beans模組包含訪問配置檔案,建立和管理bean,以及Inversion of Control/Dependency Injection相關的所有操作。
  • Context在Core和Beans模組的基礎上,提供了類似與JNDI(Java naming directory interface)註冊器的框架式的物件訪問方式。Context模組繼承了Beans的特性,為Spring核心提供了大量擴充套件。
  • Expression Language 模組提供了一個強大的表示式語言用於在執行時查詢和操縱物件。(EL表示式)

2.Data Access/Integration資料訪問/整合,這一層包含JDBC,ORM,OXM,JMS和Transation模組。

  • JDBC模組提供了一個JDBC抽象層,這個模組包含了Spring對JDBC資料訪問進行封裝的所有類。

  • ORM模組是物件-關係對映API,例如:Hibernate,iBatis等。提供了一個互動層,可以混合使用所有Spring提供的特性進行O/R對映。

  • OXM模組提供了一個對Object/XML對映實現的抽象層。
  • JMS(Java Messaging Service) 模組主要包含了一些製造和消費訊息的特性。
  • Transation模組支援程式設計和宣告的事務管理,這些事物類必須實現特定的介面,並且對所有的POJO都適用。

3.Web。Web上下文模組建立在應用程式上下文模組之上,為基於Web的應用程式提供了上下文。所以,Spring框架支援與Struts的整合。還簡化了處理多部分請求以及將請求以及請求引數繫結到域物件的工作。

  • Web模組:提供了基礎的面向Web的整合特性。例如:多檔案上傳,使用servlet listeners初始化IoC容器以及一個面向Web的應用上下文。
  • Web-Servlet:該模組包含Spring的MVC的實現。
  • Web-Struts:該模組提供了對struts的支援。

4.AOP提供了一個面向切面程式設計。
5.Test模組支援使用JUnit和TestNG對Spring元件進行測試。

容器的基本實現(bean)

1.最簡單的例子:

public class MyTestBean{
    private String testStr = "testStr";
    public String getTestStr(){
        return testStr;
    }
    public void setTestStr(String testStr){
        this.testStr = testStr;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<bean id="myTestBean" class="bean.MyTestBean">
</beans>
public class BeanFactoryTest{
    BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
    MyTestBean bean = (MyTestBean)bf.getBean("myTestBean");
    bean.getTestStr();
}

2.上述例項最簡單的架構圖

這裡寫圖片描述

  • ConfigReader:讀取配置檔案,放到記憶體中。
  • reflection:根據配置檔案中的配置進行反射例項化。
  • app:完成程式碼邏輯。

3.時序圖:
這裡寫圖片描述

  • Resource資源的封裝:Java中的配置檔案是通過ClassPathResource進行封裝,經過一系列的處理返回一個Resource類。

3.資源處理相關類圖:

這裡寫圖片描述

  • InputStreamSource:返回任何能返回InputStream的類,比如File,ClassPath下的資源等,返回一個全新的InputStream物件。
public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}
  • Reource抽象了所有Spring內部使用到的底層資源:File,URL,Classpath等,定義了判斷當前資源狀態的方法。
public interface Resource extends InputStreamSource {
    boolean exists();

    boolean isReadable();

    boolean isOpen();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;//建立相對資源

    String getFilename();

    String getDescription();//用於在錯誤資訊中詳細打印出錯的資原始檔
}
  • 剩下的類就是對不同來源的資原始檔都有相應的Reource實現:檔案(FileSystemReource),Classpath資源(ClassPathReource),URL資源(UrlResource),InputStream資源(InputStreamResource),Byte陣列(ByteArrayResource)等。

4.繼續分析XmlBeanFactory:

public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);//核心1
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);//核心2
    }

根據上述的核心1方法super()繼續跟蹤有一個ignoreDependencyInterface方法:

public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
        super(parentBeanFactory);
}

//根據super繼續追蹤
public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) {
        this();
        this.setParentBeanFactory(parentBeanFactory);
}

//根據this()繼續走。
public AbstractAutowireCapableBeanFactory() {

        //下面3個為ignoreDependencyInterface
        this.ignoreDependencyInterface(BeanNameAware.class);
        this.ignoreDependencyInterface(BeanFactoryAware.class);
        this.ignoreDependencyInterface(BeanClassLoaderAware.class);
}

這個方法的作用是是忽略給定介面的自動裝配功能。Spring一大特性,當A中有屬性B,那麼當Spring在獲取A的Bean的時候如果屬性B還沒有被初始化,那麼Spring會自動初始化B。這個方法可以忽略傳入介面的自動裝配。

5.根據上述程式碼可以發現最終載入資源的方法是loadBeanDefinitions(resource),XmlBeanFactory的時序圖如下;載入bean,從loadBeanDefinitions(resource)開始。
這裡寫圖片描述

  • 封裝配置檔案。XmlBeanDefinitionReader首先對Resource使用EncodedResource類進行封裝。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(new EncodedResource(resource));
}
//EncodedResource裡面的主要邏輯體現在getReader方法,如果設定了編碼,就按照設定的編碼作為輸入流的編碼。
public Reader getReader() throws IOException {
        return this.charset != null?new InputStreamReader(this.resource.getInputStream(), this.charset):(this.encoding != null?new InputStreamReader(this.resource.getInputStream(), this.encoding):new InputStreamReader(this.resource.getInputStream()));
}
  • 獲取輸入流。從Resource例項中獲取對應的InputStream並構造InputSource。
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());
        }

        Object 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 var5;
            try {
                InputStream ex = encodedResource.getResource().getInputStream();

                try {
                    InputSource inputSource = new InputSource(ex);
                    if(encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //此處的doLoadBeanDefinitions是這個方法的核心
                    var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                } finally {
                    ex.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 var5;
        }
    }
  • 通過構造的InputSource例項和Resource例項繼續呼叫doLoadBeanDefinitions(inputSource, encodedResource.getResource()).這部分程式碼只做了三件事,1.獲取XML檔案的驗證模式。2.載入XML檔案,並得到對應的Document。3.根據返回的Document註冊Bean資訊。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            //讀取document,下面第8步之前
            Document ex = this.doLoadDocument(inputSource, resource);
            //解析及註冊BeanDefinitions(下面第8步詳細說明)
            return this.registerBeanDefinitions(ex, resource);
        } catch (BeanDefinitionStoreException var4) {
            throw var4;
        } catch (SAXParseException var5) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
        } catch (SAXException var6) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
        } catch (ParserConfigurationException var7) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
        } catch (IOException var8) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
        } catch (Throwable var9) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
        }
    }

6.兩種XML驗證模式
         DTD即文件型別定義,是一種XML約束模式語言,是XML檔案的驗證機制,屬於XML檔案組成的一部分。DTD 是一種保證XML文件格式正確的有效方法,可以通過比較XML文件和DTD檔案來看文件是否符合規範,元素和標籤使用是否正確。 一個 DTD文件包含:元素的定義規則,元素間關係的定義規則,元素可使用的屬性,可使用的實體或符號規則。

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

         XSD(XML Schemas Definition)。XML Schema描述了XML文件的結構。可以用一個指定的XML Schema來驗證某個XML文件所允許的結構和內容,並可以用通用的XML解析器解析它。

 <xsd:schema xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"  
targetNamespace="http://www.springframework.org/schema/beans">  

7.驗證模式的讀取並載入Document

//上述的this.documentLoader.loadDocument方法。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
    }


//根據documentLoader繼續跟蹤,找到介面:
public interface DocumentLoader {
    Document loadDocument(InputSource var1, EntityResolver var2, ErrorHandler var3, int var4, boolean var5) throws Exception;
}


//根據loadDocument繼續走,找到該方法的實現:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
        if(logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }

        DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }
//loadDocument呼叫this.documentLoader.loadDocument時的每一個引數來源
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}

//各個引數以及其來源說明
/**
    1.inputSource直接傳入。
**/


/**
    2.EntityResolver entityResolver:這個引數是專案本身可以提供一個如何尋找DTD宣告的方法,可以由程式來實現並返回給SAX(Simple API for XML)即可,這樣可以避免通過網路來尋找相應的宣告。
**/

//EntityResolver介面,裡面只聲明瞭唯一方法。
public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId,String systemId)throws SAXException, IOException;
}

//通過this.getEntityResolver()獲得引數entityResolver。
protected EntityResolver getEntityResolver() {
        if(this.entityResolver == null) {
            ResourceLoader resourceLoader = this.getResourceLoader();
            if(resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                //預設執行這一步。
                this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
            }
        }

        return this.entityResolver;
}

/**
    根據DelegatingEntityResolver進行追蹤,DelegatingEntityResolver類有一個resolveEntity方法,可以看出來不同的驗證模式,使用了不同的解析器:
**/
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if(systemId != null) {
            if(systemId.endsWith(".dtd")) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }

            if(systemId.endsWith(".xsd")) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }

        return null;
    }

/**
    3.errorHandler這個引數是錯誤處理,直接傳入。
**/

/**
    4.int validationMode這個引數是驗證模式的讀取.獲取該引數的方法如下。
**/

//this.getValidationModeForResource(resource)
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 {
                //此處又委託給了detectValidationMode執行
                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);
            }
        }
}


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

        byte content;
        try {
            boolean ex = false;

            while(true) {
                String content1;
                if((content1 = reader.readLine()) != null) {
                    content1 = this.consumeCommentTokens(content1);
                    if(this.inComment || !StringUtils.hasText(content1)) {
                        continue;
                    }

                    if(this.hasDoctype(content1)) {
                        ex = true;
                    } else if(!this.hasOpeningTag(content1)) {
                        continue;
                    }
                }

                int var5 = ex?2:3;
                return var5;
            }
        } catch (CharConversionException var9) {
            content = 1;
        } finally {
            reader.close();
        }

        return content;
}

8.解析及註冊BeanDefinitions

  • registerBeanDefinitions方法:
/**
Document doc是前面的loadDocument載入轉換出來的.
**/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        //記錄統計前BeanDefinition的載入個數。
        int countBefore = this.getRegistry().getBeanDefinitionCount();

//載入以及註冊bean,documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        //記錄本次載入的BeanDefinition個數
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
}


//裡面的核心處理類BeanDefinitionDocumentReader是一個介面
public interface BeanDefinitionDocumentReader {
    void registerBeanDefinitions(Document var1, XmlReaderContext var2) throws BeanDefinitionStoreException;
}


//該類裡面的registerBeanDefinitions方法實現。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        this.logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        //核心方法
        this.doRegisterBeanDefinitions(root);
}

//doRegisterBeanDefinitions方法
protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        if(this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute("profile");
            if(StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if(this.logger.isInfoEnabled()) {
                        this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                    }

                    return;
                }
            }
        }

        //解析前處理,專門留給子類留的實現
        this.preProcessXml(root);
        //核心方法
        this.parseBeanDefinitions(root, this.delegate);
        //解析後處理,專門留給子類留的實現
        this.postProcessXml(root);
        this.delegate = parent;
    }
  • 關於profile屬性的使用,用於不同環境的配置。例如,我們可以在開發環境和生產環境設定不同的資料庫配置檔案,具體使用哪個取決於Web上下文中的配置。
<beans profile="dev">
         <jdbc:embedded-database id="dataSource">
             <jdbc:script location="classpath:xxx.sql"/>
            <jdbc:script location="classpath:yyy.sql"/>
        </jdbc:embedded-database>
     </beans>
<!-- 在Web上下文中設定profile的預設值 -->
   <context-param>
       <param-name>spring.profiles.default</param-name>
       <param-value>dev</param-value>
   </context-param>
  • 解析並註冊BeanDefinition通過上述的核心方法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)) {
                         //對預設標籤處理 
                         this.parseDefaultElement(ele, delegate);
                    } else {
                         //對自定義標籤處理      
                         delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }