Spring技术内幕 :IoC容器的实现(三)—— IoC容器的初始化过程(一)

  在IoC容器实现系列的上一篇中,我们简单了解了IoC容器的两大系列:BeanFactory和ApplicationContext系列。了解了它们的设计思想与应用场景。在本篇博文中,我们将继续探索IoC容器的初始化过程。

  在上一篇中,我们知道IoC的初始化过程是由refresh()方法启动的,启动过程包括BeanDefinition的Resource定位、载入和注册三个过程。Spring将三个过程分开,使用不同的模块来完成,使得用户可以更加灵活的对三个过程进行剪裁或扩展,定义更加符合自己需求的IoC初始化过程。

  • Resource定位过程,指的是BeanDefinition的资源定位,定位过程类似于容器寻找数据的过程。由ResourceLoader统一的Resource接口来完成。
  • BeanDefinition的载入,将用户定义好的Bean表示成IoC容器的内部数据结构(BeanDefinition)。通过BeanDefinition,使得IoC容器可以方便的对POJO对象进行管理。
  • BeanDefinition的注册,通过调用BeanDefinitionRegistry接口的实现来完成。其实就是将BeanDefinition注入到一个HashMap中,IoC容器就是通过HashMap来持有这些BeanDefinition数据。

注意:IoC容器的初始化过程,一般不包含Bean依赖注入的实现。Bean定义的载入和依赖注入是两个独立的过程。

1 BeanDefinition的Resource定位

  在定位BeanDefinition是,如果使用纯粹的IoC容器,例如DefaultListableBeanFactory,则需要为它配置特定的读取器才能完成读取BeanDefinition的功能,但是这些更底层的容器可以提高定制IoC的灵活性。

  在这里,我们以FileSystemXmlApplicationContext为例,通过它分析ApplicationContext的实现是如何实现Resource的定位过程。下图为ApplicationContext的继承体系。

  从源码实现角度,近距离关心以FileSystemXmlApplicationContext为核心的继承体系,如下图所示。

  从中可以看到FileSystemXmlApplicationContext通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。下面具体看看FileSystemXmlApplicationContext是如何实现的。

package org.springframework.context.support;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

/*
    FileSystemXmlApplicationContext使用的IoC容器是DefaultListableBeanFactory
*/
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
    public FileSystemXmlApplicationContext() {
    }

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

    // 这个构造函数的configuration包含的是BeanDefinition所在的文件路径
    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[]{configLocation}, true, (ApplicationContext)null);
    }

    // 这个构造函数允许configuration包含多个BeanDefinition的文件路径
    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, (ApplicationContext)null);
    }

    // 这个构造函数允许configuration包含多个BeanDefinition的文件路径的同时,还允许指定自己的双亲IoC容器
    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, (ApplicationContext)null);
    }

    // 在对象初始化过程中,调用refresh函数载入BeanDefinition
    // 这个refresh启动了BeanDefinition的载入过程,将在下面进行详细分析
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }

    }
    
    // 应用文件系统中的Resource实现,通过构造一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition
    // getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用的。
    // loadBeanDefinition采用了模板模式,具体的定位实现实际上是由各个子类来完成的。
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }

        return new FileSystemResource(path);
    }
}

  通过上述代码,我们可以看到以XML文件方式存在的BeanDefinition都能够得到有效的处理,并且在构造方法中通过refresh方法来启动IoC容器的初始化。

  注意:FileSystempplicationContextContext是一个支持XML定义的BeanDefinition的ApplicationContext,可以指定以文件的形式读入BeanDefinition。在测试环境和独立应用环境中,这个ApplicationContext都是十分有用的。

  对BeanDefinition资源定位的过程是由refresh方法来触发的,大致调用过程如下图所示。


  在读入BeanDefinition的过程中需要使用BeanDefinitionReader,而关于这个读入器的配置,可以到FileSystemXmlApplicationContext的父类AbstractRefreshableApplicationContext中看看他是如何实现的。如果是其他类型的ApplicationContext,则会生成其他种类的Resource。

  现在我们重点研究AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现。它通过调用createBeanFactory创建一个IoC容器供ApplicationContext使用,同时它启动了loadBeanDefinitions来载入BeanDefinition。

protected final void refreshBeanFactory() throws BeansException {
    // 如果原来已经有BeanFactory,则销毁并关闭,保证每次refreshBeanFactory后产生的为新的BeanFactory
    if (this.hasBeanFactory()) {
        this.destroyBeans();
        this.closeBeanFactory();
    }
    // 这里创建并设置持有的DefaultListableBeanFactory的地方,同时并调用loadBeanDefinitions载入BeanDefinition信息
    try {
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();    // 创建IoC容器
        beanFactory.setSerializationId(this.getId());
        this.customizeBeanFactory(beanFactory);
        this.loadBeanDefinitions(beanFactory);                                // 启动对BeanDefinition的载入
        Object var2 = this.beanFactoryMonitor;
        synchronized(this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException var5) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
    }
}

// 这里就是在上下文中创建DefaultListableBeanFactory的地方,getInternalParentBeanFactory()的
// 具体实现可以参见AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器来生成
// DefaultListableBeanFactory的双亲IoC容器
protected DefaultListableBeanFactory createBeanFactory() {
    return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}

// 这里的载入Bean定义的有很多种方式载入,所以为抽象方法,交给具体容器完成相应的功能,委托给子类完成。
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;

  其中具体资源的载入在XmlBeanDefinitionReader读入BeanDefinition时完成,具体的loadBeanDefinitions可以在XmlBeanDefinitionRead的父类AbstractBeanDefinitionReader中看到,如下所示。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // 此处的ResourceLoader,使用的是DefaultResourceLoader
    ResourceLoader resourceLoader = this.getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
	// 用于记录载入Bean的个数
        int loadCount;
        // 调用DefaultResourceLoader的getResource完成具体的Resource定位
        if (!(resourceLoader instanceof ResourcePatternResolver)) {
            Resource resource = resourceLoader.getResource(location);
            loadCount = this.loadBeanDefinitions((Resource)resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }

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

            return loadCount;
        } else {
       	    // 通过对Resource的路径进行解析,得到Resource集合,这些集合指向定义好的BeanDefinition信息,可以使多个文件
            try {
                Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                loadCount = this.loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Resource[] var6 = resources;
                    int var7 = resources.length;

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

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

// 对于取得Resource的具体过程,可以参考DefaultResourceLoader是怎样完成的。
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    Iterator var2 = this.protocolResolvers.iterator();

    Resource resource;
    do {
        if (!var2.hasNext()) {
	    // 对路径的处理
            if (location.startsWith("/")) {
                return this.getResourceByPath(location);
            }
	    // 这里处理带有classpath标识的Resource
            if (location.startsWith("classpath:")) {
                return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
            }
	    
	    // 这里处理URL表示的Resource定位
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            } catch (MalformedURLException var5) {
		// 如果既不是classpath,也不是URL表示的Resource,也不是普通的path,就将任务交给getResourceByPath()
		// 该方法为protected方法,默认实现是得到一个ClassPathContextResource,这个方法通常会用子类来实现
                return this.getResourceByPath(location);
            }
        }

        ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
        resource = protocolResolver.resolve(location, this);
    } while(resource == null);

    return resource;
}

  在BeanDefinition的定位基础上,通过上面代码中返回的Resource对象就可以来进行BeanDefinition的载入工作了。下面,我们将开始介绍BeanDefinition的载入和解析。

2 BeanDefinition的载入和解析

  在第一小节中完成了对BeanDefinition的定位后,就到了整个BeanDefinition信息的载入过程,即把定义的BeanDefinition转化为一个Spring内部表示的数据结构的过程。这些BeanDefinition数据在IoC中通过一个HashMap进行保持和维护

  我们依然从DefaultListableBeanFactory的设计入手,探索IoC容器是如何完成对BeanDefinition的载入的。在前面我们知道由refresh函数启动了IoC容器的初始化,现在我们来简单介绍一下它的实现。

  该方法在AbstractApplicationContext中,详细地描述了整个ApplicationContext的初始化过程,如BeanFactory的更新,MessageSource和PostProcessor的注册等。这个过程为Bean的声明周期提供了条件,具体代码如下所示。

public void refresh() throws BeansException, IllegalStateException {
    Object var1 = this.startupShutdownMonitor;
    // 加锁,保证线程间一致性
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
	// 启动子类中refreshBeanFactory()方法。
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
	// 准备应用于上下文的BeanFactory
        this.prepareBeanFactory(beanFactory);

        try {
	    // 设置BeanFactory的后置处理
            this.postProcessBeanFactory(beanFactory);
	    // 调用BeanFactory的后置处理器,这些后置处理器是在Bean定义总向容器中注册的。
            this.invokeBeanFactoryPostProcessors(beanFactory);
	    // 注册Bean的后处理器,在Bean创建过程中调用。
            this.registerBeanPostProcessors(beanFactory);
	    // 初始化上下文消息源
            this.initMessageSource();
	    // 初始化上下文的事件机制
            this.initApplicationEventMulticaster();
	    // 初始化其他特殊Bean
            this.onRefresh();
	    // 检查监听Bean,并且将这些Bean向容器中注册
            this.registerListeners();
	    // 实例化所有的(non-lazy-init)单件
            this.finishBeanFactoryInitialization(beanFactory);
	    // 发布容器事件,结束refresh过程
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

	    // 为了防止Bean资源占用,在异常处理中销毁已经在前面生成的Bean单件,并且重置‘active’标志
            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

  进入refreshBeanFactory方法后,创建新的BeanFactory。实例代码已经在上文中出现过,这里边不再赘述。在建立好IoC容器后,就开始了初始化过程,比如BeanDefinition的载入,具体的交互过程如下图所示。

  这里调用的loadBeanDefinitions实际上为一个抽象方法,交给各个具体的容器实现该方法。在XML方式载入过程中,这个方法中在AbstractXmlApplicationContext实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把读取器在IoC容器设置好,最后启动读取器来完成BeanDefinition的载入。

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
    // 此处省略了类中的其他构造方法,这里是实现loadBeanDefinitions的地方,即根据上一小节中refreshBeanFactory得到的
    // BeanFactory将其中的BeanDefinitions信息载入IoC容器。
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 创建XmlBeanDefinitionReader,并设置到BeanFactory中。此处使用的BeanFactory也是DefaultListableBeanFactory
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
	// 这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配置ResourceLoader
	// 因为DefaultResourceLoader是父类,所以可以直接使用this
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
	// 启动Bean定义信息的载入过程
        this.initBeanDefinitionReader(beanDefinitionReader);
        this.loadBeanDefinitions(beanDefinitionReader);
    }

    protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
        reader.setValidating(this.validating);
    }

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
	// 首先以Resource的方式得到配置文件资源的位置信息,看是否存在
        Resource[] configResources = this.getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
	
	// 以String的形式获得配置文件的位置
        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }

    }

    protected Resource[] getConfigResources() {
        return null;
    }
}

  在这里我们仅仅使用XmlBeanDefinitionReader作为示例说明,因为Spring可以对应不同形式的BeanDefinition。如果使用了其他形式的BeanDefinition,则需要使用其他种类的BeanDefinitionReader完成数据载入工作。下面我们就来看看具体载入BeanDefinition的过程。因为在AbstractBeanDefinitionReader中loadBeanDefinitions方法为抽象方法,所有应该到具体的实现读取器中方法查看相应代码,代码清单如下所示。

// 这是调用的入口方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(new EncodedResource(resource));
}
// 以XML形式载入BeanDefinition
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) {
	// 这里小编不是特别明白为什么HashSet的初始大小为4?求解
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    // 如果将当前encodedResource加入到HashSet失败,则抛出异常。
    if (!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
	// 这里得到XML文件,并得到IO的InputSource,准备进行读取。
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();

            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
		// 具体调用doLoadBeanDefinitions完成相应的BeanDefinition的载入
                var5 = 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 var5;
    }
}

public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource");
}

public int loadBeanDefinitions(InputSource inputSource, String resourceDescription) throws BeanDefinitionStoreException {
    return this.doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription));
}
// 具体的读取过程,从特定的XML文件中实际载入BeanDefinition的地方
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
	// 调用doLoadDocument方法得到XML文件的Doucment对象
        Document doc = this.doLoadDocument(inputSource, resource);
	// 启动对BeanDefinition的详细解析。
        return this.registerBeanDefinitions(doc, 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);
    }
}
// 得到XML文件的Document对象,解析过程有documentLoader完成。
// documentLoader是DefaultDocumentLoader在定义documentLoader的地方创建
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}

  在调用了registerBeanDefinitions()方法后,开始对BeanDefinition进行详细解析,而且此方法对载入的Bean还做了数量统计。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 得到BeanDefinitionDocumentReader对XML的BeanDefinition进行解析
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    // 具体的解析过程在registerBeanDefinitions中完成
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    // 返回载入Bean的个数
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

  本篇博文就先到这里吧,虽然还没有写完,但本篇博文实在有点长了,所以在下一篇博文中我们将继续分析IoC初始化过程,总结也就在下一篇写啦。


猜你喜欢

转载自blog.csdn.net/m0_37135421/article/details/80631468