Spring IoC容器初始化 — Resource定位原始碼分析

Chenyang發表於2019-02-24

Spring IoC容器的設計中,有兩個主要的容器系列。一個是實現了BeanFactory介面的簡單容器系列,這系列容器只實現了容器基本的功能;另一個是ApplicationContext應用上下文,它在簡單容器的基礎上增加了許多面向框架的特性,同時對應用環境做了許多適配。

IoC容器的初始化過程

Spring IoC容器的初始化過程分為三個階段:Resource定位、BeanDefinition的載入和向IoC容器註冊BeanDefinitionSpring把這三個階段分離,並使用不同的模組來完成,這樣可以讓使用者更加靈活的對這三個階段進行擴充套件。

  • Resource定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource介面來完成,Resource對各種形式的BeanDefinition的使用都提供了統一的介面。
  • BeanDefinition的載入是把使用者定義好的Bean表示成IoC容器內部的資料結構,而這個容器內部的資料結構就是BeanDefinitionBeanDefinition實際上就是POJO物件在IoC容器中的抽象。通過BeanDefinitionIoC容器可以方便的對POJO物件進行管理。
  • IoC容器註冊BeanDefinition是通過呼叫BeanDefinitionRegistry介面的實現來完成的,這個註冊過程是把載入的BeanDefinitionIoC容器進行註冊。實際上,在IoC容器內部維護著一個HashMap,而這個註冊過程其實就將BeanDefinition新增至這個HashMap

我們可以自己定義ResourceBeanFactoryBeanDefinitionReader來初始化一個容器。如下程式碼片段使用了DefaultListableBeanFactory作為實際使用的IoC容器。同時,建立IoC配置檔案(dispatcher-servlet.xml)的抽象資源,這個抽象資源包含了BeanDefinition的定義資訊。最後,還需要建立一個載入BeanDefinition的讀取器,此處使用XmlBeanDefinitionReader,通過一個回撥配置給BeanFactory

ClassPathResource res = new ClassPathResource("dispatcher-servlet.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);複製程式碼

我們也可以通過ApplicationContext建立一個IoC容器。在Spring中,系統已經提供許多定義好的容器實現,而不需要自己組裝。如下程式碼片段以FileSystemXmlApplicationContext為例建立了一個IoC容器。

FileSystemXmlApplicationContext context = 
        new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");複製程式碼

無論使用哪種方式初始化IoC容器,都會經歷上述三個階段。本篇文章將結合Spring 4.0.2原始碼,並以FileSystemXmlApplicationContext為例對IoC容器初始化的第一階段,也就是Resource定位階段進行分析。

BeanDefinition的Resource定位

下圖展示了FileSystemXmlApplicationContext的繼承體系,FileSystemXmlApplicationContext繼承自AbstractApplicationContext,而AbstractApplicationContext又繼承自DefaultResourceLoaderDefaultResourceLoader實現了ResourceLoader介面。因此FileSystemXmlApplicationContext具備讀取定義了BeanDefinitionResource的能力。

Spring IoC容器初始化 — Resource定位原始碼分析

我們的分析入口是new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");,這句程式碼呼叫了FileSystemXmlApplicationContext的構造方法。FileSystemXmlApplicationContext的構造方法原始碼如下(只提取與本次分析關聯的程式碼)。

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

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh,     
                                           ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if(refresh) {
            this.refresh();
        }
    }複製程式碼

在建立FileSystemXmlApplicationContext時,我們僅傳入了包含BeanDefinition的配置檔案路徑(classpath:dispatcher-servlet.xml),由此呼叫FileSystemXmlApplicationContext(String configLocation)構造方法。接著,FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)構造方法被間接呼叫,在該構造方法內部,refresh方法完成了整個IoC容器的初始化。因此,refresh方法是我們分析的下一個入口。

refresh方法的具體實現定義在FileSystemXmlApplicationContext的父類AbstractApplicationContext中,對應的原始碼如下。

    public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var5) {
                this.destroyBeans();
                this.cancelRefresh(var5);
                throw var5;
            }

        }
    }複製程式碼

refresh方法中,通過obtainFreshBeanFactory方法,ConfigurableListableBeanFactory型別的BeanFactory被建立。我們接著進入obtainFreshBeanFactory方法,obtainFreshBeanFactory方法也定義在AbstractApplicationContext中。

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;複製程式碼

我們重點關注refreshBeanFactory方法的實現。在AbstractApplicationContext中,refreshBeanFactory方法僅僅是個宣告,具體的實現委託給了子類完成。此處,refreshBeanFactory方法的具體實現定義在了AbstractRefreshableApplicationContextAbstractRefreshableApplicationContext正是繼承自AbstractApplicationContext,這點我們可以從上文的繼承體系圖可以得知。refreshBeanFactory方法在AbstractRefreshableApplicationContext中的定義如下。

    protected final void refreshBeanFactory() throws BeansException {
        if(this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
            DefaultListableBeanFactory ex = this.createBeanFactory();
            ex.setSerializationId(this.getId());
            this.customizeBeanFactory(ex);
            this.loadBeanDefinitions(ex);
            Object var2 = this.beanFactoryMonitor;
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = ex;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;複製程式碼

refreshBeanFactory方法首先會判斷是否已經建立的BeanFactory,如果已經建立,那麼需要銷燬並關閉該BeanFactory。接著,refreshBeanFactory方法通過createBeanFactory方法建立了一個IoC容器供ApplicationContext使用,且這個IoC容器的實際型別為DefaultListableBeanFactory。同時,refreshBeanFactory方法將這個IoC容器作為引數,呼叫loadBeanDefinitions載入了BeanDefinition(本文暫不分析載入過程的具體操作)。

loadBeanDefinitions方法也僅僅在AbstractRefreshableApplicationContext中宣告,具體的實現定義在AbstractXmlApplicationContext中,從繼承體系圖我們可以得知AbstractXmlApplicationContext正是AbstractRefreshableApplicationContext的子類。loadBeanDefinitions方法對應的原始碼如下。

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        this.initBeanDefinitionReader(beanDefinitionReader);
        this.loadBeanDefinitions(beanDefinitionReader);
    }

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = this.getConfigResources();
        if(configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }

        String[] configLocations = this.getConfigLocations();
        if(configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }複製程式碼

loadBeanDefinitions(DefaultListableBeanFactory beanFactory)中,定義了BeanDefinition的讀入器beanDefinitionReaderSpring把定位、讀入和註冊的過程解耦,這正是體現之處之一。接著beanDefinitionReader作為引數,呼叫loadBeanDefinitions(XmlBeanDefinitionReader reader)方法,如果configResources為空,那麼reader就會根據configLocations呼叫readerloadBeanDefinitions去載入相應的Resource。在AbstractBeanDefinitionReaderXmlBeanDefinitionReader中個自定義了不同的loadBeanDefinitions方法,與我們本次分析相關的程式碼定義在AbstractBeanDefinitionReader中,如下所示。

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        String[] var3 = locations;
        int var4 = locations.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];
            counter += this.loadBeanDefinitions(location);
        }

        return counter;
    }

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

    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if(resourceLoader == null) {
            throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        } else {
            int loadCount;
            if(!(resourceLoader instanceof ResourcePatternResolver)) {
                Resource var11 = resourceLoader.getResource(location);
                loadCount = this.loadBeanDefinitions((Resource)var11);
                if(actualResources != null) {
                    actualResources.add(var11);
                }

                if(this.logger.isDebugEnabled()) {
                    this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
                }

                return loadCount;
            } else {
                try {
                    Resource[] resource =
                        ((ResourcePatternResolver)resourceLoader).getResources(location);
                    loadCount = this.loadBeanDefinitions(resource);
                    if(actualResources != null) {
                        Resource[] var6 = resource;
                        int var7 = resource.length;

                        for(int var8 = 0; var8 < var7; ++var8) {
                            Resource resource1 = var6[var8];
                            actualResources.add(resource1);
                        }
                    }

                    if(this.logger.isDebugEnabled()) {
                        this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                    }

                    return loadCount;
                } catch (IOException var10) {
                    throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
                }
            }
        }
    }複製程式碼

loadBeanDefinitions(String location, Set<Resource> actualResources)方法中,我們可以看到,Resource的定位工作交給了ResourceLoader來完成。對於取得Resource的具體過程,我們可以看看DefaultResourceLoader是怎樣完成的,對應原始碼如下。

    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if(location.startsWith("classpath:")) {
            return new ClassPathResource(location.substring("classpath:".length()),
                                         this.getClassLoader());
        } else {
            try {
                URL ex = new URL(location);
                return new UrlResource(ex);
            } catch (MalformedURLException var3) {
                return this.getResourceByPath(location);
            }
        }
    }複製程式碼

由於我們傳入的locationclasspath:dispatcher-servlet.xml,因此getResource方法會生成一個ClassPathResource並返回,如果我們傳入的是一個檔案路徑,那麼會呼叫getResourceByPath方法,getResourceByPath方法定義在FileSystemXmlApplicationContext中,對應的原始碼如下。

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

到此,我們完成了IoC容器在初始化過程中的Resource定位過程的流程分析,這為接下來進行BeanDefinition資料的載入和解析創造了條件。

後續我會對BeanDefinition的載入和解析過程結合原始碼進行分析,歡迎關注。若本文存在分析不妥之處,建議傳送郵件至tinylcy (at) gmail.com交流。

相關文章