1. 程式人生 > >【初探Spring】——Spring IOC(三):初始化過程—Resource定位

【初探Spring】——Spring IOC(三):初始化過程—Resource定位

我們知道Spring的IoC起到了一個容器的作用,其中裝得都是各種各樣的Bean。同時在我們剛剛開始學習Spring的時候都是通過xml檔案來定義Bean,Spring會某種方式載入這些xml檔案,然後根據這些資訊繫結整個系統的物件,最終組裝成一個可用的基於輕量級容器的應用系統。

Spring IoC容器整體可以劃分為兩個階段,容器啟動階段,Bean例項化階段。其中容器啟動階段蛀牙包括載入配置資訊、解析配置資訊,裝備到BeanDefinition中以及其他後置處理,而Bean例項化階段主要包括例項化物件,裝配依賴,生命週期管理已經註冊回撥。下面LZ先介紹容器的啟動階段的第一步,即定位配置檔案。

我們使用程式設計方式使用DefaultListableBeanFactory時,首先是需要定義一個Resource來定位容器使用的BeanDefinition。

ClassPathResource resource = new ClassPathResource("bean.xml");

通過這個程式碼,就意味著Spring會在類路徑中去尋找bean.xml並解析為BeanDefinition資訊。當然這個Resource並不能直接被使用,他需要被BeanDefinitionReader進行解析處理(這是後面的內容了)。

對於各種applicationContext,如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext等等,我們從這些類名就可以看到他們提供了那些Resource的讀入功能。下面我們以FileSystemXmlApplicationContext為例來闡述Spring IoC容器的Resource定位。

先看FileSystemXmlApplicationContext繼承體系結構:


從圖中可以看出FileSystemXMLApplicationContext繼承了DefaultResourceLoader,具備了Resource定義的BeanDefinition的能力,其原始碼如下:

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
    /**
     * 預設建構函式
     */
    public FileSystemXmlApplicationContext() {
    }

    public FileSystemXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }

    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }
    
    //核心構造器
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

    //通過構造一個FileSystemResource物件來得到一個在檔案系統中定位的BeanDefinition
    //採用模板方法設計模式,具體的實現用子類來完成
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

}

AbstractApplicationContext中的refresh()方法是IoC容器初始化的入口,也就是說IoC容器的初始化是通過refresh()方法來完成整個呼叫過程的。在核心構造器中就對refresh進行呼叫,通過它來啟動IoC容器的初始化工作。getResourceByPath為一個模板方法,通過構造一個FileSystemResource物件來得到一個在檔案系統中定位的BeanDEfinition。getResourceByPath的呼叫關係如下(部分):


refresh為初始化IoC容器的入口,但是具體的資源定位還是在XmlBeanDefinitionReader讀入BeanDefinition時完成,loadBeanDefinitions() 載入BeanDefinition的載入。

protected final void refreshBeanFactory() throws BeansException {
        //判斷是否已經建立了BeanFactory,如果建立了則銷燬關閉該BeanFactory
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //建立DefaultListableBeanFactory例項物件
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            //載入BeanDefinition資訊
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

DefaultListableBeanFactory作為BeanFactory預設實現類,其重要性不言而喻,而createBeanFactory()則返回該例項物件。

protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

loadBeanDefinition方法載入BeanDefinition資訊,BeanDefinition就是在這裡定義的。AbstractRefreshableApplicationContext對loadBeanDefinitions僅僅只是定義了一個抽象的方法,真正的實現類為其子類AbstractXmlApplicationContext來實現:

AbstractRefreshableApplicationContext:

protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;

AbstractXmlApplicationContext:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //建立bean的讀取器(Reader),即XmlBeanDefinitionReader,並通過回撥設定到容器中
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        //
        beanDefinitionReader.setEnvironment(getEnvironment());
        //為Bean讀取器設定Spring資源載入器
        beanDefinitionReader.setResourceLoader(this);
        //為Bean讀取器設定SAX xml解析器
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        //
        initBeanDefinitionReader(beanDefinitionReader);
        //Bean讀取器真正實現的地方
        loadBeanDefinitions(beanDefinitionReader);
    }

程式首先首先建立一個Reader,在前面就提到過,每一類資源都對應著一個BeanDefinitionReader,BeanDefinitionReader提供統一的轉換規則;然後設定Reader,最後呼叫loadBeanDefinition,該loadBeanDefinition才是讀取器真正實現的地方:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //獲取Bean定義資源的定位
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        //獲取Bean定義資源的路徑。在FileSystemXMLApplicationContext中通過setConfigLocations可以配置Bean資源定位的路徑
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

首先通過getConfigResources()獲取Bean定義的資源定位,如果不為null則呼叫loadBeanDefinitions方法來讀取Bean定義資源的定位。

loadBeanDefinitions是中的方法:

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        for (String location : locations) {
            counter += loadBeanDefinitions(location);
        }
        return counter;
    }

繼續:

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(location, null);
    }

再繼續:

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //獲取ResourceLoader資源載入器
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        //
        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                //呼叫DefaultResourceLoader的getResourceByPath完成具體的Resource定位
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            //呼叫DefaultResourceLoader的getResourceByPath完成具體的Resource定位
            Resource resource = resourceLoader.getResource(location);
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

在這段原始碼中通過呼叫DefaultResourceLoader的getResource方法:

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        //處理帶有classPath標識的Resource
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                //處理URL資源
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                return getResourceByPath(location);
            }
        }
    }

在getResource方法中我們可以清晰地看到Resource資源的定位。這裡可以清晰地看到getResourceByPath方法的呼叫,getResourceByPath方法的具體實現有子類來完成,在FileSystemXmlApplicationContext實現如下:

protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

這樣程式碼就回到了部落格開初的FileSystemXmlApplicationContext 中來了,它提供了FileSystemResource 來完成從檔案系統得到配置檔案的資源定義。當然這僅僅只是Spring IoC容器定位資源的一種邏輯,我們可以根據這個步驟來檢視Spring提供的各種資源的定位,如ClassPathResource、URLResource等等。下圖是ResourceLoader的繼承關係:

這裡就差不多分析了Spring IoC容器初始化過程資源的定位,在BeanDefinition定位完成的基礎上,就可以通過返回的Resource物件來進行BeanDefinition的載入、解析了。

下篇部落格將探索Spring IoC容器初始化過程的解析,Spring Resource體系結構會在後面詳細講解。

參考文獻

1、《Spring技術內幕 深入解析Spring架構與設計原理》–第二版