1. 程式人生 > >Spring原始碼之bean的基本解析

Spring原始碼之bean的基本解析

先看這樣一段兒程式碼:

spring bean xml配置

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

定義一個測試bean:

public class MyTestBean {

    private String myBean = "hello bean";

    public String getMyBean() {
        return myBean;
    }

    public MyTestBean setMyBean(String myBean) {
        this
.myBean = myBean; return this; } }

獲得這個bean,並執行

public class ContextBeanTest {

    /**
     * 用XmlBeanFactory這個方式獲得bean,現在已經不用這個方式了
     */
    @Test
    public void test() {
        XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-base.xml"));
        MyTestBean myTestBean = (MyTestBean) xmlBeanFactory.getBean("myTestBean"
); System.out.println(myTestBean.getMyBean()); } }

列印結果很明顯.就是:hello bean.

DefaultListableBeanFactory

類似於上面直接使用XmlBeanFactory的方式其實已經過時了,目前大家都用ApplicationContext,那麼ApplicationContext和XmlBeanFactory有什麼關係?簡單來說,可以理解為ApplicationContext是在XmlBeanFactory基礎上的擴充套件,前者擁有後者的全部功能,而且在其基礎上做了封裝.在ApplicationContext初始化的過程中,它是獲得了BeanFactory的示例,並享受其功能的,在那裡,XmlBeanFactory會獲得新生~

研究spring,最好的辦法就是clone原始碼,自己跟著示例來看一下.到spring官網,clone下程式碼,eclipse和IDEA都有相應的.md格式的安裝指南或執行指令碼,除了耗時一點外,沒別的難處,我把我的spring原始碼放到了我的github上,會一直更新原始碼的中文註釋,感興趣的可以去看看spring原始碼中文註釋

這裡的程式碼在spring.beans的jar包,或者spring-beans這個工程內.

可以看一下XmlBeanFactory這個類:

public class XmlBeanFactory extends DefaultListableBeanFactory

繼承自DefaultListableBeanFactory

@SuppressWarnings("serial")
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

這個類是是整個bean載入的核心部分,是spring註冊及載入的預設實現,他的子類,XmlBeanFactory和他不同的地方在於,XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,當前類繼承了AbstractAutowireCapableBeanFactory,並且實現了ConfigurableListableBeanFactory等.
下面給出一個相關的UML類圖:
這裡寫圖片描述

這個圖基本從XmlBeanFactory繼承了DefaultListableBeanFactory開始,把這塊兒的脈絡畫了出來,過程很痛苦,結果很好,下面介紹圖中各個類的作用:

  • AliasRegistry 定義對alias的操作
  • SimpleAliasRegistry 實現了AliasRegistry ,map來儲存
  • SingletonBeanRegistry 單例的註冊和獲取
  • BeanFactory 定義和獲取bean,核心介面
  • DefaultSingletonBeanRegistry SingletonBeanRegistry的預設實現
  • BeanDefinitionRegistry BeanDefinition的增刪蓋茶
  • FactoryBeanRegistrySupport 在DefaultSingletonBeanRegistry的基礎上,擴充套件了一些功能,比如對FactoryBean的處理
  • ConfigurableBeanFactory 提供配置factory的方法
  • ListableBeanFactory 根據各種條件獲取bean的清單
  • AbstractBeanFactory 由圖中可知,綜合了FactoryBeanRegistrySupport和ConfigurableBeanFactory
  • ConfigurableListableBeanFactory beanFactory配置清單,指定忽略型別和介面

XmlBeanDefinitionReader

xmlBeanFactory對DefaultListableBeanFactory進行了擴充套件,作用主要是從xml中讀取bean的定義.和父類主要的不同,在於XmlBeanDefinitionReader.

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

xmlBeanFactory的屬性reader用的是XmlBeanDefinitionReader.下面看這個類,首先,UML圖:
這裡寫圖片描述
相關類的介紹:

  • AbstractBeanDefinitionReader 實現EnvironmentCapable和BeanDefinitionReader
  • EnvironmentCapable 定義獲取相關環境
  • BeanDefinitionReader 定義資原始檔,讀取或轉換BeanDefinition

XmlBeanFactory的核心實現

說這個之前,對上述兩個類:DefaultListableBeanFactory和XmlBeanDefinitionReader做了一個概括的分析,下面,從細節處來看XmlBeanFactory.
在我們的測試類:javaXmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-base.xml"));程式碼中XmlBeanFactory(new ClassPathResource(“spring-base.xml”)),構造方法的呼叫看下面:

public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }
//最終呼叫這個
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

this.reader.loadBeanDefinitions(resource);這個才是資源載入的真正實現!點進去:

    /**
     * 進來第一步,將resource封裝成EncodedResource型別
     */
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

繼續點進去:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
        //resourcesCurrentlyBeingLoaded的初始化是new NamedThreadLocal<>("XML bean definition resources currently being loaded");
        //通過ThreadLocal來儲存屬性的值
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            //獲取Resource物件,並獲取流
            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();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

進去核心方法”doLoadBeanDefinitions”:

    /**
     * 排除一堆catch,其實程式碼沒有幾句
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
        //方法一,驗證xml的驗證模式,載入xml獲取對相應的Document
            Document doc = doLoadDocument(inputSource, resource);
        ////根據返回的DOC,註冊bean
            return registerBeanDefinitions(doc, resource);
        }

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    //驗證xml的驗證模式,載入xml獲取對相應的Document
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), 
        this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
    }

上面主要做了:

  • 獲取xml的驗證模式
  • 獲得xml物件的Document
  • 根據返回的doc物件,註冊bean

這些基本就是spring容器基礎部分的實現,當然,第三部應該是最核心的部分,前兩部只是他的基礎工作,方法傳進去了一個doc,一個資源,doLoadDocument又獲得了驗證模式,那麼,接下來的工作,就是載入bean了,這應該是一個很複雜的過程,至於文件的驗證模式,主要有DTD,XSD兩種,有興趣可以研究一下

下面進registerBeanDefinitions(doc, resource);

public int  registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //例項化BeanDefinitionDocumentReader,用了DefaultBeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //當前bean定義的個數
        int countBefore = getRegistry().getBeanDefinitionCount();
        //載入及註冊bean
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //僅返回本次的載入bean個數
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

註釋中說了例項化BeanDefinitionDocumentReader,用了DefaultBeanDefinitionDocumentReader,那下面進入他的egisterBeanDefinitions(doc, createReaderContext(resource))方法:

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        //獲取檔案的root
        Element root = doc.getDocumentElement();
        //將root節點,傳入下面方法
        doRegisterBeanDefinitions(root);
    }
protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        //delegate屬性賦值給一變數parent,初始化parent為new BeanDefinitionParserDelegate(readerContext);
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        //判斷節點的名稱空間是否是http://www.springframework.org/schema/beans,如果是,進入
        if (this.delegate.isDefaultNamespace(root)) {
            //準備處理profile屬性,這個屬性不常用
            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;
                }
            }
        }
        //protected的空方法,叫給子類定製一些自己的實現
        preProcessXml(root);
        //繼續點進去
        parseBeanDefinitions(root, this.delegate);
        //protected的空方法,叫給子類定製一些自己的實現
        postProcessXml(root);

        this.delegate = parent;
    }

這個類裡面,很多方法都是protected,從這可以看出spring這塊的面向繼承的思想.其中,<beans>標籤的profile屬性,我們並不常用,當然,可以用這個屬性,配合web.xml檔案,來設定dev,test,prod各種不同環境的bean配置.下面進入程式碼:parseBeanDefinitions(root, this.delegate);

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            //獲得beans節點的子節點
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                //如果子節點的element
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        //bean的解析
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //bean的解析
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

如果是預設的名稱空間,則呼叫上面的bean解析,如果不是,呼叫下面的bean解析,預設的話,spring會正常的去解析,如果不是預設的,那麼應該是一個稍顯複雜的過程,需要重寫一些類,或者實現一些介面,從這開始,其實就進入了spring對預設的標籤的一些解析,非預設的暫時不想去看,下一篇,會寫到spring的預設標籤解析,然後就是bean的載入,儲存,獲取等,最後就是ApplicationContext的一些實現,這個主線才算略顯完整一些.