接下来分析BeanFactory bf = new XmlBeanFactory(new ClassPathResource("MyBean.xml"));
这行代码的实现细节。

通过XmlBeanFactory初始化时序图来看下上面代码具体是如何执行的。

spring batch 启动自动初始化表 spring的初始化过程_BeanFacory

首先调用ClassPathResource的构造函数将XML配置文件封装成一个Resource对象,然后根据Resource对象加载并注册BeanDefinition,最终得到了一个XmlBeanFactory对象。

封装配置文件

从上面我们了解到,读取配置文件是通过ClassPathResource来完成的。那么ClassPathResource具体是如何读取配置文件的呢?

Spring通过Resource接口提供了读取配置文件的方法,而ClassPathResource实现了Resource接口。从ClassPathResource层次结构图中可以看到:

spring batch 启动自动初始化表 spring的初始化过程_Resource_02


Resource接口抽象了所有Spring内部使用的底层侧缘:File、URL、ClassPath等等。对于不同的资源文件都有对应的实现:FileSystemResource、ClassPathResource、UrlResource、InputStreamResource、ByteArrayResource等等。相关结构层次图如图所示。

spring batch 启动自动初始化表 spring的初始化过程_BeanFacory_03

new ClassPathResource("MyBean.xml")到底做了哪些操作呢?

ClassPathResource.java代码片段

public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
}

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

从代码中可以看到,程序创建了一个ClassPathResource对象,并将ClassPathResource对象的path属性指向了处理后的路径名。

了解了如何将配置文件封装为Resource类型的实例后,我们继续了解XmlBeanFactory的初始化。

XmlBeanFactory的初始化

看下XmlBeanFactory的构造方法

XmlBeanFactory.java代码片段

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

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

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
}

跟踪到父类AbstractAutowireCapableBeanFactory查看super(parentBeanFactory)

AbstractAutowireCapableBeanFactory代码片段

/**
 * Create a new AbstractAutowireCapableBeanFactory.
 */
public AbstractAutowireCapableBeanFactory() {
    super();
    ignoreDependencyInterface(BeanNameAware.class);
    ignoreDependencyInterface(BeanFactoryAware.class);
    ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface方法的主要功能是忽略给定接口的自动装配功能。
这时相信大家都能看出来,this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现。跟踪到XmlBeanDefinitionReader中看看具体实现。

XmlBeanDefinitionReader代码片段

/**
 * Load bean definitions from the specified XML file.
 */
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

/**
 * Load bean definitions from the specified XML file.
 */
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());
    }

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

通过上面的代码,我们知道对Resource实例的处理过程如下:

  1. 封装资源文件。new EncodedResource(resource):使用EncodedResource对Resource实例进行封装。
  2. 获取InputStream 。InputStream inputStream = encodedResource.getResource().getInputStream();
  3. 构造InputSource。InputSource inputSource = new InputSource(inputStream);
  4. 调用doLoadBeanDefinitions(inputSource, encodedResource.getResource());继续处理资源。

到现在还没处理资源文件ㄒoㄒ~~

继续往下看看doLoadBeanDefinitions(inputSource, encodedResource.getResource());到底做了哪些操作。

/**
 * Actually load bean definitions from the specified XML file.
 ...
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        return registerBeanDefinitions(doc, resource);
    }
    catch (BeanDefinitionStoreException ex) {
    ...
}

看到这个方法的第一行注释,我就知道终于要真正加载XML文件了^_^。

阅读方法后,我们知道,这个方法做了这么几件事:

  1. 加载Xml文件,并封装成Document 实例。Document doc = doLoadDocument(inputSource, resource);
  2. 根据Document 实例注册BeanDefinitions。return registerBeanDefinitions(doc, resource);

这几个步骤支撑着整个Spring容器的实现基础,尤其是根据Document 实例注册BeanDefinitions,非常复杂。以后会详细讲解。

Spring IoC容器的初始化过程可以总结为以下几个步骤:

  1. 获取Resouce实例。加载XML配置文件,封装成Resouce实例。这时Resouce实例中已经有了配置文件的路径等信息。
  2. 获取Document实例。通过Resource,读取XML配置文件,封装成Document实例。这时Document实例中已经有了配置文件中的标签。
  3. 获取并注册BeanDefinition实例。解析Document实例中的标签,最终获得BeanDefinition实例。这时BeanDefinition实例中已经有了bean的id、name、alias、class等信息。注册过程把BeanDefinition向IOC容器进行注册,相当于将bean的name作为key,BeanDefinition作为value,放入一个map中。

下一篇文章会先讲解如何获取Document实例。